';
}
break;
case 'admin/settings/performance/boost':
return '
' . t('') . '
'; // TODO: add help text.
}
//hack to get drupal_get_messages before they are destroyed.
$GLOBALS['_boost_message_count'] = count(drupal_get_messages(NULL, FALSE));
}
/**
* Implementation of hook_init(). Performs page setup tasks if page not cached.
*/
function boost_init() {
// Disable all caches when nocache is set
if (isset($_GET['nocache'])) {
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
$GLOBALS['_boost_cache_this'] = FALSE;
return;
}
global $user, $base_path;
//set variables
$GLOBALS['_boost_router_item'] = _boost_get_menu_router();
$GLOBALS['_boost_path'] = $_REQUEST['q'];
// Make the proper filename for our query
$GLOBALS['_boost_query'] = BOOST_CHAR;
foreach ($_GET as $key => $val) {
if ($key != 'q' && $key != 'destination') {
$GLOBALS['_boost_query'] .= (($GLOBALS['_boost_query'] == BOOST_CHAR) ? '' : '&') . $key . '=' . $val;
}
}
if (!empty($user->uid)) {
boost_set_cookie($user);
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['SERVER_SOFTWARE'] === 'PHP CLI'
|| !BOOST_ENABLED
|| !boost_is_cacheable($GLOBALS['_boost_path'])
|| $GLOBALS['_boost_router_item']['page_callback'] == 'search404_page'
) {
$GLOBALS['_boost_cache_this'] = FALSE;
return;
}
// We only generate cached pages for anonymous visitors.
if (empty($user->uid)) {
if (BOOST_ENABLED != CACHE_AGGRESSIVE) {
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
}
$GLOBALS['_boost_cache_this'] = TRUE;
register_shutdown_function('_boost_ob_handler');
ob_start();
}
}
/**
* 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.
*/
function boost_exit($destination = NULL) {
// Check that hook_exit() was invoked by drupal_goto() for a POST request:
if (!empty($destination) && $_SERVER['REQUEST_METHOD'] == 'POST') {
// Check that we're dealing with an anonymous visitor. and that some
// session messages have actually been set during this page request:
global $user;
if (empty($user->uid) && ($messages = drupal_set_message())) {
// FIXME: call any remaining exit hooks since we're about to terminate?
$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:
exit(header('Location: ' . $destination));
}
}
}
/**
* 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'),
);
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
&& floatval(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 (!BOOST_ENABLED) {
return;
}
$expire = TRUE;
if (BOOST_CHECK_BEFORE_CRON_EXPIRE) {
$expire = boost_has_site_changed();
}
// Expire old content
if (!BOOST_LOOPBACK_BYPASS && variable_get('boost_expire_cron', TRUE) && $expire && boost_cache_db_expire()) {
if (BOOST_VERBOSE >= 5) {
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 (!BOOST_ENABLED) return;
switch ($op) {
case 'insert':
case 'update':
// Expire the relevant node page from the static page cache to prevent serving stale content:
if (!empty($comment['nid'])) {
$node = node_load($comment['nid']);
boost_expire_node($node);
}
break;
case 'publish':
case 'unpublish':
case 'delete':
if (!empty($comment->nid)) {
$node = node_load($comment->nid);
boost_expire_node($node);
}
break;
}
}
/**
* Implementation of hook_nodeapi(). Acts on nodes defined by other modules.
*/
function boost_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
if (!BOOST_ENABLED) return;
switch ($op) {
case 'insert':
case 'update':
case 'delete':
boost_expire_node($node);
break;
}
}
/**
* Implementation of hook_votingapi_insert().
*
* @param $votes
* array of votes
*/
function boost_votingapi_insert($votes) {
if (!BOOST_ENABLED) return;
foreach($votes as $vote) {
$node = node_load($vote['content_id']);
boost_expire_node($node);
}
}
/**
* Implementation of hook_votingapi_delete().
*
* @param $votes
* array of votes
*/
function boost_votingapi_delete($votes) {
if (!BOOST_ENABLED) return;
foreach($votes as $vote) {
$node = node_load($vote['content_id']);
boost_expire_node($node);
}
}
/**
* Expires a node from the cache.
*
* @param $node
* node object
*/
function boost_expire_node($node) {
// Expire all relevant node pages from the static page cache to prevent serving stale content:
if (!empty($node->nid)) {
if ($node->promote == 1) {
boost_cache_expire_derivative('');
}
boost_cache_expire_derivative('node/' . $node->nid, TRUE);
}
// get terms and flush their page
if (module_exists('taxonomy') && BOOST_FLUSH_NODE_TERMS) {
$tids = boost_taxonomy_node_get_tids($node->nid);
$filenames = array();
foreach($tids as $tid) {
$filenames = array_merge($filenames, boost_get_db_term($tid));
}
foreach($filenames as $filename) {
boost_cache_kill($filename);
}
}
}
/**
* 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;
}
return $tids;
}
/**
* Implementation of hook_taxonomy(). Acts on taxonomy changes.
*/
function boost_taxonomy($op, $type, $term = NULL) {
if (!BOOST_ENABLED) 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) {
if (!BOOST_ENABLED) return;
global $user;
switch ($op) {
case 'login':
// Set a special cookie to prevent authenticated users getting served
// pages from the static page cache.
boost_set_cookie($user);
break;
case 'logout':
boost_set_cookie($user, BOOST_TIME - 86400);
break;
case 'insert':
// TODO: create user-specific cache directory.
break;
case 'delete':
// Expire the relevant user page from the static page cache to prevent serving stale content:
if (!empty($account->uid)) {
boost_cache_expire_derivative('user/' . $account->uid);
}
// TODO: recursively delete user-specific cache directory.
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'),
'region' => 'right',
'weight' => 10,
'cache' => BLOCK_NO_CACHE,
),
'config' => array(
'info' => t('Boost: Pages cache configuration'),
'region' => 'right',
'weight' => 10,
'cache' => BLOCK_NO_CACHE,
),
'stats' => array(
'info' => t('Boost: AJAX core statistics'),
'region' => 'right',
'weight' => 10,
'cache' => BLOCK_NO_CACHE,
),
);
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;
}
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']);
}
case 'view':
$block = array();
switch ($delta) {
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 = t('This page is being served live to anonymous visitors, as it is not currently in the static page cache.');
if (boost_is_cached($GLOBALS['_boost_path'])) {
$ttl = boost_db_get_ttl(boost_file_path($GLOBALS['_boost_path']));
$output = t('This page is being served to anonymous visitors from the static page cache.') . ' ';
$output .= t($ttl < 0 ?
'The cached copy expired %interval ago.' :
'The cached copy will expire in %interval.',
array('%interval' => format_interval(abs($ttl))));
$output .= drupal_get_form('boost_block_flush_form');
}
$error = FALSE;
if (function_exists('error_get_last')) {
$error = error_get_last();
}
$drupal_msg = max(count(drupal_get_messages(NULL, FALSE)), $GLOBALS['_boost_message_count']);
if ((BOOST_HALT_ON_ERRORS || BOOST_HALT_ON_MESSAGES) && ($error || $drupal_msg != 0)) {
$output = t('There are php errors or drupal messages on this page, preventing boost from caching.');
if ($error) {
$output .= t(' ERROR: