'; // TODO: add help text.
}
//hack to get drupal_get_messages before they are destroyed.
$GLOBALS['_boost_message_count'] = count(drupal_get_messages(NULL, FALSE));
}
/**
* Get some basic node attribues from the database given node id.
*
* @param $nid
* node ID
* @return node object
* node->nid
* node->type
* node->language
* node->path
* node->domain
* node->tids
*/
function boost_node_get_basics($nid) {
static $nodes = array();
// Is the node statically cached?
if (isset($nodes[$nid])) {
return $nodes[$nid];
}
// Retrieve node nid, type, language.
$op = 'load';
$node = db_fetch_object(db_query("SELECT nid, type, language FROM {node} WHERE nid = %d", $nid));
// Get path info
if (module_exists('path')) {
path_nodeapi($node, $op, NULL, NULL);
}
// Get domain access info
if (module_exists('domain')) {
domain_nodeapi($node, $op, NULL, NULL);
}
// Get taxonomy tid info
if (module_exists('taxonomy')) {
$node->tids = boost_taxonomy_node_get_tids($node->nid);
}
$nodes[$nid] = $node;
return $nodes[$nid];
}
/**
* Implementation of hook_ctools_render_alter().
*
* Needed to get the nodes that are inside of a panel's contex
*/
function boost_ctools_render_alter($info, $page, $args, $contexts, $task, $subtask, $handler) {
// return if page not going to be cached or if database turned off
if (!$GLOBALS['_boost_cache_this'] || BOOST_NO_DATABASE) {
return;
}
foreach ($handler->conf['display']->context as $context) {
if ($context->type == 'node') {
$node = $context->data;
// skip if node type is not set
if (!isset($node->type)) {
continue;
}
// set data
$relationship = array(
'child_page_callback' => 'node',
'child_page_type' => $node->type,
'child_page_id' => $node->nid,
);
if (BOOST_VERBOSE >= 9) {
$relationship['debug'] = array(
'panel-task' => $handler->task,
'panel-subtask' => $handler->subtask,
'node-path' => $node->path,
);
}
// send to global
$GLOBALS['_boost_relationships'][] = $relationship;
}
}
}
/**
* Implementation of hook_views_pre_render().
*
* This is called right before the render process. Used to grab the NID's listed
* in this view, and set the view node relationship in the database.
*
* @param &$view
* reference to the view being worked on
*/
function boost_views_pre_render(&$view) {
// return if not a view, page not going to be cached, or if database turned off
if (is_null($view) || !$GLOBALS['_boost_cache_this'] || BOOST_NO_DATABASE) {
return;
}
// return if view doesn't belong in the set of views to search
$views = boost_views_get_valid_array();
$hash = $view->name . ' - ' . $view->current_display;
if (!array_key_exists($hash, $views)) {
return;
}
foreach ($view->result as $item) {
// skip if nid is not a number
if (!is_numeric($item->nid)) {
continue;
}
$node = boost_node_get_basics($item->nid);
// skip if node type is not set
if (!isset($node->type)) {
continue;
}
// set data
$relationship = array(
'child_page_callback' => 'node',
'child_page_type' => $node->type,
'child_page_id' => $item->nid,
);
if (BOOST_VERBOSE >= 9) {
$relationship['debug'] = array(
'view-name' => $view->name,
'view-display' => $view->current_display,
'node-path' => $node->path,
);
}
// send to global
$GLOBALS['_boost_relationships'][] = $relationship;
}
}
/**
* Genrate a list of views based off of defaults.
*
* Views that have a path, can be cached by boost & anonymous users can access
*
* @return array
* (view_name . ' - ' . display_name . ' - /' . path => Enabled/Disabled)
*/
function boost_views_generate_default_list() {
$account = user_load(0);
$views = views_get_all_views();
$enabled = array();
$disabled = array();
foreach ($views as $view_name => $view) {
// disabled views get nothing.
if (!empty($view->disabled)) {
unset($views[$view_name]);
continue;
}
$view->init_display();
foreach ($view->display as $display_id => $display) {
// Anonymous users can access view
if (!$view->access($display_id, $account)) {
continue;
}
$hash = $view_name . ' - ' . $display_id;
// Use the default view
if (strcmp($display_id, 'default') == 0) {
$enabled[$hash] = $hash;
}
// View with a path
elseif (isset($display->display_options['path'])) {
$path = $display->display_options['path'];
$hash = $view_name . ' - ' . $display_id . ' - /' . $path;
// Path is cacheable via boost & does not take arguments
if (boost_is_cacheable($path) && !stristr($path, '%')) {
$enabled[$hash] = $hash;
}
// Path is not cacheable via boost or view takes arguments
else {
$disabled[$hash] = 0;
}
}
// All other displays mark as disabled
else {
$disabled[$hash] = 0;
}
}
}
ksort($disabled);
natcasesort($enabled);
$checkboxes = array_merge(array('line-break' => 0), $disabled, $enabled);
return $checkboxes;
}
/**
* Genrate a list of valid views that boost will search for new content.
*
* @return array
*/
function boost_views_get_valid_list($fresh = FALSE) {
// Load saved default values; if not saved, save it.
$defaults = variable_get('boost_views_list_default', FALSE);
if (!$defaults || $fresh) {
$defaults = boost_views_generate_default_list();
variable_set('boost_views_list_default', $defaults);
}
// Load custom settings
$list = variable_get('boost_views_list_custom', array());
// Load defaults. http://php.net/operators.array
$list += $defaults;
return $list;
}
/**
* Genrate a list of valid views that boost will search for new content.
*
* @return array
*/
function boost_views_get_valid_array() {
$list = boost_views_get_valid_list();
$views = array();
foreach ($list as $hash => $enabled) {
if ($enabled) {
$info = explode(' - ', $hash);
$page_type = $info[0];
$page_id = $info[1];
$key = $page_type . ' - ' . $page_id;
$views[$key]['page_type'] = $page_type;
$views[$key]['page_id'] = $page_id;
}
}
return $views;
}
/**
* Implementation of hook_views_pre_view().
*
* Hack due to this issue: http://drupal.org/node/619852
*/
function boost_views_pre_view(&$view) {
}
/**
* Implementation of hook_init(). Performs page setup tasks if page not cached.
*/
function boost_init() {
global $user, $base_path, $base_root, $base_url;
// Force lowercase host name
$base_root = strtolower($base_root);
$parts = parse_url($base_url);
$parts['host'] = strtolower($parts['host']);
$base_url = boost_glue_url($parts);
// Make sure this is the correct domain
// Only works if $base_url is set in settings.php
if (strcmp($parts['host'], strtolower($_SERVER['HTTP_HOST'])) != 0) {
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
$GLOBALS['_boost_cache_this'] = FALSE;
return;
}
// Check if Drupal is started from index.php - could cause problems with other
// contrib modules like ad module.
if (strpos($_SERVER['SCRIPT_FILENAME'], 'index.php') !== FALSE) {
$uid = isset($user->uid) ? $user->uid : 0;
// Remove Boost cookie at logout if it still exists
if (BOOST_AGGRESSIVE_COOKIE && isset($_COOKIE[BOOST_COOKIE]) && $uid == 0) {
boost_set_cookie($uid, BOOST_TIME - 86400);
}
// Remove Boost cookie if set to -1
elseif (isset($_COOKIE[BOOST_COOKIE]) && $_COOKIE[BOOST_COOKIE] == '-1') {
boost_set_cookie($uid, BOOST_TIME - 86400);
}
// Set Boost cookie if it doesn't exists and user is logged in
elseif (BOOST_AGGRESSIVE_COOKIE && !isset($_COOKIE[BOOST_COOKIE]) && $uid != 0) {
boost_set_cookie($uid);
}
}
// Disable all caches when nocache is set
if (isset($_GET['nocache'])) {
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
$GLOBALS['_boost_cache_this'] = FALSE;
return;
}
// Make sure this is not a 404 redirect from the htaccesss file
$path = explode($base_path, request_uri());
array_shift($path);
$path = implode($base_path, $path);
$path = explode('?', $path);
$path = array_shift($path);
if ($path != '' && empty($_REQUEST['q']) && !stristr($path, '.php') && isset($_SERVER['REDIRECT_STATUS']) && $_SERVER['REDIRECT_STATUS'] == 404) {
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
$GLOBALS['_boost_cache_this'] = FALSE;
if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_init_404'])) {
watchdog('boost', '404 received from server via redirect, going to send a 404. Info: !output', array('!output' => boost_print_r($_SERVER, TRUE, TRUE)));
}
drupal_not_found();
return;
}
//set variables
if (empty($_REQUEST['q'])) {
//front page
$GLOBALS['_boost_path'] = '';
}
else {
$GLOBALS['_boost_path'] = $_REQUEST['q'];
}
// Remove anchor tags from url.
if (stristr($GLOBALS['_boost_path'], '#')) {
$GLOBALS['_boost_path'] = array_shift(explode('#', $GLOBALS['_boost_path']));
}
// Make the proper filename for our query
$GLOBALS['_boost_query'] = BOOST_CHAR;
$query = array();
foreach ($_GET as $key => $val) {
if (BOOST_PAGER_CLEAN && $key == 'page') {
$GLOBALS['_boost_path'] .= '/page/' . $val;
continue;
}
if ($key != 'q' && $key != 'destination') {
$query[$key] = $val;
}
}
$GLOBALS['_boost_query'] .= str_replace('&', '&', urldecode(http_build_query($query)));
if (!empty($user->uid)) {
boost_set_cookie($user->uid);
if (BOOST_DISABLE_CLEAN_URL) {
$GLOBALS['conf']['clean_url'] = 0;
db_query('TRUNCATE {cache_filter}');
db_query('TRUNCATE {cache_menu}');
cache_clear_all('*', 'cache_menu');
cache_clear_all('*', 'cache_filter');
}
}
// Make sure the page is/should be cached according to our current configuration
if ( strpos($_SERVER['SCRIPT_FILENAME'], 'index.php') === FALSE
|| variable_get('site_offline', 0)
|| ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD')
|| $_SERVER['SERVER_SOFTWARE'] === 'PHP CLI'
|| !variable_get('boost_enabled', CACHE_NORMAL)
|| !boost_is_cacheable($GLOBALS['_boost_path'])
) {
$GLOBALS['_boost_cache_this'] = FALSE;
return;
}
// We only generate cached pages for anonymous visitors.
if (empty($user->uid)) {
if (variable_get('boost_enabled', CACHE_NORMAL) != CACHE_AGGRESSIVE) {
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
}
$GLOBALS['_boost_cache_this'] = TRUE;
register_shutdown_function('_boost_ob_handler');
ob_start();
}
}
/**
* Implementation of hook_domainupdate() - keeps domain whitelist variable
* current
*/
function boost_domainupdate($op, $domain, $form_state = array()) {
switch ($op) {
case 'create':
if (variable_get('boost_domain_whitelist_use_domain', FALSE)) {
$whitelist = variable_get('boost_domain_whitelist', array());
$whitelist[ $domain['subdomain'] ] = $domain['subdomain'];
asort($whitelist);
variable_set('boost_domain_whitelist', $whitelist);
}
break;
case 'update':
if (variable_get('boost_domain_whitelist_use_domain', FALSE)) {
$whitelist = variable_get('boost_domain_whitelist', array());
unset($whitelist[ $domain['subdomain'] ]);
$new_name = $form_state['values']['subdomain'];
$whitelist[ $new_name ] = $new_name;
asort($whitelist);
variable_set('boost_domain_whitelist', $whitelist);
}
break;
case 'delete':
if (variable_get('boost_domain_whitelist_use_domain', FALSE)) {
$whitelist = variable_get('boost_domain_whitelist', array());
unset($whitelist[ $domain['subdomain'] ]);
variable_set('boost_domain_whitelist', $whitelist);
}
break;
}
}
/**
* Grabs drupal_goto requests via boost_exit and looks for redirects.
*
* Looks at the current page and the destination, seeing if the internal name
* is the same; node/8 == node/8.
*
* @param $destination
* URL that user will be sent to soon.
*/
function boost_redirect_handler($destination) {
global $base_path, $base_root;
if (empty($destination)) {
return;
}
$source = $base_root . request_uri();
// Parse the URLs
$new_parts = parse_url($destination);
$current_parts = parse_url($source);
// Get paths
$current_path = ltrim($current_parts['path'], $base_path);
$current_path_system = $_GET['q'];
$new_path = ltrim($new_parts['path'], $base_path);
$new_path_system = drupal_get_normal_path($new_path);
if (empty($new_path)) {
$new_path_system = variable_get('site_frontpage', 'node');
}
// Build alt source url
$alt_parts = $current_parts;
$alt_parts['path'] = $current_path_system;
$alt_src = boost_glue_url($alt_parts);
$urls = array($alt_src, $source);
// Handle domain alias redirects
if ( module_exists('domain_alias')
&& isset($_domain['redirect'])
&& $_domain['redirect'] == TRUE
) {
boost_cache_kill_url($urls);
return;
}
// Redirect is not to the same domain
elseif (strcmp($new_parts['host'], $current_parts['host']) != 0) {
//watchdog('boost-redirect', 'redirect is not to the same domain' . str_replace(' ', ' ', nl2br(htmlentities(print_r(array($debug), TRUE)))));
boost_cache_kill_url($urls);
return;
}
// Check for globalredirect internal to alias redirect
if (strcmp($current_path, $new_path_system) == 0) {
boost_cache_kill_url($urls);
return;
}
// Check for globalredirect alias to alias redirect (deslashing)
// Also grabs not clean to clean redirects
if (strcmp($current_path, $_REQUEST['q']) != 0) {
if (strcmp($current_path_system, $new_path_system) == 0) {
boost_cache_kill_url($urls);
return;
}
}
if (module_exists('path_redirect')) {
// Check for normal path_redirect alias to alias redirect
$path_redirects = boost_path_redirect_load(array('source' => $current_path));
if (isset($path_redirects)) {
foreach ($path_redirects as $path_redirect) {
$current_path_system = $path_redirect['redirect'];
break;
}
}
if (strcmp($current_path_system, $new_path_system) == 0) {
boost_cache_kill_url($urls);
return;
}
// Check for alt path_redirect alias to alias redirect
$path_redirects = boost_path_redirect_load(array('source' => $current_path_system));
if (isset($path_redirects)) {
foreach ($path_redirects as $path_redirect) {
$current_path_system = $path_redirect['redirect'];
break;
}
}
if (strcmp($current_path_system, $new_path_system) == 0) {
boost_cache_kill_url($urls);
return;
}
}
// Last attempt of getting a "match" for this redirect
$result = db_query("SELECT page_callback, page_type, page_id FROM {boost_cache} WHERE expire = 0 AND (hash_url = '%s' OR hash_url = '%s')", md5($source), md5($alt_src));
while ($row = db_fetch_array($result)) {
// Handle node redirects
if ($row['page_callback'] == 'node') {
$current_path_system = $row['page_callback'] . '/' . $row['page_id'];
if (strcmp($current_path_system, $new_path_system) == 0) {
boost_cache_kill_url($urls);
return;
}
}
}
}
/**
* Implementation of hook_exit(). Performs cleanup tasks.
*
* For POST requests by anonymous visitors, this adds a dummy query string
* to any URL being redirected to using drupal_goto().
*
* This is pretty much a hack that assumes a bit too much familiarity with
* what happens under the hood of the Drupal core function drupal_goto().
*
* It's necessary, though, in order for any session messages set on form
* submission to actually show up on the next page if that page has been
* cached by Boost.
*
* @param $destination
* URL that user will be sent to soon.
*/
function boost_exit($destination = NULL) {
global $_boost, $user;
// Check for redirects
if (!empty($destination) && $_SERVER['REQUEST_METHOD'] != 'POST' && empty($_GET['destination'])) {
// Make sure path functions are available.
drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);
boost_redirect_handler($destination);
}
// 404, 403 dection and removal from the boost cache.
// Only run if user is anonymous.
if (empty($user->uid)) {
$status = boost_get_http_status();
if ($status == 404 || $status == 403) {
// Bail out of caching
$GLOBALS['_boost_cache_this'] = FALSE;
// Get content type
$types = boost_get_content_type();
$types = array_pop($types);
// Match extension
if (stristr($types, 'text/javascript')) {
$extension = BOOST_JSON_EXTENSION;
}
elseif ( stristr($types, 'application/rss')
|| stristr($types, 'text/xml')
|| stristr($types, 'application/rss+xml')
) {
$extension = BOOST_JSON_EXTENSION;
}
elseif (stristr($types, 'text/html') && BOOST_CACHE_HTML) {
$extension = BOOST_FILE_EXTENSION;
}
// Get filename
if (!empty($extension)) {
$filename = boost_file_path($GLOBALS['_boost_path'], TRUE, $extension);
// Remove dead item from the cache (file & db);
if ($filename) {
$files = array(array('filename' => $filename));
boost_cache_kill($files, TRUE);
boost_remove_db($files);
}
}
}
}
// Check that hook_exit() was invoked by drupal_goto() for a POST request:
// Check that we're dealing with an anonymous visitor. and that some
// session messages have actually been set during this page request:
if ( !empty($destination)
&& $_SERVER['REQUEST_METHOD'] == 'POST'
&& empty($user->uid)
&& $messages = drupal_set_message()
) {
$query_parts = parse_url($destination);
// Add a nocache parameter to query. Such pages will never be cached
$query_parts['query'] .= (empty($query_parts['query']) ? '' : '&') . 'nocache=1';
// Rebuild the URL with the new query string. Do not use url() since
// destination has presumably already been run through url().
$destination = boost_glue_url($query_parts);
// Do what drupal_goto() would do if we were to return to it:
if (BOOST_EXIT_IN_HOOK_EXIT) {
// FIXME: call any remaining exit hooks since we're about to terminate?
exit(header('Location: ' . $destination));
}
else {
header('Location: ' . $destination);
}
}
// Set watchdog error if headers already sent
if ( BOOST_ASYNCHRONOUS_OUTPUT
&& isset($GLOBALS['_boost_cache_this'])
&& $GLOBALS['_boost_cache_this']
&& headers_sent($filename, $linenum)
&& !boost_headers_contain('Location: ')
&& BOOST_VERBOSE >= 7
&& isset($_boost['verbose_option_selected']['boost_exit_headers'])
) {
watchdog('boost', 'boost_exit() Debug: Headers already sent in @filename on line @linenum. Asynchronous Operation will not be used.', array('@filename' => $filename, '@linenum' => $linenum));
}
}
/**
* Implementation of hook_menu().
*/
function boost_menu() {
$items['admin/settings/performance/default'] = array(
'title' => 'Performance',
'type' => MENU_DEFAULT_LOCAL_TASK,
'file path' => drupal_get_path('module', 'system'),
);
$items['admin/settings/performance/boost'] = array(
'title' => 'Boost Settings',
'description' => 'Advanced boost configuration.',
'page callback' => 'drupal_get_form',
'page arguments' => array('boost_admin_boost_performance_page'),
'access arguments' => array('administer site configuration'),
'weight' => 10,
'type' => MENU_LOCAL_TASK,
'file' => 'boost.admin.inc',
);
$items['admin/settings/performance/boost-rules'] = array(
'title' => 'Boost htaccess rules generation',
'description' => 'htaccess boost rules.',
'page callback' => 'drupal_get_form',
'page arguments' => array('boost_admin_htaccess_page'),
'access arguments' => array('administer site configuration'),
'weight' => 12,
'type' => MENU_LOCAL_TASK,
'file' => 'boost.admin.inc',
);
$items['boost_stats.php'] = array(
'page callback' => 'boost_stats_ajax_callback',
'type' => MENU_CALLBACK,
'access callback' => 1,
'access arguments' => array('access content'),
'file path' => drupal_get_path('module', 'boost'),
'file' => 'stats/boost_stats.ajax.inc',
);
$items['boost-crawler'] = array(
'page callback' => 'boost_crawler_run',
'type' => MENU_CALLBACK,
'access callback' => 1,
'access arguments' => array('access content'),
'file path' => drupal_get_path('module', 'boost'),
);
$items['boost_views.php'] = array(
'page callback' => 'boost_views_async',
'type' => MENU_CALLBACK,
'access arguments' => array('access content'),
'file path' => drupal_get_path('module', 'boost'),
);
return $items;
}
/**
* Implementation of hook_form_alter(). Performs alterations before a form
* is rendered.
*/
function boost_form_alter(&$form, $form_state, $form_id) {
switch ($form_id) {
// Alter Drupal's system performance settings form by hiding the default
// cache enabled/disabled control (which will now always default to
// CACHE_DISABLED), and inject our own settings in its stead.
case 'system_performance_settings':
module_load_include('inc', 'boost', 'boost.admin');
$form['page_cache'] = boost_admin_performance_page($form['page_cache']);
$form['#submit'][] = 'boost_admin_performance_page_submit';
$form['clear_cache']['clear']['#submit'][0] = 'boost_admin_clear_cache_submit';
break;
// Alter Drupal's site maintenance settings form in order to ensure that
// the static page cache gets wiped if the administrator decides to take
// the site offline.
case 'system_site_maintenance_settings':
module_load_include('inc', 'boost', 'boost.admin');
$form['#submit'][] = 'boost_admin_site_offline_submit';
break;
// Alter Drupal's modules build form in order to ensure that
// the static page cache gets wiped if the administrator decides to
// change enabled modules
case 'system_modules':
module_load_include('inc', 'boost', 'boost.admin');
$form['#submit'][] = 'boost_admin_modules_submit';
break;
// Alter Drupal's theme build form in order to ensure that
// the static page cache gets wiped if the administrator decides to
// change theme
case 'system_themes_form':
module_load_include('inc', 'boost', 'boost.admin');
$form['#submit'][] = 'boost_admin_themes_submit';
// Added below due to this bug: http://drupal.org/node/276615
if ( variable_get('preprocess_css', FALSE)==TRUE
&& version_compare(VERSION, 6.13, '<=')
&& boost_cache_clear_all()
) {
drupal_set_message(t('Boost: Static page cache cleared. See http://drupal.org/node/276615 for reason why (core bug that is fixed in 6.14+).'), 'warning');
}
break;
}
}
/**
* Implementation of hook_cron(). Performs periodic actions.
*/
function boost_cron() {
if (!variable_get('boost_enabled', CACHE_NORMAL)) {
return;
}
global $_boost;
// Remove old domains if they are now a redirect
if (module_exists('domain_alias')) {
$domains = domain_domains();
$old_domains = array();
foreach ($domains as $key => $value) {
foreach ($value['aliases'] as $alias) {
if ($alias['redirect'] == 1) {
$old_domains[] = boost_cache_directory($alias['pattern'], FALSE);
}
}
}
if (!empty($old_domains)) {
$result = boost_db_multi_select_in('boost_cache', 'base_dir', "'%s'", $old_domains);
$files = array();
while ($row = db_fetch_array($result)) {
$files[] = array('filename' => $row['filename'], 'hash' => $row['hash']);
}
if (!empty($files)) {
boost_cache_kill($files, TRUE);
boost_remove_db($files);
}
}
}
// Remove old domains if they are "inactive"
if (module_exists('domain')) {
$domains = domain_domains();
$old_domains = array();
foreach ($domains as $key => $value) {
if ($value['valid'] == 0) {
$old_domains[] = boost_cache_directory($value['subdomain'], FALSE);
}
}
if (!empty($old_domains)) {
$result = boost_db_multi_select_in('boost_cache', 'base_dir', "'%s'", $old_domains);
$files = array();
while ($row = db_fetch_array($result)) {
$files[] = array('filename' => $row['filename'], 'hash' => $row['hash']);
}
if (!empty($files)) {
boost_cache_kill($files, TRUE);
boost_remove_db($files);
}
}
}
// Check for new views on cron
if (module_exists('views') && BOOST_VIEWS_LIST_BEHAVIOR == 0) {
$defaults = boost_views_generate_default_list();
variable_set('boost_views_list_default', $defaults);
}
$expire = TRUE;
if (BOOST_CHECK_BEFORE_CRON_EXPIRE) {
$expire = boost_has_site_changed(TRUE);
}
// Expire old content
if (!BOOST_LOOPBACK_BYPASS && variable_get('boost_expire_cron', TRUE) && $expire && boost_cache_expire_all()) {
if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_cron_expire'])) {
watchdog('boost', 'Expired stale files from static page cache.', array(), WATCHDOG_NOTICE);
}
}
// Update Stats
if (module_exists('statistics') && variable_get('boost_block_show_stats', FALSE)) {
$block = module_invoke('statistics', 'block', 'view', 0);
variable_set('boost_statistics_html', $block['content']);
}
// Crawl Site
if (BOOST_CRAWL_ON_CRON && !variable_get('site_offline', 0)) {
boost_crawler_run((int)$expire);
}
}
/*
* Implementation of hook_flush_caches(). Deletes all static files.
*/
function boost_flush_caches() {
if (variable_get('cron_semaphore', FALSE)==FALSE && (variable_get('preprocess_css', FALSE)==TRUE || variable_get('preprocess_js', FALSE)==TRUE)) {
boost_cache_clear_all();
}
return;
}
/**
* Implementation of hook_comment(). Acts on comment modification.
*/
function boost_comment($comment, $op) {
if (!variable_get('boost_enabled', CACHE_NORMAL)) return;
if (is_array($comment)) {
$comment = (object)$comment;
}
// Expire the relevant node page from the static page cache to prevent serving stale content:
switch ($op) {
case 'insert':
case 'update':
case 'publish':
case 'unpublish':
case 'delete':
if (!empty($comment->nid)) {
$node = node_load($comment->nid);
boost_expire_node($node, $comment->nid);
}
break;
}
}
/**
* Sets the base_dir array key based on settings.
*
* @param array &$data
* array that might want to have the base_dir key added to it.
*/
function boost_set_base_dir_in_array(&$data) {
if ($data) {
foreach ($data as $key => $value) {
if (!array_key_exists('base_dir', $data[$key]) && !BOOST_FLUSH_ALL_MULTISITE) {
$data[$key]['base_dir'] = BOOST_FILE_PATH;
}
if (array_key_exists('base_dir', $data[$key]) && BOOST_FLUSH_ALL_MULTISITE) {
unset($data[$key]['base_dir']);
}
}
}
}
/**
* Implementation of hook_nodeapi(). Acts on nodes defined by other modules.
*/
function boost_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
global $_boost;
if (!variable_get('boost_enabled', CACHE_NORMAL) || !isset($node->nid)) {
return;
}
$data[] = array('page_callback' => 'node', 'page_id' => $node->nid);
boost_set_base_dir_in_array($data);
switch ($op) {
case 'insert':
boost_expire_node($node);
// Run all cached views, looking for this new node
if (BOOST_FLUSH_VIEWS_INSERT && module_exists('views')) {
$GLOBALS['_boost_nid'] = $node->nid;
$_boost['new_nodes'][$node->nid] = $node->nid;
register_shutdown_function('_boost_view_insert');
}
// Insert new node into boost_cache table
if (variable_get('boost_insert_node_on_creation', FALSE)) {
$GLOBALS['_boost_nid'] = $node->nid;
$_boost['new_nodes'][$node->nid] = $node->nid;
register_shutdown_function('_boost_cache_insert');
}
break;
case 'update':
boost_expire_node($node);
// Run all cached views, looking for this new node
if (BOOST_FLUSH_VIEWS_UPDATE && module_exists('views')) {
$GLOBALS['_boost_nid'] = $node->nid;
$_boost['new_nodes'][$node->nid] = $node->nid;
register_shutdown_function('_boost_view_insert');
}
// if node is not published, delete it.
if (!$node->status) {
boost_cache_expire_router($data, TRUE, TRUE);
}
break;
case 'delete':
boost_expire_node($node);
boost_cache_expire_router($data, TRUE, TRUE);
break;
case 'presave':
// If path changes remove old path entry from database
// Logic taken from path_redirect_node_presave()
if (!empty($node->path)) {
$node_path = 'node/'. $node->nid;
$old_alias = drupal_get_path_alias($node_path, ($node->language ? $node->language : ''));
if ($old_alias != $node_path && $node->path != $old_alias) {
// If the user is manually changing the path alias, nuke the old files
boost_cache_expire_router($data, TRUE, TRUE);
}
}
break;
}
}
/**
* Shutdown function, gets called at the very end of node creation.
*
* Node is now created, thus we can get the node path and set the boost_cache
* table.
*/
function _boost_cache_insert() {
static $processed = FALSE;
if ($processed) {
return;
}
global $_boost;
if (empty($_boost['new_nodes'])) {
return FALSE;
}
// Ensure we're in the correct working directory, since some web servers (e.g. Apache) mess this up here.
chdir(dirname($_SERVER['SCRIPT_FILENAME']));
// Load node
foreach ($_boost['new_nodes'] as $nid) {
$node = boost_node_get_basics($nid);
if (!$node) {
continue;
}
$router_item = array();
$router_item['page_callback'] = 'node';
$router_item['page_type'] = $node->type;
$router_item['page_id'] = $node->nid;
// Set DB defaults
$expire = 0;
$lifetime = -1;
$push = -1;
$timer = 0;
$timer_average = 0;
$extension = BOOST_FILE_EXTENSION;
// Get list of base urls for this node
$base_urls = array();
foreach (boost_get_base_urls($node) as $domain_id) {
foreach ($domain_id as $base) {
$base_urls[] = $base . $node->path;
}
}
// Insert each url into the DB
foreach ($base_urls as $url) {
$parts = parse_url($url);
$file_path = boost_cache_directory($parts['host'], FALSE);
$filename = boost_file_path($node->path, FALSE, BOOST_FILE_EXTENSION, $file_path);
boost_put_db($filename, $expire, $lifetime, $push, $router_item, $timer, $timer_average, $extension, $url, $file_path);
}
}
$processed = TRUE;
}
/**
* Get domains the node is currently published to
*
* @param $node
* node object
* @return array
* array('$gid' => $gid)
*/
function boost_get_domains(&$node) {
if (empty($node->nid)) {
return array();
}
$domains = array();
$result = db_query("SELECT gid FROM {domain_access} WHERE nid = %d", $node->nid);
while ($row = db_fetch_array($result)) {
$gid = $row['gid'];
$domains[$gid] = $gid;
}
return $domains;
}
/**
* Get all base url's where this node can appear
*
* @param $node
* node object
* @param $reset
* A boolean flag to clear the static variable if necessary.
* @return array
* array(0 => array(0 => $base_url . '/'))
*/
function boost_get_base_urls(&$node, $reset = FALSE) {
global $base_url, $base_path;
// Get list of URL's if using domain access
$base_urls = array();
$domains = array();
if (module_exists('domain') && isset($node->domains)) {
// Get domains from node object
foreach ($node->domains as $key => $domain_id) {
if ($key != $domain_id) {
continue;
}
$domains[$domain_id] = $domain_id;
}
// Get domains from database
foreach (boost_get_domains($node) as $domain_id) {
$domains[$domain_id] = $domain_id;
}
// Get aliases and set base url
foreach ($domains as $domain_id) {
$domain = domain_lookup($domain_id, NULL, $reset);
if ($domain['valid'] == 1) {
if (isset($domain['path'])) {
$base_urls[$domain_id][] = $domain['path'];
}
if (is_array($domain['aliases'])) {
foreach ($domain['aliases'] as $alias) {
$alias['pattern'] = trim($alias['pattern']);
if ($alias['redirect'] != 1 && !empty($alias['pattern'])) {
$temp_domain = array('scheme' => $domain['scheme'], 'subdomain' => $alias['pattern']);
$base_urls[$domain_id][] = domain_get_path($temp_domain);
}
}
}
}
}
}
else {
$base_urls[0][] = $base_url . '/';
}
return $base_urls;
}
/**
* Run views looking for new nodes.
*/
function boost_views_async() {
// Exit if no nodes
if (empty($_GET['new_nodes']) || !is_array($_GET['new_nodes'])) {
return;
}
// Exit if key does not match.
if (!empty($_GET['key'])) {
$key = variable_get('boost_crawler_key', FALSE);
if ($key == $_GET['key']) {
// Break connection so processing is async. Return key.
boost_async_opp($_GET['key']);
}
else {
return;
}
}
else {
return;
}
// Get list of nodes to process.
global $_boost;
$_boost['new_nodes'] = $_GET['new_nodes'];
// Process list.
return _boost_views_runit();
}
/**
* Shutdown function, gets called at the very end of node creation.
*
* Node is now created, thus views has access to the new node. Searches all
* cached views for newly created node. Expires the outdated views from the cache.
*/
function _boost_view_insert() {
global $_boost, $base_url, $base_path;
static $processed = FALSE;
// Only run once
if ($processed) {
return;
}
// Exit if no new nodes
if (empty($_boost['new_nodes'])) {
return FALSE;
}
// Make sure node is numeric
foreach ($_boost['new_nodes'] as $key => $value) {
if (!is_numeric($value) || !is_numeric($key)) {
unset($_boost['new_nodes'][$key]);
}
}
if (empty($_boost['new_nodes'])) {
return;
}
// Prep for async
// URL key.
$key = variable_get('boost_crawler_key', FALSE);
if ($key == FALSE) {
variable_set('boost_crawler_key', mt_rand());
$key = variable_get('boost_crawler_key', FALSE);
}
// Query string.
$query = array(
'new_nodes' => $_boost['new_nodes'],
'rand' => mt_rand(),
'key' => $key,
);
$query_string = str_replace('&', '&', urldecode(http_build_query($query)));
// Setup request URL and headers.
$ip = variable_get('boost_server_addr', FALSE);
if (empty($ip)) {
$ip = $_SERVER['SERVER_ADDR'];
}
$url = 'http://' . $ip . $base_path . 'boost_views.php?' . $query_string;
$headers['Host'] = $_SERVER['HTTP_HOST'];
// Send nodes to async processor
$socket_timeout = ini_set('default_socket_timeout', 2);
$results = drupal_http_request($url, $headers);
ini_set('default_socket_timeout', $socket_timeout);
// Check response.
$key_back = trim($results->data);
// $key_back = (int)$key_back;
// If async failed; block here and generate images and caches.
if ($key_back != $key) {
watchdog('boost', 'Asynchronous views failed. Using Synchronous mode.');
_boost_views_runit();
}
// We have processed nodes
$processed = TRUE;
return;
}
function _boost_views_runit($debug = FALSE, $nid = FALSE) {
global $_boost, $base_url, $base_path;
// Ensure we're in the correct working directory, since some web servers (e.g. Apache) mess this up here.
chdir(dirname($_SERVER['SCRIPT_FILENAME']));
// Get all views that are not expired in database
if (BOOST_FLUSH_ALL_MULTISITE) {
$result = db_query("SELECT page_id, page_type FROM {boost_cache} WHERE page_callback = 'view' AND expire > 0 AND expire <> 434966400 GROUP BY page_id, page_type");
}
else {
$result = db_query("SELECT page_id, page_type FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = 'view' AND expire > 0 AND expire <> 434966400 GROUP BY page_id, page_type", BOOST_FILE_PATH);
}
// Setup data arrays and presets
$data = array();
$number_views = 0;
$number_hits = 0;
$views = array();
while ($boost = db_fetch_array($result)) {
$key = $boost['page_type'] . ' - ' . $boost['page_id'];
$views[$key]['page_type'] = $boost['page_type'];
$views[$key]['page_id'] = $boost['page_id'];
}
// Get list of views that might contain the new content.
$views += boost_views_get_valid_array();
$base_urls = array();
if (module_exists('domain')) {
// Get all domains into the base_urls array
foreach (domain_domains() as $key => $value) {
$domains[$key] = $key;
}
$fake_node = new stdClass();
$fake_node->domains = $domains;
$base_urls = boost_get_base_urls($fake_node);
unset($fake_node);
}
else {
$base_urls[0][] = $base_url . '/';
}
// get list of nodes
$nodes = array();
if (!empty($nid)) {
$nodes[] = $nid;
}
elseif (!empty($_boost['new_nodes'])) {
$nodes = $_boost['new_nodes'];
}
if (empty($nodes)) {
return;
}
// Loop through each node
foreach ($nodes as $nid) {
$node = boost_node_get_basics($nid);
if (!$node || !is_numeric($node->nid)) {
continue;
}
// Get terms for future usage (/taxonomy/term/% view)
$tids = boost_taxonomy_node_get_tids($node->nid);
if (isset($_boost['nid-' . $node->nid]['tids'])) {
$tids += $_boost['nid-' . $node->nid]['tids'];
}
$options = array(
'operator' => '=',
'value' => array(
'value' => $node->nid,
),
'group' => '0',
'exposed' => FALSE,
'expose' => array(
'operator' => FALSE,
'label' => '',
),
'relationship' => 'none',
);
// Loop through each view in the boost cache
foreach ($views as $boost) {
unset($view);
$view = views_get_view($boost['page_type'], TRUE);
if (!is_object($view)) {
continue;
}
// Only work with node views
if ($view->base_table != 'node') {
continue;
}
// Make sure the view is a valid display
if (empty($view->display[$boost['page_id']])) {
continue;
}
// Set view display
$view->set_display($boost['page_id']);
$domains = array();
$domain_filter = FALSE;
if (module_exists('domain')) {
// Check for domain access filters
$filters = $view->get_items('filter', $boost['page_id']);
if (isset($filters['current_all']) || isset($filters['gid']) || isset($filters['domain_id'])) {
// Only need to check the view for domains this node is published to
$domains = $node->domains;
foreach ($domains as $key => $value) {
if ($key != $value) {
unset($domains[$key]);
}
}
$domain_filter = TRUE;
}
}
if (!$domain_filter) {
// boost_get_base_urls uses 0 if domain access is not installed
$domains = array(0 => 0);
}
$first = TRUE;
$view_checked = FALSE;
foreach ($domains as $key => $domain_id) {
// View has to be reloaded inorder for hook_views_query_substitutions
// to work correctly because this is in a loop
if (!$first) {
unset($view);
$view = views_get_view($boost['page_type'], TRUE);
if (!is_object($view)) {
continue;
}
// Make sure the view is a valid display
if (empty($view->display[$boost['page_id']])) {
continue;
}
}
$first = FALSE;
$view->set_display($boost['page_id']);
// Make sure view is valid
if (!$view_checked) {
$h = $view->display[$boost['page_id']]->handler->default_display->options;
$broken = FALSE;
if (!empty($h)) {
foreach ($h as $type => $rows) {
if (empty($rows)) {
continue;
}
if ($type == 'sorts' || $type == 'fields' || $type == 'arguments' || $type == 'filters' || $type == 'relationships') {
$type = rtrim($type, 's');
foreach ($rows as $id => $row) {
$table = $row['table'];
$field = $row['field'];
$views_data = views_fetch_data($table);
if (empty($views_data)) {
$broken = TRUE;
break 2;
}
if (!isset($views_data[$field][$type])) {
$broken = TRUE;
break 2;
}
}
}
}
}
unset($h);
if ($broken) {
break;
}
$view_checked = TRUE;
}
// Filter to just this nid
$view->add_item($boost['page_id'], 'filter', 'node', 'nid', $options);
// Set ***CURRENT_DOMAIN*** variable
// See domain_views_query_substitutions()
if ($domain_filter) {
domain_set_domain($domain_id, TRUE);
}
$view->pre_execute();
$view->execute();
// Increment Counter
$number_views++;
foreach ($view->result as $item) {
if ($item->nid == $nid) {
foreach ($base_urls as $key => $value) {
$save = TRUE;
if ($domain_filter && $key != $domain_id) {
$save = FALSE;
}
if ($save) {
foreach ($value as $url) {
$parts = parse_url($url);
$file_path = boost_cache_directory($parts['host'], FALSE);
$hash = $file_path . 'view' . $boost['page_type'] . $boost['page_id'];
$data[$hash] = array(
'base_dir' => $file_path,
'page_callback' => 'view',
'page_type' => $boost['page_type'],
'page_id' => $boost['page_id'],
);
$number_hits++;
}
}
}
}
}
// Free memory
if (isset($view) && is_object($view) && method_exists($view, 'destroy')) {
$view->destroy();
// Fix views bug http://drupal.org/node/988680
unset($view->old_view);
}
unset($view);
}
// Free memory
if (isset($view) && is_object($view) && method_exists($view, 'destroy')) {
$view->destroy();
// Fix views bug http://drupal.org/node/988680
unset($view->old_view);
}
unset($view);
if ($domain_filter) {
domain_reset_domain(TRUE);
}
}
}
if (!$debug) {
if ($data) {
$flushed = boost_cache_expire_router($data);
}
if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_view_insert'])) {
watchdog('boost', 'Debug: _boost_view_insert() %count Views Searched (%viewnames) %times times; %hits of them contain the new nodes (%nids) and where thus flushed. As a result of this %flushed pages where expired from the boost cache. !domains', array(
'%nids' => implode(', ', $nodes),
'%count' => count($views),
'%times' => $number_views,
'%hits' => $number_hits,
'%flushed' => $flushed,
'!domains' => str_replace(' ', ' ', nl2br(htmlentities(print_r($base_urls, TRUE)))),
'%viewnames' => implode(', ', array_keys($views)),
));
}
$processed = TRUE;
}
else {
return array('in-view' => $data, 'in-cache' => boost_cache_expire_router($data, FALSE, FALSE, TRUE));
}
}
/**
* Implementation of hook_votingapi_insert().
*
* @param $votes
* array of votes
*/
function boost_votingapi_insert($votes) {
if (!variable_get('boost_enabled', CACHE_NORMAL)) return;
foreach ($votes as $vote) {
$node = node_load($vote['content_id'], NULL, TRUE);
boost_expire_node($node, $vote['content_id']);
}
}
/**
* Implementation of hook_votingapi_delete().
*
* @param $votes
* array of votes
*/
function boost_votingapi_delete($votes) {
if (!variable_get('boost_enabled', CACHE_NORMAL)) return;
foreach ($votes as $vote) {
$node = node_load($vote['content_id'], NULL, TRUE);
boost_expire_node($node, $vote['content_id']);
}
}
/**
* Expires a node from the cache; including related pages.
*
* Expires front page if promoted, taxonomy terms,
*
* @param $node
* node object
* @param $nid
* node id
* @param $debug
* TRUE to return values without expiring anything.
* @return
* TRUE if no debug, array($data, $paths) if debug turned on.
*/
function boost_expire_node($node, $nid = 0, $debug = FALSE) {
global $_boost;
$data = array();
$paths = array();
// Check node object
if (empty($node->nid)) {
if (is_numeric($nid)) {
$node->nid = $nid;
}
else {
return FALSE;
}
}
// Expire this node
if (BOOST_NO_DATABASE) {
$paths['node'] = 'node/' . $node->nid;
}
else {
$data['node'] = array('page_callback' => 'node', 'page_id' => $node->nid);
}
// If promoted to front page, expire front page
if (BOOST_FLUSH_FRONT && $node->promote == 1) {
$paths['front'] = '';
}
// Get taxonomy terms and flush
if (module_exists('taxonomy') && BOOST_FLUSH_NODE_TERMS) {
// Get old terms from DB
$tids = boost_taxonomy_node_get_tids($node->nid);
// Get new terms from node object
if (!empty($node->taxonomy)) {
foreach ($node->taxonomy as $vocab) {
if (is_array($vocab)) {
foreach ($vocab as $term) {
$tids[$term] = $term;
}
}
}
}
// See if the taxonony/term/% path is a view containing domain access restrictions
if (module_exists('domain') && module_exists('views')) {
$list = boost_views_get_valid_list();
foreach ($list as $hash => $enabled) {
$info = explode(' - ', $hash);
if ($info[2] == '/taxonomy/term/%') {
// Load View
$view = views_get_view($info[0], TRUE);
if (!is_object($view)) {
continue;
}
// Check for domain access filter "Available on current domain"
$filters = $view->get_items('filter', $info[1]);
// Free memory
$view->destroy();
unset($view);
if (isset($filters['current_all']) || isset($filters['gid']) || isset($filters['domain_id'])) {
$fake_node = $node;
}
else {
foreach (domain_domains() as $key => $value) {
$domains[$key] = $key;
}
$fake_node = new stdClass();
$fake_node->domains = $domains;
}
break;
}
}
}
if (!isset($fake_node)) {
$fake_node = $node;
}
// Get list of base urls for this node
$base_dirs = array();
foreach (boost_get_base_urls($fake_node) as $domain_id) {
foreach ($domain_id as $base) {
$parts = parse_url($base);
$base_dirs[] = boost_cache_directory($parts['host'], FALSE);
}
}
unset($fake_node);
// Set each tid in the data array
foreach ($tids as $tid) {
if (is_numeric($tid)) {
if (BOOST_NO_DATABASE) {
$term = taxonomy_get_term($tid);
$paths['term' . $tid] = taxonomy_term_path($term);
}
else {
foreach ($base_dirs as $base_dir) {
$data['term:' . $tid . ' base:' . $base_dir] = array(
'page_callback' => 'taxonomy',
'page_id' => $tid,
'base_dir' => $base_dir,
);
}
}
}
}
// Save all term IDs into the global scope
$_boost['nid-' . $node->nid]['tids'] = $tids;
}
// Get menu and flush related items in the menu.
if (BOOST_FLUSH_MENU_ITEMS !=0) {
if (!isset($node->menu['menu_name'])) {
menu_nodeapi($node, 'prepare');
}
$menu = menu_tree_all_data($node->menu['menu_name']);
$tempa = NULL;
$tempb = NULL;
if (BOOST_FLUSH_MENU_ITEMS == 1) {
$links = boost_get_menu_structure($menu, FALSE, 'node/' . $node->nid, NULL, $tempa, $tempb);
}
elseif (BOOST_FLUSH_MENU_ITEMS == 2) {
$links = boost_get_menu_structure($menu, NULL, NULL, NULL, $tempa, $tempb);
}
unset($tempa);
unset($tempb);
$paths = array_merge($links, $paths);
}
// Get CCK References and flush.
if (BOOST_FLUSH_CCK_REFERENCES && module_exists('nodereference')) {
$nids = array();
$type = content_types($node->type);
if ($type) {
foreach ($type['fields'] as $field) {
// Add referenced nodes to nids. This will clean up nodereferrer fields
// when the referencing node is updated.
if ($field['type'] == 'nodereference') {
$node_field = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();
foreach ($node_field as $delta => $item) {
$nids[$item['nid']] = $item['nid'];
}
}
}
foreach ($nids as $nid) {
if (is_numeric($nid)) {
if (BOOST_NO_DATABASE) {
$paths['reference' . $nid] = 'node/' . $nid;
}
else {
$data['reference' . $nid] = array('page_callback' => 'node', 'page_id' => $nid);
}
}
}
}
// Get CCK references pointing to this node and flush.
if (module_exists('nodereferrer')) {
$nids = nodereferrer_referrers($node->nid);
foreach ($nids as $nid) {
if (is_numeric($nid['nid'])) {
if (BOOST_NO_DATABASE) {
$paths['referrer' . $nid['nid']] = 'node/' . $nid['nid'];
}
else {
$data['referrer' . $nid['nid']] = array('page_callback' => 'node', 'page_id' => $nid['nid']);
}
}
}
}
}
// Get views containing this node and flush.
if (BOOST_FLUSH_VIEWS && module_exists('views')) {
$router_item = _boost_get_menu_router();
$relationship = array();
$relationship[] = array('page_callback' => 'node', 'page_type' => $node->type, 'page_id' => $node->nid);
$relationships = boost_cache_get_node_relationships($relationship);
$data = array_merge($data, $relationships);
}
// Flush the cache
$flushed = 0;
if (!$debug) {
if (!empty($data)) {
$flushed += boost_cache_expire_router($data);
}
if (!empty($paths)) {
$flushed += boost_cache_expire_derivative($paths, TRUE);
}
if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_expire_node'])) {
watchdog('boost', 'Debug: boost_expire_node() Node !nid was flushed resulting in !flushed pages being expired from the cache data: !data paths: !paths', array(
'!nid' => $node->nid,
'!flushed' => $flushed,
'!data' => boost_print_r($data, TRUE, TRUE),
'!paths' => boost_print_r($paths, TRUE, TRUE),
));
}
return $flushed;
}
else {
return array('router-in' => $data, 'router-results' => boost_cache_expire_router($data, FALSE, FALSE, TRUE), 'paths-in' => $paths, 'path-results' => boost_cache_expire_derivative($paths, FALSE, FALSE, TRUE));
}
}
/**
* Finds parent, siblings and children of the menu item. UGLY CODE...
*
* @param array $menu
* Output from menu_tree_all_data()
* @param bool $found
* Signal for when the needle was found in the menu array.
* Set TRUE to get entire menu
* @param string $needle
* Name of menu link. Example 'node/21'
* @param bool $first
* Keep track of the first call; this is a recursive function.
* @param bool &$found_global
* Used to signal the parent item was found in one of it's children
* @param bool &$menu_out
* Output array of parent, siblings and children menu links
*
* TODO: Use page_callback and page_arguments instead of link_path.
* Can use boost_cache_expire_router() then.
*/
function boost_get_menu_structure($menu, $found, $needle, $first, &$found_global, &$menu_out) {
// Set Defaults
$found = !is_null($found) ? $found : TRUE;
$needle = !is_null($needle) ? $needle : '';
$first = !is_null($first) ? $first : TRUE;
$found_global = FALSE;
$menu_out = !is_null($menu_out) ? $menu_out : array();
// Get Siblings
foreach ($menu as $item) {
if ($item['link']['hidden'] == 0 && $item['link']['page_callback'] != '' && ($item['link']['link_path'] == $needle || $found)) {
$menu_out[] = $item['link']['link_path'];
$found = TRUE;
}
}
// Get Children
foreach ($menu as $item) {
if ($item['link']['hidden'] != 0) {
continue;
}
if ($item['link']['page_callback'] != '' && ($item['link']['link_path'] == $needle || $found)) {
$menu_out[] = $item['link']['link_path'];
$found = TRUE;
}
// Get Grandkids
if (!empty($item['below'])) {
$sub_menu = array();
foreach ($item['below'] as $below) {
if ($below['link']['hidden'] == 0) {
$sub_menu[] = $below;
}
}
boost_get_menu_structure($sub_menu, $needle, $found, FALSE, $found_global, $menu_out);
$structure[$item['link']['link_path']][] = $sub;
if ($item['link']['page_callback'] != '' && $found_global) {
// Get Parent of kid
$menu_out[] = $item['link']['link_path'];
}
}
else {
$structure[$item['link']['link_path']] = '';
}
}
// Clean up
if (isset($structure) && is_array($structure)) {
$structure = array_unique($structure);
}
$found_global = $found;
if ($first) {
if (isset($menu_out) && is_array($menu_out)) {
$menu_out = array_unique($menu_out);
sort($menu_out);
return $menu_out;
}
else {
return array();
}
}
else {
return $structure;
}
}
/**
* Return taxonomy terms given a nid.
*
* Needed because of a weird bug with CCK & node_load()
* http://drupal.org/node/545922
*/
function boost_taxonomy_node_get_tids($nid) {
$vid = db_result(db_query('SELECT vid FROM {node} WHERE nid = %d', $nid));
$result = db_query(db_rewrite_sql('SELECT t.tid FROM {term_node} r INNER JOIN {term_data} t ON r.tid = t.tid INNER JOIN {vocabulary} v ON t.vid = v.vid WHERE r.vid = %d ORDER BY v.weight, t.weight, t.name', 't', 'tid'), $vid);
$tids = array();
while ($term = db_result($result)) {
$tids[$term] = $term;
}
return $tids;
}
/**
* Implementation of hook_taxonomy(). Acts on taxonomy changes.
*/
function boost_taxonomy($op, $type, $term = NULL) {
if (!variable_get('boost_enabled', CACHE_NORMAL)) return;
switch ($op) {
case 'insert':
case 'update':
case 'delete':
// TODO: Expire all relevant taxonomy pages from the static page cache to prevent serving stale content.
break;
}
}
/**
* Implementation of hook_user(). Acts on user account actions.
*/
function boost_user($op, &$edit, &$account, $category = NULL) {
global $user, $_boost;
if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_user_op'])) {
watchdog('boost', 'Debug: boost_user() The %op operation was sent for user %user', array('%op' => $op, '%user' => $user->uid));
}
switch ($op) {
case 'login':
// Set a special cookie to prevent authenticated users getting served
// pages from the static page cache.
boost_set_cookie($user->uid);
break;
case 'logout':
// set the cookie to 0, then remove at the following request, this way browsers won't show a cached logged in page
boost_set_cookie(-1);
break;
case 'delete':
if (!variable_get('boost_enabled', CACHE_NORMAL)) {
return;
}
// Expire the relevant user page from the static page cache to prevent serving stale content:
if (!empty($account->uid)) {
if (BOOST_NO_DATABASE) {
$paths[] = 'user/' . $account->uid;
$flushed = boost_cache_expire_derivative($paths, TRUE, TRUE);
}
else {
$data[] = array('page_callback' => 'user', 'page_id' => $account->uid);
boost_set_base_dir_in_array($data);
$flushed = boost_cache_expire_router($data, TRUE, TRUE);
}
if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_user_delete'])) {
watchdog('boost', 'Debug: boost_user() User !uid was deleted resulting in !flushed pages being expired from the cache', array('!uid' => $account->uid, '!flushed' => $flushed));
}
}
break;
}
}
/**
* Implementation of hook_block().
*/
function boost_block($op = 'list', $delta = 0, $edit = array()) {
global $user;
switch ($op) {
case 'list':
return array(
'status' => array(
'info' => t('Boost: Pages cache status'),
'weight' => 10,
'cache' => BLOCK_NO_CACHE,
),
'config' => array(
'info' => t('Boost: Pages cache configuration'),
'weight' => 10,
'cache' => BLOCK_NO_CACHE,
),
'stats' => array(
'info' => t('Boost: AJAX core statistics'),
'weight' => 10,
'cache' => BLOCK_NO_CACHE,
),
'relationship' => array(
'info' => t('Boost: Expiration Relationship'),
'weight' => 10,
'cache' => BLOCK_NO_CACHE,
),
);
break;
case 'configure':
if ($delta == 'stats') {
$form['boost_block_show_stats'] = array(
'#type' => 'checkbox',
'#title' => t('Display Statistics.'),
'#default_value' => variable_get('boost_block_show_stats', FALSE),
'#description' => t('If false, uses Javascript to hide the block via "parent().parent().hide()".'),
);
$form['boost_block_cache_stats_block'] = array(
'#type' => 'checkbox',
'#title' => t('Cache Statistics Block'),
'#default_value' => variable_get('boost_block_cache_stats_block', FALSE),
);
return $form;
}
break;
case 'save':
if ($delta == 'stats') {
variable_set('boost_block_show_stats', $edit['boost_block_show_stats']);
variable_set('boost_block_cache_stats_block', $edit['boost_block_cache_stats_block']);
}
break;
case 'view':
$block = array();
switch ($delta) {
case 'relationship':
// Don't show the block to anonymous users, nor on any pages that
// aren't even cacheable to begin with (e.g. admin/*).
if (!empty($user->uid) && boost_is_cacheable($GLOBALS['_boost_path'])) {
$output = '';
$router_item = _boost_get_menu_router();
if ($router_item['page_callback'] == 'node') {
$node = node_load(arg(1));
$data = boost_expire_node($node, $node->nid, TRUE);
if (BOOST_FLUSH_VIEWS_INSERT && module_exists('views')) {
$data['view'] = _boost_views_runit(TRUE, $node->nid);
}
$output .= str_replace(' ', ' ', nl2br(htmlentities(print_r($data, TRUE))));
}
$block['subject'] = '';
$block['content'] = $output;
}
break;
case 'status':
// Don't show the block to anonymous users, nor on any pages that
// aren't even cacheable to begin with (e.g. admin/*).
if (!empty($user->uid) && boost_is_cacheable($GLOBALS['_boost_path'])) {
$output = '';
// Has anything on the site changed?
if (BOOST_CHECK_BEFORE_CRON_EXPIRE) {
$output .= t('Site Has Changed: %old ', array('%old' => boost_has_site_changed() ? 'True' : 'False'));
}
// Display Cached File Info
$filename = boost_file_path($GLOBALS['_boost_path']);
$ttl = boost_db_get_ttl($filename);
$generate = boost_get_generation_time($filename);
if ($generate !== FALSE) {
if (boost_is_cached($GLOBALS['_boost_path'])) {
$output .= $filename . ' ';
}
else {
$output .= $filename . ' Does not exist. ';
}
if ($ttl < 0) {
$output .= t('Expired: %interval ago ', array('%interval' => format_interval(abs($ttl))));
}
else {
$output .= t('Expire In: %interval ', array('%interval' => format_interval(abs($ttl))));
}
$tim = boost_db_get_cache_age($filename);
$output .= t('Cache Age: %time ', array('%time' => format_interval($tim)));
$output .= t('Cache Generated: %time seconds ', array('%time' => round($generate, 2))) . ' ';
if (user_access('administer site configuration')) {
$output .= drupal_get_form('boost_block_flush_form');
}
$output .= ' ';
}
// If page is not cached, let user know.
else {
$output .= t('This page is being served live to anonymous visitors, as it is not currently in the static page cache.');
}
// Display any info about errors on this page
$error = _boost_page_have_error();
$msg_count = empty($GLOBALS['_boost_message_count']) ? 0 : $GLOBALS['_boost_message_count'];
$drupal_msg = max(count(drupal_get_messages(NULL, FALSE)), $msg_count);
if ($error || (BOOST_HALT_ON_MESSAGES && $drupal_msg != 0)) {
$output .= t('There are php errors or Drupal messages on this page, preventing boost from caching.') . ' ';
if ($error) {
$output .= t('ERROR:
';
}
//////////////////////////////////////////////////////////////////////////////
// Output buffering callback
/**
* PHP output buffering callback for static page caching.
*/
function _boost_ob_handler() {
global $_boost;
$buffer = ob_get_contents();
// If Compressed data was given to us decompress it
if (boost_headers_contain('gzip')) {
$decompressed_buffer = gzinflate(substr(substr($buffer, 10), 0, -8));
}
else {
$decompressed_buffer = $buffer;
}
// Ensure we're in the correct working directory, since some web servers (e.g. Apache) mess this up here.
chdir(dirname($_SERVER['SCRIPT_FILENAME']));
// Very late cache canceling
$router_item = _boost_get_menu_router();
if ( $router_item['page_callback'] == 'search404_page'
|| $router_item['page_callback'] == 'fivestar_vote'
) {
$GLOBALS['_boost_cache_this'] = FALSE;
}
// Check for PHP errors
if ($error = _boost_page_have_error()) {
$GLOBALS['_boost_cache_this'] = FALSE;
if (BOOST_VERBOSE >= 3) {
watchdog('boost', 'There are php errors on this page, preventing boost from caching. ERROR:
%error
!link !performance', array('%error' => boost_print_r($error, TRUE), '!link' => l(t('Lookup Error Type'), 'http://php.net/errorfunc.constants'), '!performance' => l(t('Turn Off Error Checking'), 'admin/settings/performance/boost')), WATCHDOG_WARNING);
}
}
// Check for drupal messages
if (BOOST_HALT_ON_MESSAGES && isset($GLOBALS['_boost_message_count']) && $GLOBALS['_boost_message_count'] != 0) {
$GLOBALS['_boost_cache_this'] = FALSE;
if (BOOST_VERBOSE >= 3) {
watchdog('boost', 'There are Drupal messages on this page, preventing boost from caching. MESSAGES: %msg !performance', array('%msg' => $GLOBALS['_boost_message_count'], '!performance' => l(t('Turn Off Error Checking'), 'admin/settings/performance/boost')), WATCHDOG_WARNING);
}
}
// Check the currently set content type and the HTTP response code. only cache
// 'text/*' pages that were output with a 200 status number.
if (!empty($decompressed_buffer)) {
$status = boost_get_http_status();
$types = boost_get_content_type();
if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_ob_handler_info'])) {
watchdog('boost', 'Debug: _boost_ob_handler() HTTP Info: !status - !types Path: !path Content Container: !callback Content Type: !type ID: !id Cache This: !cache.', array('!status' => $status, '!types' => implode(', ', $types), '!path' => boost_file_path($GLOBALS['_boost_path']), '!callback' => $router_item['page_callback'], '!type' => $router_item['page_type'], '!id' => $router_item['page_id'], '!cache' => $GLOBALS['_boost_cache_this'] ? 'TRUE' : 'FALSE'));
}
// Bail out if we can not cache
if ($status != 200 || $GLOBALS['_boost_cache_this'] == FALSE) {
return;
}
// Check for corret types and cache accordingly
$types = array_pop($types);
if (stristr($types, 'text/javascript') && BOOST_CACHE_JSON) {
if (BOOST_ASYNCHRONOUS_OUTPUT && !headers_sent()) {
boost_async_opp($buffer, FALSE, 'text/javascript; charset=utf-8');
}
boost_cache_set($GLOBALS['_boost_path'], $decompressed_buffer, BOOST_JSON_EXTENSION);
}
elseif ((stristr($types, 'application/rss') || stristr($types, 'text/xml') || stristr($types, 'application/rss+xml')) && BOOST_CACHE_XML) {
if (BOOST_ASYNCHRONOUS_OUTPUT && !headers_sent()) {
boost_async_opp($buffer, FALSE, 'text/xml; charset=utf-8');
}
boost_cache_set($GLOBALS['_boost_path'], $decompressed_buffer, BOOST_XML_EXTENSION);
}
elseif (stristr($types, 'text/html') && BOOST_CACHE_HTML) {
if (BOOST_ASYNCHRONOUS_OUTPUT && !headers_sent()) {
boost_async_opp($buffer, FALSE, 'text/html; charset=utf-8');
}
boost_cache_set($GLOBALS['_boost_path'], $decompressed_buffer, BOOST_FILE_EXTENSION);
// html output requires special handling of the aggregated js/css files.
boost_cache_css_js_files($decompressed_buffer);
}
}
}
/**
* Determines the MIME content type of the current page response based on
* the currently set Content-Type HTTP header.
*
* This should normally return the string 'text/html' unless another module
* has overridden the content type.
*/
function boost_get_content_type() {
$headers = explode("content-type: ", strtolower(drupal_get_headers()));
$types = array();
foreach ($headers as $header) {
$types[] = array_shift(explode('; charset=', array_shift(explode('\n', $header))));
}
return array_filter($types);
}
/**
* Determines the HTTP response code that the current page request will be
* returning by examining the HTTP headers that have been output so far.
*/
function boost_get_http_status() {
if (function_exists('drupal_get_headers')) {
foreach (explode("\n", drupal_get_headers()) as $header) {
preg_match('!^HTTP\/.*?\s+(\d+)!', $header, $matches);
if (isset($matches[1])) {
return (int) $matches[1];
}
}
}
return 200;
}
function boost_headers_contain($text) {
if (function_exists(headers_list)) {
$list = headers_list();
if (empty($list)) {
return FALSE;
}
foreach ($list as $header) {
$info = stristr($header, $text);
if ($info !== FALSE) {
return $info;
}
}
}
return FALSE;
}
//////////////////////////////////////////////////////////////////////////////
// Boost API implementation
/**
* Determines whether a given url can be cached or not by boost.
*
* To avoid potentially troublesome situations, the user login page is never
* cached, nor are any admin pages.
*
* @param $path
* Current URL
*
* $path = $GLOBALS['_boost_path'] most of the time
* uses $GLOBALS['_boost_query'] as well
*/
function boost_is_cacheable($path) {
global $base_root;
$is_front = FALSE;
if (empty($path)) {
$is_front = TRUE;
$path = variable_get('site_frontpage', 'node');
}
$normal_path = drupal_get_normal_path($path); // normalize path
$full = $normal_path . '-' . $GLOBALS['_boost_query'];
$decoded1 = urldecode($full);
$decoded2 = urldecode($decoded1);
while ($decoded1 != $decoded2) {
$decoded1 = urldecode($decoded2);
$decoded2 = urldecode($decoded1);
}
$decoded = $decoded2;
unset($decoded2);
unset($decoded1);
$url = $base_root . request_uri();
// Never cache
// the user login/registration/password/reset pages
// any admin pages
// comment reply pages
// boost_stats.php
// any shopping cart pages
// node add page
// openid login page
// filefield upload progress page
// URL variables that contain / or \
// if incoming URL contains '..' or null bytes or ://
// if url contains #
// Limit the maximum directory nesting depth of the path
// Do not cache if destination is set
if ( $normal_path == 'user'
|| preg_match('!^user/(login|register|password|reset)!', $normal_path)
|| preg_match('!^admin!', $normal_path)
|| preg_match('!^comment/reply!', $normal_path)
|| preg_match('!boost_stats.php$!', $normal_path)
|| preg_match('!^cart!', $normal_path)
|| preg_match('!^node/add!', $normal_path)
|| preg_match('!^openid!', $normal_path)
|| preg_match('!^filefield/progress/!', $normal_path)
|| strpos($GLOBALS['_boost_query'], '/')
|| strpos($GLOBALS['_boost_query'], "\\")
|| strpos($full, '..') !== FALSE
|| strpos($full, "\0") !== FALSE
|| strpos($decoded, '://') !== FALSE
|| strpos($decoded, '#') !== FALSE
|| strpos($decoded, '..') !== FALSE
|| strpos($decoded, "\0") !== FALSE
|| count(explode('/', $path)) > BOOST_MAX_PATH_DEPTH
|| !empty($_GET['destination'])
) {
return FALSE;
}
if (!BOOST_CACHE_XML && (preg_match('!/feed$!', $normal_path) || preg_match('!\.xml$!', $normal_path))) {
return FALSE;
}
if (!BOOST_CACHE_QUERY && ($GLOBALS['_boost_query'] != BOOST_CHAR || strstr($url, '?') !== FALSE)) {
return FALSE;
}
// Don't cache path if it can't be served by apache.
if (BOOST_ONLY_ASCII_PATH) {
if (preg_match('@[^/a-z0-9_\-&=,\.:]@i', $path)) {
return FALSE;
}
}
// Check if this domain has been whitelisted for caching.
$use_lists = variable_get('boost_domain_use_lists', BOOST_DOMAIN_NO_LISTS);
if ( $use_lists == BOOST_DOMAIN_WHITELIST_ONLY
|| $use_lists == BOOST_DOMAIN_BOTH_LISTS) {
$is_whitelisted = FALSE;
$whitelist = variable_get('boost_domain_whitelist', array());
$current_domain = $_SERVER['HTTP_HOST'];
/* It'd be possible (and involve less code overall) to use domain_lookup()
* here instead of stuffing everything into the "boost_domain_whitelist"
* variable, but this method will avoid the extra db query and hooks that
* calling domain_lookup can cause during page load.
*/
$is_whitelisted = isset($whitelist[$current_domain]);
if (!$is_whitelisted) {
// Loop through the list of wildcards, trying to match the current domain.
$whitelist_wild = variable_get('boost_domain_whitelist_wild', array());
$current_domain_array = explode('.', $current_domain); // www.bar.com -> array('www','bar','com')
foreach ($whitelist_wild as $wildcard) {
$wildcard_array = explode('.', $wildcard); // *.quux.qz -> array('*','quux','qz')
// If the arrays aren't the same size, don't bother matching their contents.
$wc_count = count($wildcard_array);
if (count($current_domain_array) != $wc_count) {
continue;
}
for ($i = 0; $i < $wc_count; $i++) {
/* If the current element isn't * and doesn't match the pattern, skip
* to the next wildcard. */
if ( $wildcard_array[$i] != '*'
&& $wildcard_array[$i] != $current_domain_array[$i]) {
break;
}
/* If the loop hasn't terminated by now, all elements of the current
* wildcard array were checked. Mark this domain as whitelisted and
* don't check any other whitelist elements.
*/
if ($i == $wc_count-1) {
$is_whitelisted = TRUE;
break 2;
}
}
}
}
if ( !$is_whitelisted
&& variable_get('boost_domain_whitelist_use_domain', FALSE)
&& function_exists('domain_alias_lookup')) {
/* Either I call domain_alias_lookup (and deal with the cost), I ignore
* its wildcard capabilities and stuff all aliases into
* boost_domain_whitelist, or I implement something subtly incompabile. */
$is_whitelisted = -1 != domain_alias_lookup($current_domain);
}
if (!$is_whitelisted) {
return FALSE;
}
}
// Check if this domain has been blacklisted for caching.
if ( $use_lists == BOOST_DOMAIN_BLACKLIST_ONLY
|| $use_lists == BOOST_DOMAIN_BOTH_LISTS) {
$blacklist = variable_get('boost_domain_blacklist', array());
if (isset($blacklist[$current_domain])) {
return FALSE;
}
}
// Check for reserved characters if on windows
// http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
// " * : < > |
$chars = '"*:<>|';
if (stristr(PHP_OS, 'WIN') && preg_match("/[" . $chars . "]/", $full)) {
return FALSE;
}
// Don't cache if path is in the source; but allow the front page to be cached
if (variable_get('boost_cache_url_alias_src', FALSE) && !$is_front && (int)db_result(db_query("SELECT count(*) FROM {url_alias} WHERE src = '%s'", $path)) > 0) {
return FALSE;
}
// Invoke hook_boost_is_cacheable($path)
foreach (module_implements('boost_is_cacheable') as $module) {
if (($result = module_invoke($module, 'boost_is_cacheable', $path)) !== NULL) {
if (!$result) {
return FALSE;
}
}
}
// See http://api.drupal.org/api/function/block_list/6
// Match the user's cacheability settings against the path
if (BOOST_CACHEABILITY_PAGES) {
if (BOOST_CACHEABILITY_OPTION < 2) {
$page_match = drupal_match_path($path, BOOST_CACHEABILITY_PAGES);
if ($path != $_GET['q']) {
$page_match = $page_match || drupal_match_path($_GET['q'], BOOST_CACHEABILITY_PAGES);
}
// When BOOST_CACHEABILITY_OPTION has a value of 0, boost will cache
// all pages except those listed in BOOST_CACHEABILITY_PAGES. When set
// to 1, boost will cache only on those pages listed in BOOST_CACHEABILITY_PAGES.
$page_match = !(BOOST_CACHEABILITY_OPTION xor $page_match);
}
else {
$page_match = drupal_eval(BOOST_CACHEABILITY_PAGES);
}
}
else {
$page_match = TRUE;
}
return $page_match;
}
/**
* This hook is run inorder to determine if a page should be cached.
* Runs in boost_init().
*
* @return FALSE to not cache the page, TRUE to cache the page.
* Returning a FALSE is absolute, Returning TRUE is more like an ignore,
* doesn't guarantee it will be cached; some other setting could make the
* is_boost_cacheable() call return FALSE to boost_init()
*
*/
function hook_boost_is_cacheable($path) {
return TRUE;
}
/**
* Determines whether a given Drupal page is currently cached or not.
*
* @param $path
* Current URL
*/
function boost_is_cached($path) {
// no more need to check if path is empty cause it is done on the input of this function before calling it
// no more need to use drupal_get_normal_path - we do not need the internal path (node/56) - we are fine with aliases
return file_exists(boost_file_path($path));
}
/**
* Deletes all files currently in the cache.
*/
function boost_cache_clear_all() {
global $_boost;
if (variable_get('boost_ignore_flush', 0) == 0) {
boost_cache_clear_all_db();
boost_cache_delete(TRUE);
if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_cache_clear_all'])) {
watchdog('boost', 'Flushed ALL files from static page cache.', array(), WATCHDOG_NOTICE);
}
return TRUE;
}
return FALSE;
}
/**
* Deletes all expired static files currently in the cache via filesystem.
*/
function boost_cache_expire_all_filesystem() {
boost_cache_delete(FALSE);
return TRUE;
}
/**
* Resets all entries in database.
*/
function boost_cache_clear_all_db() {
if (BOOST_FLUSH_ALL_MULTISITE) {
db_query("UPDATE {boost_cache} SET expire = %d", 0);
}
else {
db_query("UPDATE {boost_cache} SET expire = %d WHERE base_dir = '%s'", 0, BOOST_FILE_PATH);
}
}
/**
* Deletes files in the cache.
*
* @param $flush
* If true clear the entire cache directory.
*/
function boost_cache_delete($flush = FALSE) {
clearstatcache();
//recreate dirs
_boost_mkdir_p(BOOST_FILE_PATH);
_boost_mkdir_p(BOOST_GZIP_FILE_PATH);
//add in .boost root id file
_boost_write_file_chmod(BOOST_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
_boost_write_file_chmod(BOOST_GZIP_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
foreach (_boost_copy_file_get_domains(BOOST_PERM_FILE_PATH) as $dir) {
_boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
}
foreach (_boost_copy_file_get_domains(BOOST_PERM_GZIP_FILE_PATH) as $dir) {
_boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
}
//Flush Cache
if (file_exists(BOOST_FILE_PATH)) {
_boost_rmdir_rf(BOOST_FILE_PATH, $flush, TRUE);
}
if (file_exists(BOOST_GZIP_FILE_PATH)) {
_boost_rmdir_rf(BOOST_GZIP_FILE_PATH, $flush, TRUE);
}
//recreate dirs
_boost_mkdir_p(BOOST_FILE_PATH);
_boost_mkdir_p(BOOST_GZIP_FILE_PATH);
//add in .boost root id file
_boost_write_file_chmod(BOOST_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
_boost_write_file_chmod(BOOST_GZIP_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
foreach (_boost_copy_file_get_domains(BOOST_PERM_FILE_PATH) as $dir) {
_boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
}
foreach (_boost_copy_file_get_domains(BOOST_PERM_GZIP_FILE_PATH) as $dir) {
_boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
}
// Make sure cache dir has htaccess rules
boost_htaccess_cache_dir_put();
}
function boost_htaccess_cache_dir_put() {
// Server is not apache; do nothing
if (stristr($_SERVER["SERVER_SOFTWARE"], 'apache') == FALSE) {
return TRUE;
}
// Get some info
$cache_dir = BOOST_ROOT_CACHE_DIR;
$filename = $cache_dir . '/.htaccess';
$generated = boost_htaccess_cache_dir_generate();
$htaccess = file_exists($filename) ? file_get_contents($filename) : FALSE;
// htaccess exists and has the correct contents
if ($htaccess && strcmp($htaccess, $generated) === 0) {
return TRUE;
}
// cache dir doesn't exist, try to create it; if no go, bail out.
if (!is_dir($cache_dir) && !_boost_mkdir_p($cache_dir)) {
return FALSE;
}
// cache dir htaccess is not there, create it.
$result = file_put_contents($filename, $generated);
if ($result) {
return $result;
}
else {
return FALSE;
}
}
function boost_htaccess_cache_dir_generate() {
Global $base_path;
// no dot
$_html = str_replace('.', '', BOOST_FILE_EXTENSION);
$_xml = str_replace('.', '', BOOST_XML_EXTENSION);
$_css = str_replace('.', '', BOOST_CSS_EXTENSION);
$_js = str_replace('.', '', BOOST_JS_EXTENSION);
$_json = str_replace('.', '', BOOST_JSON_EXTENSION);
$_gz = str_replace('.', '', BOOST_GZIP_EXTENSION);
// with a \ slash
$gz = str_replace('.', '\\.', BOOST_GZIP_EXTENSION);
$string = '';
if (BOOST_CACHE_HTML) {
if (variable_get('boost_force_utf8', TRUE)) {
$string .= "AddDefaultCharset utf-8\n";
}
}
switch (variable_get('boost_apache_etag', 0)) {
case 0:
break;
case 1:
$string .= "FileETag None\n";
break;
case 2:
$string .= "FileETag All\n";
break;
case 3:
$string .= "FileETag MTime Size\n";
break;
}
if (!BOOST_DISABLE_CLEAN_URL && (BOOST_CACHE_HTML || BOOST_CACHE_XML || BOOST_CACHE_JSON)) {
$files = array();
if (BOOST_CACHE_HTML) {
$files[] = $_html;
}
if (BOOST_CACHE_XML) {
$files[] = $_xml;
}
if (BOOST_CACHE_JSON) {
$files[] = $_json;
}
$files = '(' . implode('|' , $files) . ')';
if (BOOST_GZIP) {
$files = "$files|($files$gz)";
}
$string .= "\n";
$string .= " \n";
$string .= " ExpiresDefault A1\n";
$string .= " \n";
$string .= " \n";
$string .= " Header set Expires \"Sun, 19 Nov 1978 05:00:00 GMT\"\n";
$string .= " Header set Cache-Control \"no-store, no-cache, must-revalidate, post-check=0, pre-check=0\"\n";
if (variable_get('boost_apache_xheader', 0) > 0) {
$string .= " Header set X-Header \"Boost Citrus 1.8\"\n";
}
$string .= " \n";
$string .= "\n";
}
if (BOOST_CACHE_HTML || BOOST_CACHE_XML || BOOST_CACHE_CSS || BOOST_CACHE_JS || BOOST_CACHE_JSON) {
$string .= "\n";
$string .= BOOST_CACHE_HTML ? " AddCharset utf-8 .$_html\n" : '';
$string .= BOOST_CACHE_XML ? " AddCharset utf-8 .$_xml\n" : '';
$string .= BOOST_CACHE_CSS ? " AddCharset utf-8 .$_css\n" : '';
$string .= BOOST_CACHE_JS ? " AddCharset utf-8 .$_js\n" : '';
$string .= BOOST_CACHE_JSON ? " AddCharset utf-8 .$_json\n" : '';
$string .= BOOST_GZIP ? " AddEncoding gzip .$_gz\n" : '';
$string .= "\n";
}
// Fix for versions of apache that do not respect the T='' RewriteRule
$files = '';
if (BOOST_CACHE_HTML) {
$files .= "$_html|";
if (BOOST_GZIP) {
$files .= "$_html$gz|";
}
$files = trim($files, '|');
$string .= "\n";
$string .= " ForceType text/html\n";
$string .= "\n";
}
$files = '';
if (BOOST_CACHE_XML) {
$files .= "$_xml|";
if (BOOST_GZIP) {
$files .= "$_xml$gz|";
}
$files = trim($files, '|');
$string .= "\n";
$string .= " ForceType text/xml\n";
$string .= "\n";
}
$files = '';
if (BOOST_CACHE_JSON) {
$files .= "$_json|";
if (BOOST_GZIP) {
$files .= "$_json$gz|";
}
}
if (BOOST_CACHE_JS) {
$files .= "$_js|";
if (BOOST_GZIP) {
$files .= "$_js$gz|";
}
}
if ($files != '') {
$files = trim($files, '|');
$string .= "\n";
$string .= " ForceType text/javascript\n";
$string .= "\n";
}
$files = '';
if (BOOST_CACHE_CSS) {
$files .= "$_css|";
if (BOOST_GZIP) {
$files .= "$_css$gz|";
}
$files = trim($files, '|');
$string .= "\n";
$string .= " ForceType text/css\n";
$string .= "\n";
}
$string .= "\n";
$string .= "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\n";
$string .= "Options None\n";
$string .= "Options +FollowSymLinks\n";
$string .= "\n";
return $string;
}
/**
* Finds all possible paths/redirects/aliases given the root path.
*
* @param $paths
* Array of current URLs
* @param $both
* Expire database & file
* @param $force_flush
* Override the settings and kill the file
*/
function boost_cache_expire_derivative($paths, $both = FALSE, $force_flush = FALSE, $debug = FALSE) {
global $base_path;
$expire = array();
if (empty($paths)) {
return FALSE;
}
foreach ($paths as $path) {
// Given path
$expire[] = $path;
// Add the empty front page path if this is the alias
if ($path == variable_get('site_frontpage', 'node')) {
$expire[] = '';
$expire[] = 'rss.xml';
}
// Special front page feed handling
if (BOOST_CACHE_XML && ($path == '' || $path == '')) {
$expire[] = 'rss.xml';
}
// Path alias
$path_alias = url($path, array('absolute' => FALSE));
if ($base_path != '/') {
$path_alias = implode('/', array_diff_assoc(array_filter(explode('/', $path_alias)), array_filter(explode('/', $base_path))));
}
$expire[] = $path_alias;
// Path redirects
if (module_exists('path_redirect')) {
$path_redirects = boost_path_redirect_load(array('redirect' => $path));
if (isset($path_redirects)) {
foreach ($path_redirects as $path_redirect) {
$expire[] = $path_redirect['path'];
}
}
}
}
// Expire cached files
$counter = 0;
if (empty($expire)) {
return FALSE;
}
$expire = array_unique($expire);
if (!$debug) {
if (BOOST_NO_DATABASE ) {
$counter += boost_cache_expire_by_filename($expire, TRUE, $force_flush);
}
elseif ($both) {
$counter += boost_cache_expire_by_db($expire);
$counter += boost_cache_expire_by_filename($expire, TRUE, $force_flush);
}
else {
$counter += boost_cache_expire_by_db($expire);
if ($counter == 0) {
// Database was a negative. Fallback: Look into flushing by filename
$counter += boost_cache_expire_by_filename($expire, TRUE, $force_flush);
}
}
return $counter;
}
else {
return array('in' => $expire, 'db' => boost_cache_expire_by_db($expire, TRUE), 'filename' => boost_cache_expire_by_filename($expire, TRUE, FALSE, TRUE));
}
}
/**
* Expires the static file cache for the given paths via database.
*
* @param $paths
* Array of URL's
* @param $debug
* TRUE to display what would have happened
*/
function boost_cache_expire_by_db($paths, $debug = FALSE) {
$hashes = array();
if (empty($paths)) {
return FALSE;
}
// Get all cache files directly associated with this path
foreach ($paths as $path) {
// With URL Variables
$html = boost_file_path($path, TRUE, BOOST_FILE_EXTENSION);
if ($html !== FALSE) {
$xml = boost_file_path($path, TRUE, BOOST_XML_EXTENSION);
$json = boost_file_path($path, TRUE, BOOST_JSON_EXTENSION);
// Hash the paths
$hashes[] = md5($html);
$hashes[] = md5($xml);
$hashes[] = md5($json);
}
// Without URL Variables
$html = boost_file_path($path, FALSE, BOOST_FILE_EXTENSION);
if ($html !== FALSE) {
$xml = boost_file_path($path, FALSE, BOOST_XML_EXTENSION);
$json = boost_file_path($path, FALSE, BOOST_JSON_EXTENSION);
// Hash the paths
$hashes[] = md5($html);
$hashes[] = md5($xml);
$hashes[] = md5($json);
}
}
$result = boost_db_multi_select_in('boost_cache', 'hash', "'%s'", $hashes);
// Eliminate duplicates with the key hash
$data = array();
$counter = 0;
$filenames = array();
if ($result) {
while ($info = db_fetch_array($result)) {
if (($info['page_callback'] == 'node' || $info['page_callback'] == 'taxonomy') && $info['page_id'] == 0) {
// If we can't get a 'lock' just expire the file
$filenames[] = $info['filename'];
}
elseif ($info['page_id'] != '' && $info['page_type'] != '' && $info['page_callback'] != '') {
// Use boost_cache_expire_router() if we can get a 'lock' on this item in the database
$hash = BOOST_FILE_PATH . $info['page_callback'] . $info['page_type'] . $info['page_id'];
$data[$hash] = $info;
}
else {
// If we can't get a 'lock' just expire the file
$filenames[] = $info['filename'];
}
}
// Expire all files that match up
if (!$debug) {
if ($data) {
boost_set_base_dir_in_array($data);
$counter += boost_cache_expire_router($data);
}
if ($filenames) {
$counter += boost_cache_flush_by_filename($filenames);
}
}
else {
boost_set_base_dir_in_array($data);
return array($data, $filenames);
}
}
return $counter;
}
/**
* Expires the static file cache for paths matching a wildcard via filesystem.
*
* @param $path
* Array of URLs
* @param $wildcard
* If true get all chached files that start with this path.
* @param $force_flush
* If true kill file no matter what.
*/
function boost_cache_expire_by_filename($paths, $wildcard = TRUE, $force_flush, $debug = FALSE) {
$filenames = array();
if (empty($paths)) {
return FALSE;
}
foreach ($paths as $path) {
// Sanity check
if (boost_file_path($path, FALSE) === FALSE) {
continue;
}
// Get list of related files
$html = glob(boost_file_path($path, FALSE, NULL) . (($wildcard) ? '*' : '') . BOOST_FILE_EXTENSION, GLOB_NOSORT);
$xml = glob(boost_file_path($path, FALSE, NULL) . (($wildcard) ? '*' : '') . BOOST_XML_EXTENSION, GLOB_NOSORT);
$json = glob(boost_file_path($path, FALSE, NULL) . (($wildcard) ? '*' : '') . BOOST_JSON_EXTENSION, GLOB_NOSORT);
// Make sure something is in the arrays
$html[] = '';
$xml[] = '';
$json[] = '';
// Merge arrays
$filenames = array_filter(array_merge($filenames, $html, $xml, $json));
}
// Remove double slash from filename if it exists.
foreach ($filenames as $key => $filename) {
$filenames[$key] = implode('/', array_filter(explode('/', $filename)));
}
// Flush expired files
if (!$debug) {
boost_cache_flush_by_filename($filenames, $force_flush);
}
else {
return $filenames;
}
}
/**
* Expires the static file cache for files given.
*
* @param array $filenames
* filenames
* @param $force_flush
* If true get all chached files that start with this path.
*/
function boost_cache_flush_by_filename($filenames, $force_flush = FALSE) {
global $_boost;
$files = array();
if ($filenames) {
$filenames = array_unique($filenames);
foreach ($filenames as $filename) {
$files[] = array('filename' => $filename);
}
$counter = boost_cache_kill($files, $force_flush);
if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_flush_filename'])) {
watchdog('boost', 'Debug: boost_cache_flush_by_filename() Following files where flushed: !list', array('!list' => implode(' ', $filenames)));
}
return $counter;
}
else {
return FALSE;
}
}
/**
* Expires the static file cache for the given router items.
*
* @param $router_items
* Array of $router_item array "objects"
* ['page_callback']
* ['page_type']
* ['page_id']
* ['base_dir']
* @param $force_flush
* Override BOOST_EXPIRE_NO_FLUSH setting
* @param $remove_from_db
* Remove this entry from the boost-cache table.
* @param $debug
* Do nothing but return what would have been expired.
*/
function boost_cache_expire_router($router_items, $force_flush = FALSE, $remove_from_db = FALSE, $debug = FALSE) {
// Get filenames & hash from db
if (!is_array($router_items)) {
return FALSE;
}
global $_boost;
$count = 0;
$files = $list = array();
foreach ($router_items as $dblookup) {
if (isset($dblookup['base_dir'])) {
if (isset($dblookup['page_id']) && isset($dblookup['page_type']) && isset($dblookup['page_callback'])) {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_type'], $dblookup['page_id']);
if (!$debug) {
db_query("DELETE FROM {boost_cache_relationships} WHERE base_dir = '%s' AND page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_type'], $dblookup['page_id']);
}
}
elseif (isset($dblookup['page_id']) && isset($dblookup['page_callback'])) {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = '%s' AND page_id = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_id']);
if (!$debug) {
db_query("DELETE FROM {boost_cache_relationships} WHERE base_dir = '%s' AND page_callback = '%s' AND page_id = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_id']);
}
}
elseif (isset($dblookup['page_type']) && isset($dblookup['page_callback'])) {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = '%s' AND page_type = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_type']);
if (!$debug) {
db_query("DELETE FROM {boost_cache_relationships} WHERE base_dir = '%s' AND page_callback = '%s' AND page_type = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_type']);
}
}
elseif (isset($dblookup['page_callback'])) {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = '%s'", $dblookup['base_dir'], $dblookup['page_callback']);
if (!$debug) {
db_query("DELETE FROM {boost_cache_relationships} WHERE base_dir = '%s' AND page_callback = '%s'", $dblookup['base_dir'], $dblookup['page_callback']);
}
}
else {
continue;
}
}
else {
if (isset($dblookup['page_id']) && isset($dblookup['page_type']) && isset($dblookup['page_callback'])) {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $dblookup['page_callback'], $dblookup['page_type'], $dblookup['page_id']);
if (!$debug) {
db_query("DELETE FROM {boost_cache_relationships} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $dblookup['page_callback'], $dblookup['page_type'], $dblookup['page_id']);
}
}
elseif (isset($dblookup['page_id']) && isset($dblookup['page_callback'])) {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE page_callback = '%s' AND page_id = '%s'", $dblookup['page_callback'], $dblookup['page_id']);
if (!$debug) {
db_query("DELETE FROM {boost_cache_relationships} WHERE page_callback = '%s' AND page_id = '%s'", $dblookup['page_callback'], $dblookup['page_id']);
}
}
elseif (isset($dblookup['page_type']) && isset($dblookup['page_callback'])) {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE page_callback = '%s' AND page_type = '%s'", $dblookup['page_callback'], $dblookup['page_type']);
if (!$debug) {
db_query("DELETE FROM {boost_cache_relationships} WHERE page_callback = '%s' AND page_type = '%s'", $dblookup['page_callback'], $dblookup['page_type']);
}
}
elseif (isset($dblookup['page_callback'])) {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE page_callback = '%s'", $dblookup['page_callback']);
if (!$debug) {
db_query("DELETE FROM {boost_cache_relationships} WHERE page_callback = '%s'", $dblookup['page_callback']);
}
}
else {
continue;
}
}
while ($info = db_fetch_array($result)) {
if (stristr($info['filename'], '#')) {
continue;
}
$files[$info['hash']] = $info;
if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_expire_router'])) {
$list[] = $info['filename'];
}
}
}
if (!$debug) {
if (count($files)) {
$count = boost_cache_kill($files, $force_flush);
if ($remove_from_db) {
boost_remove_db($files);
}
}
if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_expire_router'])) {
watchdog('boost', 'Debug: boost_cache_expire_router() Following files where flushed: !list
Input: !input Files: !files', array(
'!list' => implode(' ', $list),
'!input' => boost_print_r($router_items, TRUE, TRUE),
'!files' => boost_print_r($files, TRUE, TRUE),
));
}
return $count;
}
else {
return $files;
}
}
/**
* Deletes cached page from file system & database.
*
* @param array $urls
* list of urls to remove from the boost cache
* @param boolean $force_flush = TRUE
* Override BOOST_EXPIRE_NO_FLUSH setting.
*/
function boost_cache_kill_url($urls, $force_flush = TRUE) {
global $base_path;
$files = array();
foreach ($urls as $value) {
$decoded = urldecode($value);
if ($decoded != $value) {
$urls[] = $decoded;
}
$raw = rawurldecode($value);
if ($raw != $decoded) {
$urls[] = $decoded;
}
}
$urls = array_unique($urls);
$hashes = array_map('md5', $urls);
$parts = array_map('parse_url', $urls);
foreach ($parts as $part) {
$files[]['filename'] = boost_file_path(ltrim($part['path'], $base_path), TRUE, BOOST_FILE_EXTENSION);
}
$result = boost_db_multi_select_in('boost_cache', 'hash_url', "'%s'", $hashes);
while ($row = db_fetch_array($result)) {
$files[] = array('filename' => $row['filename'], 'hash' => $row['hash']);
}
// Try again with a more expensive query
if (empty($files)) {
$result = boost_db_multi_select_in('boost_cache', 'url', "'%s'", $urls);
while ($row = db_fetch_array($result)) {
$files[] = array('filename' => $row['filename'], 'hash' => $row['hash']);
}
}
if (!empty($files)) {
boost_cache_kill($files, $force_flush);
boost_remove_db($files);
return TRUE;
}
else {
return FALSE;
}
}
/**
* Deletes cached page from file system.
*
* @param array $files
* An array of files. Each file is a secondary array must have a key for 'filename'
* Optional keys for 'hash' and 'base_dir' that will be recalculated if necessary.
* The hash is the primary key in the database. If omitted it will be recalculated from the filename.
* @param boolean $force_flush = FALSE
* Override BOOST_EXPIRE_NO_FLUSH setting.
*/
function boost_cache_kill($files, $force_flush = FALSE) {
$hashes = array();
$count = 0;
if (!$files) {
return FALSE;
}
// If not ignoring file removal
// AND site is multisite and cache path matches filename
// OR full base url matches filename
if (variable_get('boost_ignore_flush', 0) < 3) {
// Calc md5 hash and set base dir
foreach ($files as $key => $file) {
if (!is_string($file['filename'])) {
if (BOOST_VERBOSE >= 5) {
watchdog('boost', 'Error in boost_cache_kill() String was not given for filename: !output', array('!output' => boost_print_r($file, TRUE, TRUE)));
}
continue;
}
if (empty($file['hash'])) {
$files[$key]['hash'] = md5($file['filename']);
}
if (empty($file['base_dir'])) {
$files[$key]['base_dir'] = BOOST_FILE_PATH;
}
$hashes[] = $files[$key]['hash'];
if (stristr($files[$key]['filename'], BOOST_ROOT_CACHE_DIR) == FALSE) {
unset($files[$key]);
}
}
// Expire entries from Database
if (count($hashes)) {
if ($force_flush || !BOOST_EXPIRE_NO_FLUSH) {
boost_db_multi_update_set('boost_cache', 'expire', '%d', 0, 'hash', "'%s'", $hashes);
boost_db_multi_update_set('boost_cache', 'timer', '%d', 0, 'hash', "'%s'", $hashes);
}
else {
boost_db_multi_update_set('boost_cache', 'expire', '%d', 434966400, 'hash', "'%s'", $hashes);
$count = db_affected_rows();
}
}
// Kill Files from filesystem
if ($force_flush || !BOOST_EXPIRE_NO_FLUSH) {
foreach ($files as $file) {
$filenames = boost_get_all_filenames($file['filename'], $file['base_dir']);
foreach ($filenames as $key => $values) {
foreach ($values as $num => $filename) {
if (file_exists($filename)) {
@unlink($filename);
if ($key == 'normal' && $num == 0) {
$count++;
}
}
}
}
}
}
}
return $count;
}
/**
* Flushes all expired pages from cache.
*
* TODO del empty dirs if enabled
*/
function boost_cache_expire_all() {
if (variable_get('boost_ignore_flush', 0) < 2) {
if (BOOST_NO_DATABASE) {
boost_cache_expire_all_filesystem();
}
else {
boost_cache_expire_all_db();
}
return TRUE;
}
return FALSE;
}
/**
* Flushes all expired pages via database lookup.
*
* TODO del empty dirs if enabled
*/
function boost_cache_expire_all_db() {
global $_boost;
$list = $files = array();
if (BOOST_FLUSH_ALL_MULTISITE) {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE expire BETWEEN 1 AND %d", BOOST_TIME);
}
else {
$result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND expire BETWEEN 1 AND %d", BOOST_FILE_PATH, BOOST_TIME);
}
while ($boost = db_fetch_array($result)) {
$files[] = $boost;
if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_expire_all_db_list'])) {
$list[] = $boost['filename'];
}
}
if (count($files)) {
$count = boost_cache_kill($files, TRUE);
}
if (BOOST_FLUSH_DIR) {
// TO-DO: del empty dirs.
}
if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_expire_all_db_list'])) {
watchdog('boost', 'Debug: boost_cache_expire_all_db() Following files where flushed: !list', array('!list' => implode(' ', $list)));
}
elseif (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_verbose_refined'])) {
watchdog('boost', 'Debug: boost_cache_expire_all_db() !num files where flushed', array('!num' => $count));
}
return TRUE;
}
/**
* Returns the cached contents of the specified page, if available.
*
* @param $path
* Current URL
*/
function boost_cache_get($path) {
if (($filename = boost_file_path($path))) {
if (file_exists($filename) && is_readable($filename)) {
return file_get_contents($filename);
}
}
return NULL;
}
/**
* Returns all possible filenames given the input and current settings
*
* @param $filename
* Name of file
* @param $base_dir
* Value from base_dir column in database
* @return
* returns a 2 dimensional array
* 1st dimension key is either gzip or normal
* 2nd dimension contains all the filenames
*/
function boost_get_all_filenames($filename, $base_dir = NULL) {
$namesA = array();
$namesB = array();
$filenames = array();
$base_dir = is_null($base_dir) ? BOOST_FILE_PATH : $base_dir;
if (stristr($filename, '[')) {
$paths = explode('/', $filename);
$end = array_pop($paths);
$end = preg_replace("(\x5B[0-9]\x5D)", ']', $end);
$paths[] = $end;
$namesA[] = implode('/', $paths);
}
$namesA[] = $filename;
foreach ($namesA as $filename) {
$namesB[] = $filename;
// Generate urlencoded filename, if name contains decoded characters
$paths = explode('/', $filename);
$end = array_pop($paths);
$end = str_replace('[', '%5B', $end);
$end = str_replace(']', '%5D', $end);
$end = str_replace(',', '%2C', $end);
$end = str_replace(' ', '%20', $end);
$paths[] = $end;
$namesB[] = implode('/', $paths);
}
$namesB = array_unique($namesB);
// Generate gzip filenames
foreach ($namesB as $name) {
if (BOOST_SET_FILE_ENCODING != '') {
$name = iconv("UTF-8", BOOST_SET_FILE_ENCODING, $name);
}
$filenames['normal'][] = $name;
if (BOOST_GZIP) {
// Replace the correct dir with the gzip version for the given base dir.
$gzip_base_path = implode('/', array_filter(explode('/', str_replace(BOOST_ROOT_CACHE_DIR . '/' . BOOST_NORMAL_DIR, BOOST_ROOT_CACHE_DIR . '/' . BOOST_GZIP_DIR . '/', $base_dir))));
$filenames['gzip'][] = str_replace($base_dir, $gzip_base_path, $name) . BOOST_GZIP_EXTENSION;
}
}
return $filenames;
}
/**
* Edit document before it is put into the boost cache.
*
* This hook is run at right before the page is cached by boost.
*
* $GLOBALS['_boost_cache_this'] and $GLOBALS['_boost_router_item'] are useful.
* set $GLOBALS['_boost_cache_this'] = FALSE if you wish to not cache this page.
*
* @param $path
* URL path of the document
* @param $data
* String containing the data
* @param $extension
* file extension type. Use to detect what type of document your operating on.
* @return
* $data string containing the document
*/
function hook_boost_preprocess($path, $data, $extension) {
return $data;
}
/**
* Replaces/Sets the cached contents of the specified page, if stale.
*
* @param $path
* Current URL
* @param $data
* URL's contents
* @param $extension
* File extension for this mime type
*/
function boost_cache_set($path, $data, $extension = BOOST_FILE_EXTENSION) {
// Exit if nothing is here to cache
if (empty($data)) {
return FALSE;
}
// Get custom expiration time if set
$router_item = _boost_get_menu_router();
$settings = boost_get_settings_db($router_item);
$expire = -2;
foreach ($settings as $value) {
if ($value != NULL) {
$expire = $value['lifetime'];
break;
}
}
$cached_at = date('Y-m-d H:i:s', BOOST_TIME);
// Code commenting style based on what is being cached.
// Append the Boost footer with the relevant timestamps
switch ($extension) {
case BOOST_FILE_EXTENSION:
$expire = ($expire == -2) ? BOOST_CACHE_LIFETIME : $expire;
if (variable_get('boost_apache_xheader', 0) < 2) {
$comment_start = '\n";
$expires_at = date('Y-m-d H:i:s', BOOST_TIME + $expire);
$comment = $comment_start . str_replace(array('%cached_at', '%expires_at'), array($cached_at, $expires_at), BOOST_BANNER) . $comment_end;
//$data = _boost_inject_code(rtrim($data), "\n" . $comment);
$data = rtrim($data) . "\n" . $comment;
}
break;
case BOOST_XML_EXTENSION:
$expire = ($expire == -2) ? BOOST_CACHE_XML_LIFETIME : $expire;
if (variable_get('boost_apache_xheader', 0) < 2) {
$comment_start = '\n";
$expires_at = date('Y-m-d H:i:s', BOOST_TIME + $expire);
$comment = $comment_start . str_replace(array('%cached_at', '%expires_at'), array($cached_at, $expires_at), BOOST_BANNER) . $comment_end;
$data = rtrim($data) . "\n" . $comment;
}
break;
case BOOST_JSON_EXTENSION:
$expire = ($expire == -2) ? BOOST_CACHE_JSON_LIFETIME : $expire;
if (variable_get('boost_apache_xheader', 0) < 2) {
$comment_start = '/* ';
$comment_end = " */\n";
$expires_at = date('Y-m-d H:i:s', BOOST_TIME + $expire);
$comment = $comment_start . str_replace(array('%cached_at', '%expires_at'), array($cached_at, $expires_at), BOOST_BANNER) . $comment_end;
$data = rtrim($data) . "\n" . $comment;
}
break;
}
// Invoke hook_boost_preprocess($path, $data, $extension)
foreach (module_implements('boost_preprocess') as $module) {
if (($result = module_invoke($module, 'boost_preprocess', $path, $data, $extension)) != NULL) {
$data = $result;
}
}
// Execute the pre-process function if one has been defined
if (function_exists(BOOST_PRE_PROCESS_FUNCTION)) {
$data = call_user_func(BOOST_PRE_PROCESS_FUNCTION, $path, $data, $extension);
}
db_set_active();
// Final check, make sure this page should be cached. Allow for the preprocess
// function to have a final say in if this page should be cached.
if (!$GLOBALS['_boost_cache_this'] || empty($data)) {
return FALSE;
}
// Create or update the static files as needed
if (($filename = boost_file_path($path, TRUE, $extension)) && (BOOST_OVERWRITE_FILE || !file_exists($filename) || boost_db_is_expired($filename))) {
// Special handling of the front page for aggressive gzip test
if ($path == '' && BOOST_AGGRESSIVE_GZIP && $extension == BOOST_FILE_EXTENSION) {
_boost_generate_gzip_test_file();
boost_cache_write($filename, _boost_inject_code($data,
'' . "\n"));
}
else {
boost_cache_write($filename, $data);
}
if (!BOOST_NO_DATABASE) {
boost_db_prep($filename, $extension, BOOST_TIME + $expire);
boost_cache_set_node_relationships(isset($GLOBALS['_boost_relationships']) ? $GLOBALS['_boost_relationships'] : array());
}
return TRUE;
}
else {
return FALSE;
}
}
/**
* Creates a parent child relationship for pages like views.
*
* @param $relationships
* Array of $router_item array "objects"
* Required
* ['child_page_callback']
* ['child_page_type']
* ['child_page_id']
* Optional
* ['base_dir']
* ['page_callback']
* ['page_type']
* ['page_id']
*
* If Optional is not set it will use values in $GLOBALS['_boost_router_item']
*/
function boost_cache_set_node_relationships($relationships) {
global $base_root, $_boost;
$url = $base_root . request_uri();
if (variable_get('boost_store_url_percent_enc', FALSE)) {
$url = urldecode(rawurlencode($url));
}
$hash_url = md5($url);
$router_item = _boost_get_menu_router();
if (!is_array($relationships) || empty($relationships)) {
return FALSE;
}
// Grab all entries related to this url
$old_data = array();
$results = db_query("SELECT hash, timestamp FROM {boost_cache_relationships} WHERE hash_url = '%s'", $hash_url);
while ($row = db_fetch_array($results)) {
$old_data[$row['hash']] = $row['timestamp'];
}
$counter = 0;
$new_data = array();
foreach ($relationships as $data) {
// If one of the required items is not set, skip this entry
if (!isset($data['child_page_callback']) || !isset($data['child_page_type']) || !isset($data['child_page_id'])) {
if (BOOST_VERBOSE >= 5) {
watchdog('boost', 'boost_cache_set_node_relationships() child_page_* was not set. !data !backtrace', array('!data' => boost_print_r($data, TRUE, TRUE), '!backtrace' => boost_backtrace(FALSE)));
}
continue;
}
// Set the optional parameters
$data['base_dir'] = isset($data['base_dir']) ? $data['base_dir'] : BOOST_FILE_PATH;
$data['page_callback'] = isset($data['page_callback']) ? $data['page_callback'] : $router_item['page_callback'];
$data['page_type'] = isset($data['page_type']) ? $data['page_type'] : $router_item['page_type'];
$data['page_id'] = isset($data['page_id']) ? $data['page_id'] : $router_item['page_id'];
// Sanity Checks
foreach ($data as $key => $value) {
$loop = 0;
while (is_array($data[$key]) && $loop < 25) {
$data[$key] = array_pop($value);
$loop++;
}
}
foreach ($data as $key => $value) {
if (is_object($value) || is_array($value)) {
if (BOOST_VERBOSE >= 5) {
watchdog('boost', 'boost_cache_set_node_relationships() "!key" has a value of "!value"; should be a string or int.', array('!key' => $key, '!value' => $value));
}
continue;
}
}
// Skip if this is referencing its self.
if ($data['page_callback'] == $data['child_page_callback'] && $data['page_type'] == $data['child_page_type'] && $data['page_id'] == $data['child_page_id']) {
continue;
}
// Create the primary key
$data['hash'] = md5($data['base_dir'] . $data['page_callback'] . $data['page_type'] . $data['page_id'] . $data['child_page_callback'] . $data['child_page_type'] . $data['child_page_id']);
$new_data[] = $data;
}
// Count number of entries
$update_required = FALSE;
if (count($new_data) !== count($old_data)) {
$update_required = TRUE;
}
// See if new and old data is not the same
if (!$update_required) {
foreach ($new_data as $data) {
if (empty($old_data[$data['hash']])) {
$update_required = TRUE;
break;
}
}
}
if ($update_required) {
foreach ($new_data as $data) {
// Insert data into database
db_query("UPDATE {boost_cache_relationships}
SET base_dir = '%s',
page_callback = '%s',
page_type = '%s',
page_id = '%s',
child_page_callback = '%s',
child_page_type = '%s',
child_page_id = '%s',
hash_url = '%s',
timestamp = '%d'
WHERE hash = '%s'", $data['base_dir'], $data['page_callback'], $data['page_type'], $data['page_id'], $data['child_page_callback'], $data['child_page_type'], $data['child_page_id'], $hash_url, BOOST_TIME, $data['hash']);
if (!db_affected_rows()) {
@db_query("INSERT INTO {boost_cache_relationships} (hash, base_dir, page_callback, page_type, page_id, child_page_callback, child_page_type, child_page_id, hash_url, timestamp) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)", $data['hash'], $data['base_dir'], $data['page_callback'], $data['page_type'], $data['page_id'], $data['child_page_callback'], $data['child_page_type'], $data['child_page_id'], $hash_url, BOOST_TIME);
}
$counter++;
}
$removed = boost_cache_prune_node_relationship($hash_url);
if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_cache_set_node_relationships'])) {
watchdog('boost', 'Debug: boost_cache_set_node_relationships() !num of !total given entries to the boost_cache_relationships table added or updated; !removed entries removed due to them being outdated. !dump',
array(
'!num' => $counter,
'!total' => count($GLOBALS['_boost_relationships']),
'!removed' => $removed,
'!dump' => str_replace(' ', ' ', nl2br(htmlentities(print_r($relationships, TRUE)))),
)
);
}
}
unset($GLOBALS['_boost_relationships']);
return TRUE;
}
/**
* Creates a parent child relationship for pages like views.
*
* @param $relationships
* Array of $router_item array "objects"
* Required
* ['page_callback']
* ['page_type']
* ['page_id']
* Optional
* ['base_dir']
*/
function boost_cache_get_node_relationships($relationships) {
if (!is_array($relationships)) {
return FALSE;
}
$results = array();
foreach ($relationships as $data) {
// If one of the required items is not set, skip this entry
if (!isset($data['page_callback']) || !isset($data['page_type']) || !isset($data['page_id'])) {
continue;
}
if (BOOST_FLUSH_ALL_MULTISITE) {
$result = db_query("SELECT page_callback, page_type, page_id, base_dir FROM {boost_cache_relationships} WHERE child_page_id = '%s' AND child_page_type = '%s' AND child_page_callback = '%s'", $data['page_id'], $data['page_type'], $data['page_callback']);
}
else {
$data['base_dir'] = isset($data['base_dir']) ? $data['base_dir'] : BOOST_FILE_PATH;
$result = db_query("SELECT page_callback, page_type, page_id, base_dir FROM {boost_cache_relationships} WHERE child_page_id = '%s' AND child_page_type = '%s' AND child_page_callback = '%s' AND base_dir = '%s'", $data['page_id'], $data['page_type'], $data['page_callback'], $data['base_dir']);
}
while ($info = db_fetch_array($result)) {
$hash = 'relationship base:' . $info['base_dir'] . ' ' . $info['page_callback'] . ' ' . $info['page_type'] . ' ' . $info['page_id'];
$results[$hash] = $info;
}
}
return $results;
}
/**
* Given hash of url delete any old relationships.
*
* @param $hash_url
*/
function boost_cache_prune_node_relationship($hash_url) {
// Grab all entires related to this URL; find ones that don't match the latest
// timestamp and remove them.
$records = 0;
$result = db_query("SELECT hash, timestamp FROM {boost_cache_relationships} WHERE hash_url = '%s' ORDER BY timestamp DESC", $hash_url);
while ($info = db_fetch_array($result)) {
if ($info['timestamp'] < BOOST_TIME) {
db_query("DELETE FROM {boost_cache_relationships} WHERE hash = '%s'", $info['hash']);
$records++;
}
}
return $records;
}
/**
* Figure out what is going in the database & put it in
*
* @param $filename
* Name of cached file; primary key in database
* @param $extension
* Filename extension: Used for content types.
* @param $expire
* Cache expiration time in seconds (UNIX time).
*/
function boost_db_prep($filename, $extension, $expire) {
$router_item = _boost_get_menu_router();
$timer = timer_read('page');
$timer_average = $timer;
$lifetime = -1;
$push = -1;
$settings = boost_get_settings_db($router_item);
foreach ($settings as $value) {
if ($value != NULL) {
$boost_settings_db = $value;
break;
}
}
$boost_db = boost_get_db($filename);
//get time data from actual entry, if this page has been cached before.
if ($boost_db) {
// $expire = $boost_db['lifetime'] != -1 ? $boost_db['lifetime'] + BOOST_TIME : $expire;
// $lifetime = $boost_db['lifetime'];
// $push = $boost_db['push'];
$timer_average = ($boost_db['timer_average'] + $timer)/2;
}
//get data from settings table, if this page has not been put into the cache.
if (isset($boost_settings_db)) {
$expire = $boost_settings_db['lifetime'] != -1 ? $boost_settings_db['lifetime'] + BOOST_TIME : $expire;
$lifetime = $boost_settings_db['lifetime'];
$push = $boost_settings_db['push'];
}
boost_put_db($filename, $expire, $lifetime, $push, $router_item, $timer, $timer_average, $extension);
}
/**
* Puts boost info into database.
*
* @param $filename
* Name of cached file; hash of this is primary key in database
* @param $expire
* Expiration time
* @param $lifetime
* Default lifetime
* @param $push
* Pre-cache this file
* @param $router_item
* Array containing page_callback, page_type & page_id.
* @param $timer
* Time it took drupal to build this page.
* @param $timer_average
* Average time Drupal has spent building this page.
* @param $extension
* Filename extension: Used for content types.
* @param $url
* Optional: Full URL of cached page
* @param $file_path
* Optional: BOOST_FILE_PATH
*/
function boost_put_db($filename, $expire, $lifetime, $push, $router_item, $timer, $timer_average, $extension, $url = NULL, $file_path = NULL) {
global $base_root;
$url = is_null($url) ? $base_root . request_uri() : $url;
$file_path = is_null($file_path) ? BOOST_FILE_PATH : $file_path;
$hash = md5($filename);
if (variable_get('boost_store_url_percent_enc', FALSE)) {
$url = urldecode(rawurlencode($url));
}
$hash_url = md5($url);
db_query("UPDATE {boost_cache} SET expire = %d, lifetime = %d, push = %d, page_callback = '%s', page_type = '%s', timer = %d, timer_average = %d, base_dir = '%s', page_id = '%s', extension = '%s', url = '%s', filename = '%s', hash_url = '%s' WHERE hash = '%s'", $expire, $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], $timer, $timer_average, $file_path, $router_item['page_id'], $extension, $url, $filename, $hash_url, $hash);
if (!db_affected_rows()) {
@db_query("INSERT INTO {boost_cache} (hash, hash_url, filename, expire, lifetime, push, page_callback, page_type, timer, timer_average, base_dir, page_id, extension, url) VALUES ('%s', '%s', '%s', %d, %d, %d, '%s', '%s', %d, %d, '%s', '%s', '%s', '%s')", $hash, $hash_url, $filename, $expire, $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], $timer, $timer_average, $file_path, $router_item['page_id'], $extension, $url);
}
}
/**
* Removes info from database. Use on 404 or 403.
*
* @param array $files
* An array of files. Each file is a secondary array with keys for 'filename' and 'hash'.
* The hash is the primary key in the database. If omitted it will be recalculated from the filename.
*/
function boost_remove_db($files) {
$hashes = array();
foreach ($files as $file) {
if (empty($file['hash'])) {
$file['hash'] = md5($file['filename']);
}
$hashes[] = $file['hash'];
}
if ($hashes) {
boost_db_multi_delete_in('boost_cache', 'hash', "'%s'", $hashes);
}
}
/**
* Puts boost info into database.
*
* @param $expire
* Expiration time
* @param $lifetime
* Default lifetime
* @param $push
* Pre-cache this file
* @param $router_item
* Array containing page_callback, page_type & page_id.
*/
function boost_put_settings_db($lifetime, $push, $router_item, $scope) {
switch ($scope) {
case 0:
db_query("UPDATE {boost_cache_settings} SET lifetime = %d, push = %d WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '%s'", $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, $router_item['page_id']);
if (!db_affected_rows()) {
@db_query("INSERT INTO {boost_cache_settings} (lifetime, push, page_callback, page_type, base_dir, page_id) VALUES (%d, %d, '%s', '%s', '%s', '%s')", $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, $router_item['page_id']);
}
break;
case 1:
db_query("UPDATE {boost_cache_settings} SET lifetime = %d, push = %d WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '0'", $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH);
if (!db_affected_rows()) {
@db_query("INSERT INTO {boost_cache_settings} (lifetime, push, page_callback, page_type, base_dir, page_id) VALUES (%d, %d, '%s', '%s', '%s', '%s')", $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, 0);
}
break;
case 2:
db_query("UPDATE {boost_cache_settings} SET lifetime = %d, push = %d WHERE page_callback = '%s' AND page_type = '0' AND base_dir = '%s' AND page_id = '0'", $lifetime, $push, $router_item['page_callback'], BOOST_FILE_PATH);
if (!db_affected_rows()) {
@db_query("INSERT INTO {boost_cache_settings} (lifetime, push, page_callback, page_type, base_dir, page_id) VALUES (%d, %d, '%s', '%s', '%s', '%s')", $lifetime, $push, $router_item['page_callback'], '0', BOOST_FILE_PATH, 0);
}
break;
}
}
/**
* Removes info from boost database.
*
* @param $csid
* Cache Settings primary ID
*/
function boost_remove_settings_db($csid) {
db_query("DELETE FROM {boost_cache_settings} WHERE csid = %d", $csid);
}
/**
* Sets per page configuration.
*
* @param $lifetime
* Default lifetime
* @param $push
* Pre-cache this file
* @param $scope
* At what level does this effect cache expiration
*/
function boost_set_db_page_settings($lifetime, $push, $scope) {
$router_item = _boost_get_menu_router();
$filename = boost_file_path($GLOBALS['_boost_path']);
$info = boost_get_db($filename);
if (!$info) {
$info['expire'] = 0;
}
elseif ($lifetime == -1) {
$info['expire'] = $info['expire'] - $info['lifetime'] + BOOST_CACHE_LIFETIME;
}
elseif ($info['lifetime'] == -1) {
$info['expire'] = $info['expire'] - BOOST_CACHE_LIFETIME + $lifetime;
}
elseif ($info['lifetime'] != $lifetime) {
$info['expire'] = $info['expire'] - $info['lifetime'] + $lifetime;
}
// Clear old files so they acquire the new settings.
$data = array();
switch ($scope) {
case 0:
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => $router_item['page_callback'], 'page_type' => $router_item['page_type'], 'page_id' => $router_item['page_id']);
break;
case 1:
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => $router_item['page_callback'], 'page_type' => $router_item['page_type']);
break;
case 2:
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => $router_item['page_callback']);
break;
}
boost_put_settings_db($lifetime, $push, $router_item, $scope);
$count = 0;
if ($data) {
$count += boost_cache_expire_router($data);
}
return $count;
}
/**
* Gets boost info from cache database.
*
* @param $filename
* Filename to be looked up in the database
*/
function boost_get_db($filename) {
$hash = md5($filename);
return db_fetch_array(db_query("SELECT * FROM {boost_cache} WHERE hash = '%s'", $hash));
}
/**
* Gets boost settings from cache settings database.
*
* @param $router_item
* Array containing page_callback, page_type & page_id.
*/
function boost_get_settings_db($router_item) {
$settings = array();
// Get a more exact match first
if (BOOST_FLUSH_ALL_MULTISITE) {
$settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $router_item['page_callback'], $router_item['page_type'], $router_item['page_id'], 0, 1));
// Get for the content type
$settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $router_item['page_callback'], $router_item['page_type'], 0, 0, 1));
// Finally get the content container (node, view, term, ect...)
$settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $router_item['page_callback'], 0, 0, 0, 1));
}
else {
$settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '%s'", $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, $router_item['page_id'], 0, 1));
// Get for the content type
$settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '%s'", $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, 0, 0, 1));
// Finally get the content container (node, view, term, ect...)
$settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '%s'", $router_item['page_callback'], 0, BOOST_FILE_PATH, 0, 0, 1));
}
return $settings;
}
/**
* Checks various timestamps in the database.
*
* @param $set_max
* bool Allow one to read and not set the max_timestamp.
*
* @return bool
* Returns TRUE if the site has changed since the last time this function was called.
*/
function boost_has_site_changed($set_max = FALSE) {
// Make sure database has been indexed.
if (boost_drupal_get_installed_schema_version('boost') <= 6120) {
return FALSE;
}
// Index any new tables
$ret = array();
_boost_index_exists($ret, 'node_revisions', 'timestamp');
_boost_index_exists($ret, 'files', 'timestamp');
_boost_index_exists($ret, 'comments', 'timestamp');
_boost_index_exists($ret, 'node', 'changed');
_boost_index_exists($ret, 'node_comment_statistics', 'last_comment_timestamp');
_boost_index_exists($ret, 'votingapi_vote', 'timestamp');
// Get timestamps from the database
$node_revisions = boost_get_time('node_revisions', 'timestamp');
//$history = boost_get_time('history', 'timestamp');
$files = boost_get_time('files', 'timestamp');
$comments = boost_get_time('comments', 'timestamp');
$voteapi_vote = boost_get_time('votingapi_vote', 'timestamp');
$node = boost_get_time('node', 'changed');
$last_comment_timestamp = boost_get_time('node_comment_statistics', 'last_comment_timestamp');
$max = max($node_revisions, $files, $comments, $node, $last_comment_timestamp, $voteapi_vote);
if ($max != variable_get('boost_max_timestamp', BOOST_TIME)) {
if ($set_max) {
variable_set('boost_max_timestamp', (int)$max);
}
return TRUE;
}
else {
return FALSE;
}
}
/**
* Returns the currently installed schema version for a module.
*
* @see drupal_get_installed_schema_version()
*
* @param $module
* A module name.
* @param $reset
* Set to TRUE after modifying the system table.
* @param $array
* Set to TRUE if you want to get information about all modules in the
* system.
* @return
* The currently installed schema version.
*/
function boost_drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
static $versions = array();
if ($reset) {
$versions = array();
}
if (!$versions) {
$versions = array();
$result = db_query("SELECT name, schema_version FROM {system} WHERE type = '%s'", 'module');
while ($row = db_fetch_object($result)) {
$versions[$row->name] = $row->schema_version;
}
}
return $array ? $versions : $versions[$module];
}
/**
* Checks various timestamps in the database.
*
* @param $table
* Database table name
* @param $column
* Column containing the time stamp
* @return int
* Returns largest time in the table.
*/
function boost_get_time($table, $column) {
if (db_table_exists($table)) {
return (int)db_result(db_query_range("SELECT %s FROM {%s} ORDER BY %s DESC", $column, $table, $column, 0, 1));
}
else {
return 0;
}
}
/**
* Writes data to filename in an atomic operation thats compatible with older
* versions of php (php < 5.2.4 file_put_contents() doesn't lock correctly).
*
* @param $filename
* Name of file to be written
* @param $buffer
* Contents of file
*/
function boost_cache_write($filename, $buffer) {
$filenames = boost_get_all_filenames($filename);
foreach ($filenames as $key => $values) {
if ($key == 'gzip') {
$data = gzencode($buffer, 9);
}
else {
$data = $buffer;
}
foreach ($values as $filename) {
if (!_boost_mkdir_p(dirname($filename))) {
if (BOOST_VERBOSE >= 3) {
watchdog('boost', 'Unable to create directory: %dir Group ID: %gid User ID: %uid Current script owner: %user ', array('%dir' => dirname($filename), '%gid' => getmygid(), '%uid' => getmyuid(), '%user' => get_current_user()), WATCHDOG_WARNING);
}
}
$tempfile = $filename . getmypid();
$oldfile = $tempfile . 'old';
if (@file_put_contents($tempfile, $data) === FALSE) {
if (BOOST_VERBOSE >= 3) {
watchdog('boost', 'Unable to write temp file: %file Group ID: %gid User ID: %uid Current script owner: %user ', array('%file' => $tempfile, '%gid' => getmygid(), '%uid' => getmyuid(), '%user' => get_current_user()), WATCHDOG_WARNING);
}
return FALSE;
}
else {
if (is_numeric(BOOST_PERMISSIONS_FILE)) {
@chmod($tempfile, octdec(BOOST_PERMISSIONS_FILE));
}
// Erase old file
if (BOOST_OVERWRITE_FILE) {
// Keep old file around just in case rename fails
@rename($filename, $oldfile);
}
// Put temp file in its final location
if (@rename($tempfile, $filename) === FALSE) {
// If rename failed then remove new file and put old file back
@unlink($tempfile);
@rename($oldfile, $filename);
if (BOOST_VERBOSE >= 5) {
watchdog('boost', 'Unable to rename file: %temp to %file Group ID: %gid User ID: %uid Current script owner: %user ', array('%temp' => $tempfile, '%file' => $filename, '%gid' => getmygid(), '%uid' => getmyuid(), '%user' => get_current_user()), WATCHDOG_WARNING);
}
return FALSE;
}
elseif (BOOST_OVERWRITE_FILE) {
// Rename is sucessful; remove old file
@unlink($oldfile);
}
}
}
}
return TRUE;
}
/**
* Returns the full directory path to the static file cache directory.
*
* @param $host
* Host name. Example: example.com
* @param $absolute
* Give path from system root if true. If false give path from web root.
* @param $root_dir
* Cache directory
* @param $normal_dir
* Normal directory
*/
function boost_cache_directory($host = NULL, $absolute = TRUE, $root_dir = NULL, $normal_dir = NULL) {
global $base_url;
$temp_base_url = $base_url;
$root_dir = is_null($root_dir) ? BOOST_ROOT_CACHE_DIR : $root_dir;
$normal_dir = is_null($normal_dir) ? BOOST_NORMAL_DIR : $normal_dir;
if ($temp_base_url == "http://") {
if (!BOOST_MULTISITE_SINGLE_DB) {
$temp_base_url = $temp_base_url . str_replace($root_dir . '/', '', variable_get('boost_file_path', boost_cache_directory(NULL, FALSE)));
}
elseif (BOOST_NORMAL_DIR != '' && db_result(db_query("SELECT count(DISTINCT base_dir) FROM {boost_cache}")) == 1) {
$temp_base_url = db_result(db_query("SELECT DISTINCT base_dir FROM {boost_cache}"));
$temp_base_url = $temp_base_url . str_replace($root_dir . '/', '', $base_dir);
$temp_base_url = $temp_base_url . str_replace($normal_dir . '/', '', $base_dir);
}
}
if (@parse_url($temp_base_url) === FALSE) {
//Error has been caught here
if (BOOST_VERBOSE >= 1) {
watchdog('boost', 'base_url is not set in your settings.php file. Please read Important Notes in boosts README.txt file.', array(), WATCHDOG_NOTICE);
}
return FALSE;
}
$parts = parse_url($temp_base_url);
$host = !empty($host) ? $host : $parts['host'];
$host = strtolower($host);
$parts['path'] = isset($parts['path']) ? $parts['path'] : '/';
$subdir = implode('/', array_filter(explode('/', (!empty($base_path)) ? $base_path : $parts['path'])));
return implode('/', !$absolute ? array_filter(array($root_dir, $normal_dir, $host, $subdir)) : array_filter(array(getcwd(), $root_dir, $normal_dir, $host, $subdir)));
}
/**
* Returns the static file path for a Drupal page.
*
* @param $path
* path to convert to boost's file naming convention
* @param $query
* add query to path
* @param $extension
* add extension to end of filename
* @param $file_path
* Optional: BOOST_FILE_PATH
*
* $path = $GLOBALS['_boost_path'] most of the time
*/
function boost_file_path($path, $query = TRUE, $extension = BOOST_FILE_EXTENSION, $file_path = NULL) {
//handling of url variables
if ($GLOBALS['_boost_query'] != BOOST_CHAR) {
if ($query) {
$path .= $GLOBALS['_boost_query'];
}
else {
$path .= BOOST_CHAR;
}
}
else {
$path .= $GLOBALS['_boost_query'];
}
// Under no circumstances should the incoming path contain '..' or null
// bytes; we also limit the maximum directory nesting depth of the path
if ( strpos($path, '..') !== FALSE
|| strpos($path, "\0") !== FALSE
|| count(explode('/', $path)) > BOOST_MAX_PATH_DEPTH
) {
return FALSE;
}
$file_path = is_null($file_path) ? BOOST_FILE_PATH : $file_path;
return implode('/', array($file_path, $path . (is_null($extension) ? '' : $extension)));
}
/**
* Returns the time it took to generate this cached page.
* @param $filename
* Name of cached file
*/
function boost_get_generation_time($filename) {
$boost_db = boost_get_db($filename);
return $boost_db['timer'] != 0 ? $boost_db['timer']/1000.0 : FALSE;
}
/**
* Returns the age of a cached file, measured in seconds since it was last
* updated.
* @param $filename
* Name of cached file
*/
function boost_file_get_age($filename) {
return BOOST_TIME - filemtime($filename);
}
function boost_db_get_age($filename) {
$boost_db = boost_get_db($filename);
return $boost_db['expire'] != 0 ? $boost_db['expire'] : FALSE;
}
/**
* Returns the remaining time-to-live for a cached file, measured in
* seconds.
* @param $filename
* Name of cached file
*/
function boost_file_get_ttl($filename) {
return BOOST_CACHE_LIFETIME - boost_file_get_age($filename);
}
function boost_db_get_ttl($filename) {
return boost_db_get_age($filename) - BOOST_TIME;
}
function boost_db_get_cache_age($filename) {
$boost_db = boost_get_db($filename);
$lifetime = BOOST_CACHE_LIFETIME;
if ($boost_db['lifetime'] != -1) {
$lifetime = $boost_db['lifetime'];
}
$time = BOOST_TIME - ($boost_db['expire'] - $lifetime);
return $time;
}
/**
* Determines whether a cached file has expired, i.e. whether its age
* exceeds the maximum cache lifetime as defined by Drupal's system
* settings.
* @param $filename
* Name of cached file
*/
function boost_file_is_expired($filename) {
return boost_file_get_age($filename) > BOOST_CACHE_LIFETIME;
}
function boost_db_is_expired($filename) {
return boost_db_get_age($filename) < BOOST_TIME;
}
/**
* Sets a special cookie preventing authenticated users getting served pages
* from the static page cache.
*
* @param $uid
* User ID Number
* @param $expires
* Expiration time
*/
function boost_set_cookie($uid, $expires = NULL) {
if (!$expires) {
// Let the old way still work, in case user object was passed
$uid = is_object($uid) ? $uid->uid : $uid;
$expires = ini_get('session.cookie_lifetime');
$expires = (!empty($expires) && is_numeric($expires)) ? BOOST_TIME + (int)$expires : 0;
setcookie(BOOST_COOKIE, strval($uid), $expires, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure') == '1');
}
else {
setcookie(BOOST_COOKIE, '0', $expires, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure') == '1');
}
$GLOBALS['_boost_cache_this'] = FALSE;
}
/**
* Retrieve a specific URL redirect from the database.
* http://drupal.org/node/451790
*
* @param $where
* Array containing 'redirect' => $path
*/
function boost_path_redirect_load($where = array(), $args = array(), $sort = array()) {
$redirects = array();
if (is_numeric($where)) {
$where = array('rid' => $where);
}
foreach ($where as $key => $value) {
if (is_string($key)) {
$args[] = $value;
$where[$key] = $key .' = '. (is_numeric($value) ? '%d' : "'%s'");
}
}
if ($where && $args) {
$sql = "SELECT * FROM {path_redirect} WHERE ". implode(' AND ', $where);
if ($sort) {
$sql .= ' ORDER BY '. implode(' ,', $sort);
}
$result = db_query($sql, $args);
while ($redirect = db_fetch_array($result)) {
$redirects[] = $redirect;
}
return $redirects;
}
}
/**
* Cache css and or js files.
*
* Parse the html file so we get all css/js files. drupal_get_js/css isn't 100%.
*
* @param $buffer
* String containing documents html.
*/
function boost_cache_css_js_files($buffer) {
if (BOOST_CACHE_CSS) {
// Extract external css files from html document
$css_files = explode(' $value) {
// Extract css filename
$temp = explode(base_path(), array_pop(explode('//', array_pop(explode('href="', array_shift(explode('" />', $value)))))));
array_shift($temp);
$css_files[$key] = array_shift(explode('"', array_shift(explode('?', implode('/', $temp)))));
}
_boost_copy_css_files($css_files);
}
if (BOOST_CACHE_JS) {
$js_files = explode('