Newer
Older
Arto Bendiken
committed
<?php
// $Id$
/**
* @file
Mike Carper
committed
* Provides static file caching for Drupal text output. Pages, Feeds, ect...
Arto Bendiken
committed
*/
//////////////////////////////////////////////////////////////////////////////
// Module settings
Arto Bendiken
committed
define('BOOST_MAX_TIMESTAMP', variable_get('boost_max_timestamp', BOOST_TIME));
define('BOOST_ENABLED', variable_get('boost_enabled', CACHE_NORMAL));
define('BOOST_GZIP', variable_get('page_compression', TRUE));
// This cookie is set for all authenticated users, so that they can be
// excluded from caching (or in the future get a user-specific cached page):
define('BOOST_COOKIE', variable_get('boost_cookie', 'DRUPAL_UID'));
// This line is appended to the generated static files; it is very useful
// for troubleshooting (e.g. determining whether one got the dynamic or
// static version):
define('BOOST_BANNER', variable_get('boost_banner', "Page cached by Boost @ %cached_at, expires @ %expires_at"));
// Caching Options
define('BOOST_CACHE_LIFETIME', variable_get('boost_cache_lifetime', 3600));
define('BOOST_CACHE_XML_LIFETIME', variable_get('boost_cache_xml_lifetime', 3600));
define('BOOST_CACHE_JSON_LIFETIME', variable_get('boost_cache_json_lifetime', 3600));
define('BOOST_CACHE_QUERY', variable_get('boost_cache_query', TRUE));
define('BOOST_CACHE_HTML', variable_get('boost_cache_html', TRUE));
define('BOOST_CACHE_XML', variable_get('boost_cache_xml', FALSE));
define('BOOST_CACHE_JSON', variable_get('boost_cache_json', FALSE));
define('BOOST_CACHE_CSS', variable_get('boost_cache_css', TRUE));
define('BOOST_CACHE_JS', variable_get('boost_cache_js', TRUE));
define('BOOST_CACHEABILITY_OPTION', variable_get('boost_cacheability_option', 0));
define('BOOST_CACHEABILITY_PAGES', variable_get('boost_cacheability_pages', ''));
define('BOOST_ROOT_CACHE_DIR', variable_get('boost_root_cache_dir', 'cache'));
define('BOOST_MULTISITE_SINGLE_DB', variable_get('boost_multisite_single_db', TRUE));
define('BOOST_NORMAL_DIR', variable_get('boost_normal_dir', 'normal'));
define('BOOST_GZIP_DIR', variable_get('boost_gzip_dir', 'normal'));
define('BOOST_PERM_NORMAL_DIR', variable_get('boost_perm_normal_dir', 'perm'));
define('BOOST_PERM_GZ_DIR', variable_get('boost_perm_gz_dir', 'perm'));
define('BOOST_CHAR', variable_get('boost_char', '_'));
define('BOOST_PERM_CHAR', variable_get('boost_perm_char', '_'));
define('BOOST_HOST', variable_get('boost_host', ''));
Arto Bendiken
committed
define('BOOST_FILE_EXTENSION', variable_get('boost_file_extension', '.html'));
define('BOOST_XML_EXTENSION', variable_get('boost_xml_extension', '.xml'));
define('BOOST_JSON_EXTENSION', variable_get('boost_json_extension', '.json'));
define('BOOST_CSS_EXTENSION', variable_get('boost_css_extension', '.css'));
define('BOOST_JS_EXTENSION', variable_get('boost_js_extension', '.js'));
define('BOOST_GZIP_EXTENSION', variable_get('boost_gzip_extension', '.gz'));
define('BOOST_ROOT_FILE', variable_get('boost_root_file', '.boost'));
define('BOOST_MAX_PATH_DEPTH', 10);
// Advanced Settings
define('BOOST_CHECK_BEFORE_CRON_EXPIRE', variable_get('boost_check_before_cron_expire', FALSE));
Arto Bendiken
committed
define('BOOST_PRE_PROCESS_FUNCTION', variable_get('boost_pre_process_function', ''));
define('BOOST_FLUSH_ALL_MULTISITE', variable_get('boost_flush_all_multisite', FALSE));
define('BOOST_ONLY_ASCII_PATH', variable_get('boost_only_ascii_path', TRUE));
define('BOOST_ASYNCHRONOUS_OUTPUT', variable_get('boost_asynchronous_output', TRUE));
define('BOOST_FLUSH_DIR', variable_get('boost_flush_dir', FALSE));
define('BOOST_FLUSH_CCK_REFERENCES', variable_get('boost_flush_cck_references', TRUE));
define('BOOST_FLUSH_NODE_TERMS', variable_get('boost_flush_node_terms', TRUE));
define('BOOST_FLUSH_MENU_ITEMS', variable_get('boost_flush_menu_items', 0));
define('BOOST_FLUSH_VIEWS', variable_get('boost_flush_views', TRUE));
define('BOOST_FLUSH_VIEWS_INSERT', variable_get('boost_flush_views_insert', TRUE));
define('BOOST_CLEAR_CACHE_OFFLINE', variable_get('boost_clear_cache_offline', FALSE));
define('BOOST_OVERWRITE_FILE', variable_get('boost_overwrite_file', FALSE));
Mike Carper
committed
define('BOOST_HALT_ON_ERRORS', variable_get('boost_halt_on_errors', FALSE));
Mike Carper
committed
define('BOOST_HALT_ON_MESSAGES', variable_get('boost_halt_on_messages', TRUE));
define('BOOST_DISABLE_CLEAN_URL', variable_get('boost_disable_clean_url', FALSE));
Mike Carper
committed
define('BOOST_AGGRESSIVE_GZIP', BOOST_GZIP ? variable_get('boost_aggressive_gzip', TRUE) : FALSE);
define('BOOST_PERMISSIONS_FILE', variable_get('boost_permissions_file', ''));
define('BOOST_PERMISSIONS_DIR', variable_get('boost_permissions_dir', ''));
define('BOOST_EXPIRE_NO_FLUSH', variable_get('boost_expire_no_flush', FALSE));
define('BOOST_VERBOSE', variable_get('boost_verbose', 5));
define('BOOST_IGNORE_SAFE_WARNING', variable_get('boost_ignore_safe_warning', FALSE));
define('BOOST_IGNORE_SUBDIR_LIMIT', variable_get('boost_ignore_subdir_limit', TRUE));
define('BOOST_NO_DATABASE', variable_get('boost_no_database', FALSE));
define('BOOST_CRAWL_ON_CRON', variable_get('boost_crawl_on_cron', FALSE));
define('BOOST_LOOPBACK_BYPASS', BOOST_OVERWRITE_FILE && BOOST_CRAWL_ON_CRON ? variable_get('boost_loopback_bypass', FALSE) : FALSE);
define('BOOST_PUSH_HTML', variable_get('boost_push_html', TRUE));
define('BOOST_PUSH_XML', variable_get('boost_push_xml', TRUE));
define('BOOST_PUSH_JSON', variable_get('boost_push_json', TRUE));
define('BOOST_CRAWLER_THROTTLE', variable_get('boost_crawler_throttle', 0));
define('BOOST_CRAWLER_THREADS', variable_get('boost_crawler_threads', 2));
define('BOOST_CRAWL_URL_ALIAS', variable_get('boost_crawl_url_alias', FALSE));
Arto Bendiken
committed
// Requires Boost Functions or global scope, Define These Last
global $base_url;
define('BOOST_CRAWLER_SELF', $base_url . '/' . 'boost-crawler?nocache=1&key=' . variable_get('boost_crawler_key', FALSE));
define('BOOST_FILE_PATH', BOOST_MULTISITE_SINGLE_DB ? boost_cache_directory(NULL, FALSE) : variable_get('boost_file_path', boost_cache_directory(BOOST_HOST, FALSE)));
define('BOOST_GZIP_FILE_PATH', implode('/', array_filter(explode('/', str_replace(BOOST_ROOT_CACHE_DIR . '/' . BOOST_NORMAL_DIR, BOOST_ROOT_CACHE_DIR . '/' . BOOST_GZIP_DIR . '/', BOOST_FILE_PATH)))));
define('BOOST_PERM_GZIP_FILE_PATH', implode('/', array_filter(explode('/', str_replace(BOOST_ROOT_CACHE_DIR . '/' . BOOST_NORMAL_DIR, BOOST_ROOT_CACHE_DIR . '/' . BOOST_PERM_GZ_DIR . '/', BOOST_FILE_PATH)))));
define('BOOST_PERM_FILE_PATH', implode('/', array_filter(explode('/', str_replace(BOOST_ROOT_CACHE_DIR . '/' . BOOST_NORMAL_DIR, BOOST_ROOT_CACHE_DIR . '/' . BOOST_PERM_NORMAL_DIR . '/', BOOST_FILE_PATH)))));
define('BOOST_CRAWLER_BATCH_SIZE', variable_get('boost_crawler_batch_size', min(15, ini_get('max_execution_time')/(2 * boost_average_time()))));
define('BOOST_MAX_THREAD_TIME', max(300, 2 * boost_average_time() * BOOST_CRAWLER_THREADS * BOOST_CRAWLER_BATCH_SIZE));
Arto Bendiken
committed
Mike Carper
committed
//////////////////////////////////////////////////////////////////////////////
// Global variables
//$GLOBALS['_boost_path'] = '';
//$GLOBALS['_boost_query'] = '';
//$GLOBALS['_boost_message_count'] = '';
//$GLOBALS['_boost_cache_this'] = '';
//$GLOBALS['_boost_max_execution_time'] = '';
//$GLOBALS['_boost_output_buffering'] = '';
//$GLOBALS['_boost_default_socket_timeout'] = '';
//$GLOBALS['_boost_router_item'] = '';
//$GLOBALS['_boost_relationships'] = '';
Mike Carper
committed
Arto Bendiken
committed
//////////////////////////////////////////////////////////////////////////////
// Core API hooks
Arto Bendiken
committed
/**
* Implementation of hook_help(). Provides online user help.
*/
function boost_help($path, $arg) {
switch ($path) {
Arto Bendiken
committed
case 'admin/help#boost':
if (file_exists($file = drupal_get_path('module', 'boost') . '/README.txt')) {
Arto Bendiken
committed
return '<pre>' . implode("\n", array_slice(explode("\n", @file_get_contents($file)), 2)) . '</pre>';
Arto Bendiken
committed
break;
case 'admin/settings/performance/boost':
return '<p>' . t('') . '</p>'; // TODO: add help text.
Arto Bendiken
committed
}
Mike Carper
committed
//hack to get drupal_get_messages before they are destroyed.
$GLOBALS['_boost_message_count'] = count(drupal_get_messages(NULL, FALSE));
Arto Bendiken
committed
}
/**
* 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) {
if (!is_null($view) && $GLOBALS['_boost_cache_this'] && !BOOST_NO_DATABASE) {
foreach ($view->result as $item) {
$node = node_load($item->nid);
$GLOBALS['_boost_relationships'][] = array('child_page_callback' => 'node', 'child_page_type' => $node->type, 'child_page_id' => $item->nid);
}
}
}
Arto Bendiken
committed
/**
* Implementation of hook_init(). Performs page setup tasks if page not cached.
Arto Bendiken
committed
*/
function boost_init() {
Mike Carper
committed
global $user, $base_path;
// Disable all caches when nocache is set
if (isset($_GET['nocache'])) {
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
$GLOBALS['_boost_cache_this'] = FALSE;
return;
}
Mike Carper
committed
// 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 != '' && $_REQUEST['q'] == '' && !stristr($path, '.php') && isset($_SERVER['REDIRECT_STATUS']) && $_SERVER['REDIRECT_STATUS'] == 404) {
Mike Carper
committed
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
$GLOBALS['_boost_cache_this'] = FALSE;
if (BOOST_VERBOSE >= 7) {
watchdog('boost', '404 recieved from server via redirect, going to send a 404. Info: !output', array('!output' => boost_print_r($_SERVER, TRUE, TRUE)));
}
Mike Carper
committed
drupal_not_found();
return;
}
//set variables
$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;
Arto Bendiken
committed
}
}
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'])
) {
Mike Carper
committed
$GLOBALS['_boost_cache_this'] = FALSE;
return;
}
// We only generate cached pages for anonymous visitors.
if (empty($user->uid)) {
Mike Carper
committed
if (BOOST_ENABLED != CACHE_AGGRESSIVE) {
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
}
Mike Carper
committed
$GLOBALS['_boost_cache_this'] = TRUE;
Mike Carper
committed
register_shutdown_function('_boost_ob_handler');
ob_start();
}
Arto Bendiken
committed
}
Arto Bendiken
committed
/**
* 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())) {
Mike Carper
committed
// 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);
Mike Carper
committed
// Do what drupal_goto() would do if we were to return to it:
exit(header('Location: ' . $destination));
Arto Bendiken
committed
}
}
/**
* 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'),
$items['boost-crawler'] = array(
Mike Carper
committed
'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;
}
Arto Bendiken
committed
/**
* Implementation of hook_form_alter(). Performs alterations before a form
* is rendered.
*/
function boost_form_alter(&$form, $form_state, $form_id) {
Arto Bendiken
committed
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';
Arto Bendiken
committed
$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';
Mike Carper
committed
// Added below due to this bug: http://drupal.org/node/276615
Mike Carper
committed
if ( variable_get('preprocess_css', FALSE)==TRUE
&& version_compare(VERSION, 6.13, '<=')
Mike Carper
committed
&& boost_cache_clear_all()
) {
drupal_set_message(t('Boost: Static page cache cleared. See <a href="http://drupal.org/node/276615">http://drupal.org/node/276615</a> for reason why (core bug that is fixed in 6.14+).'), 'warning');
Mike Carper
committed
}
Arto Bendiken
committed
}
}
/**
* Implementation of hook_cron(). Performs periodic actions.
*/
function boost_cron() {
Mike Carper
committed
if (!BOOST_ENABLED) {
return;
}
Arto Bendiken
committed
$expire = TRUE;
if (BOOST_CHECK_BEFORE_CRON_EXPIRE) {
$expire = boost_has_site_changed(TRUE);
}
Mike Carper
committed
// Expire old content
if (!BOOST_LOOPBACK_BYPASS && variable_get('boost_expire_cron', TRUE) && $expire && boost_cache_expire_all()) {
if (BOOST_VERBOSE >= 5) {
watchdog('boost', 'Expired stale files from static page cache.', array(), WATCHDOG_NOTICE);
}
Arto Bendiken
committed
}
Mike Carper
committed
// 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);
Arto Bendiken
committed
}
/*
* Implementation of hook_flush_caches(). Deletes all static files.
*/
function boost_flush_caches() {
Mike Carper
committed
if (variable_get('cron_semaphore', FALSE)==FALSE && (variable_get('preprocess_css', FALSE)==TRUE || variable_get('preprocess_js', FALSE)==TRUE)) {
Mike Carper
committed
boost_cache_clear_all();
}
return;
}
Arto Bendiken
committed
/**
* 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'])) {
Mike Carper
committed
$node = node_load($comment['nid']);
boost_expire_node($node, $comment['nid']);
Arto Bendiken
committed
break;
Arto Bendiken
committed
case 'publish':
case 'unpublish':
case 'delete':
if (!empty($comment->nid)) {
Mike Carper
committed
$node = node_load($comment->nid);
boost_expire_node($node, $comment->nid);
Arto Bendiken
committed
}
break;
Arto Bendiken
committed
}
}
/**
* Implementation of hook_nodeapi(). Acts on nodes defined by other modules.
*/
function boost_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
if (!BOOST_ENABLED) return;
Mike Carper
committed
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'node', 'page_id' => $node->nid);
Arto Bendiken
committed
switch ($op) {
case 'insert':
if (BOOST_FLUSH_VIEWS_INSERT && module_exists('views')) {
$GLOBALS['_boost_nid'] = $node->nid;
register_shutdown_function('_boost_view_insert');
}
Mike Carper
committed
boost_expire_node($node);
break;
Arto Bendiken
committed
case 'update':
Mike Carper
committed
boost_expire_node($node);
if (!$node->status) {
boost_cache_expire_router($data, TRUE, TRUE);
}
break;
Arto Bendiken
committed
case 'delete':
Mike Carper
committed
boost_expire_node($node);
Mike Carper
committed
boost_cache_expire_router($data, TRUE, TRUE);
Arto Bendiken
committed
break;
}
}
/**
* 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.
$result = db_query("SELECT * FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = 'view' AND expire > 0 AND expire <> 434966400", BOOST_FILE_PATH);
while ($boost = db_fetch_array($result)) {
$view = views_get_view($boost['page_type']);
$view->set_display($boost['page_id']);
$view->pre_execute();
$view->set_items_per_page(0);
$view->execute();
foreach ($view->result as $item) {
if ($item->nid == $GLOBALS['_boost_nid']) {
$hash = BOOST_FILE_PATH . 'view' . $boost['page_type'] . $boost['page_id'];
$data[$hash] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'view', 'page_type' => $boost['page_type'], 'page_id' => $boost['page_id']);
$number_hits++;
if ($data) {
$flushed = boost_cache_expire_router($data);
}
if (BOOST_VERBOSE >= 7) {
watchdog('boost', 'Debug: _boost_view_insert() <br />!views Views Searched; !hits of them contain the new node and where thus flushed. As a result of this !flushed pages where expired from the boost cache.', array('!views' => $num_views, '!hits' => $num_hits, '!flushed' => $flushed));
}
Mike Carper
committed
/**
* Implementation of hook_votingapi_insert().
*
* @param $votes
* array of votes
*/
function boost_votingapi_insert($votes) {
if (!BOOST_ENABLED) return;
foreach ($votes as $vote) {
Mike Carper
committed
$node = node_load($vote['content_id']);
boost_expire_node($node, $vote['content_id']);
Mike Carper
committed
}
}
/**
* Implementation of hook_votingapi_delete().
*
* @param $votes
* array of votes
*/
function boost_votingapi_delete($votes) {
if (!BOOST_ENABLED) return;
foreach ($votes as $vote) {
Mike Carper
committed
$node = node_load($vote['content_id']);
boost_expire_node($node, $vote['content_id']);
Mike Carper
committed
}
}
/**
* Expires a node from the cache; including related pages.
*
* Expires front page if promoted, taxonomy terms,
Mike Carper
committed
*
* @param $node
* node object
* @param $nid
* node id
Mike Carper
committed
*/
function boost_expire_node($node, $nid = 0) {
$data = array();
$paths = array();
if (empty($node->nid)) {
Mike Carper
committed
if (is_int($nid)) {
$node->nid = $nid;
}
else {
return FALSE;
}
}
if (BOOST_NO_DATABASE) {
Mike Carper
committed
$paths['node'] = 'node/' . $node->nid;
Mike Carper
committed
$data['node'] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'node', 'page_id' => $node->nid);
Mike Carper
committed
}
// If promoted to front page, expire front page
if ($node->promote == 1) {
Mike Carper
committed
$paths['front'] = '<front>';
Mike Carper
committed
if (module_exists('taxonomy') && BOOST_FLUSH_NODE_TERMS) {
$tids = boost_taxonomy_node_get_tids($node->nid);
$filenames = array();
foreach ($tids as $tid) {
Mike Carper
committed
if (is_int($tid)) {
if (BOOST_NO_DATABASE) {
$paths['term' . $tid] = 'taxonomy/term/' . $tid;
}
else {
$data['term' . $tid] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'taxonomy', 'page_id' => $tid);
}
Mike Carper
committed
}
}
// 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']);
if (BOOST_FLUSH_MENU_ITEMS == 1) {
$links = boost_get_menu_structure($menu, FALSE, 'node/' . $node->nid);
}
elseif (BOOST_FLUSH_MENU_ITEMS == 2) {
$links = boost_get_menu_structure($menu);
}
$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) {
Mike Carper
committed
if (is_int($nid)) {
if (BOOST_NO_DATABASE) {
$paths['reference' . $nid] = 'node/' . $nid;
}
else {
$data['reference' . $nid] = array('base_dir' => BOOST_FILE_PATH, '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) {
Mike Carper
committed
if (is_int($nid['nid'])) {
if (BOOST_NO_DATABASE) {
$paths['referrer' . $nid['nid']] = 'node/' . $nid['nid'];
}
else {
$data['referrer' . $nid['nid']] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'node', 'page_id' => $nid['nid']);
}
}
}
}
// Get views containing this node and flush.
if (BOOST_FLUSH_VIEWS && module_exists('views')) {
$GLOBALS['_boost_router_item'] = isset($GLOBALS['_boost_router_item']) ? $GLOBALS['_boost_router_item'] : _boost_get_menu_router();
$router_item = $GLOBALS['_boost_router_item'];
$relationship = array();
$relationship[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => $router_item['page_callback'], 'page_type' => $router_item['page_type'], 'page_id' => $router_item['page_id']);
Mike Carper
committed
$data = array_merge($data, boost_cache_get_node_relationships($relationship));
if (!empty($data)) {
$flushed += boost_cache_expire_router($data);
}
if (!empty($paths)) {
$flushed += boost_cache_expire_derivative($paths, TRUE);
}
if (BOOST_VERBOSE >= 7) {
watchdog('boost', 'Debug: boost_expire_node() <br />Node !nid was flushed resulting in !flushed pages being expired from the cache', array('!nid' => $node->nid, '!flushed' => $flushed));
}
Mike Carper
committed
}
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
/**
* 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 = TRUE, $needle = '', $first = TRUE, &$found_global = FALSE, &$menu_out = array()) {
$found_global = FALSE;
// 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
$structure = array_unique($structure);
$found_global = $found;
if ($first) {
$menu_out = array_unique($menu_out);
sort($menu_out);
return $menu_out;
}
else {
return $structure;
}
}
Mike Carper
committed
/**
* 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;
}
Arto Bendiken
committed
/**
* 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.
Arto Bendiken
committed
boost_set_cookie($user);
Arto Bendiken
committed
break;
case 'logout':
boost_set_cookie($user, BOOST_TIME - 86400);
Arto Bendiken
committed
break;
case 'delete':
// 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;
Mike Carper
committed
$flushed = boost_cache_expire_derivative($paths, TRUE, TRUE);
}
else {
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'user', 'page_id' => $account->uid);
$flushed = boost_cache_expire_router($data, TRUE, TRUE);
}
if (BOOST_VERBOSE >= 7) {
watchdog('boost', 'Debug: boost_user() <br />User !uid was deleted resulting in !flushed pages being expired from the cache', array('!uid' => $account->uid, '!flushed' => $flushed));
Arto Bendiken
committed
break;
}
}
/**
* Implementation of hook_block().
*/
function boost_block($op = 'list', $delta = 0, $edit = array()) {
global $user;
switch ($op) {
case 'list':
return array(
'status' => array(
Mike Carper
committed
'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,
),
Mike Carper
committed
'stats' => array(
'info' => t('Boost: AJAX core statistics'),
'region' => 'right',
'weight' => 10,
'cache' => BLOCK_NO_CACHE,
),
);
Mike Carper
committed
case 'configure':
if ($delta == 'stats') {
$form['boost_block_show_stats'] = array(
Mike Carper
committed
'#type' => 'checkbox',
Mike Carper
committed
'#default_value' => variable_get('boost_block_show_stats', FALSE),
'#description' => t('If false, uses Javascript to hide the block via "parent().parent().hide()".'),
Mike Carper
committed
);
$form['boost_block_cache_stats_block'] = array(
'#type' => 'checkbox',
'#title' => t('Cache Statistics Block'),
'#default_value' => variable_get('boost_block_cache_stats_block', FALSE),
);
Mike Carper
committed
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']);
Mike Carper
committed
}
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 <strong>live</strong> to anonymous visitors, as it is not currently in the static page cache.');
Mike Carper
committed
if (boost_is_cached($GLOBALS['_boost_path'])) {
$filename = boost_file_path($GLOBALS['_boost_path']);
$ttl = boost_db_get_ttl($filename);
$generate = boost_get_generation_time($filename);
$output = '';
if (BOOST_CHECK_BEFORE_CRON_EXPIRE) {
$output .= t('Site Has Changed: %old<br />', array('%old' => boost_has_site_changed() ? 'True' : 'False'));
}
$output .= t('<strong>Expired: %interval ago</strong><br />', array('%interval' => format_interval(abs($ttl))));
$output .= t('Expire In: %interval<br />', array('%interval' => format_interval(abs($ttl))));
$output .= t('Cache Generated: %time seconds<br />', array('%time' => round($generate, 2))) . ' ';
$output .= drupal_get_form('boost_block_flush_form');
}
$drupal_msg = max(count(drupal_get_messages(NULL, FALSE)), $GLOBALS['_boost_message_count']);
Mike Carper
committed
if ($error || (BOOST_HALT_ON_MESSAGES && $drupal_msg != 0)) {
$output = t('There are <strong>php errors</strong> or <strong>drupal messages</strong> on this page, preventing boost from caching.') . ' ';
$output .= t('ERROR: <pre>%error</pre> !link <br /> !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')));
Mike Carper
committed
}
if (BOOST_HALT_ON_MESSAGES && $drupal_msg != 0) {
$output .= t('MESSAGES: %msg <br /> !performance', array('%msg' => $drupal_msg, '!performance' => l(t('Turn Off Error Checking'), 'admin/settings/performance')));
Mike Carper
committed
}
}
$block['subject'] = '';
$block['content'] = theme('boost_cache_status', isset($ttl) ? $ttl : -1, $output);
}
break;
case 'config':
// 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']) && !BOOST_NO_DATABASE) {
$block['subject'] = '';
$block['content'] = theme('boost_cache_status', -1, drupal_get_form('boost_block_db_settings_form'));
}
break;
Mike Carper
committed
case 'stats':
$filename = 'boost_stats.php';
$block = module_invoke('statistics', 'block', 'view', 0);
variable_set('boost_statistics_html', $block['content']);
if (!( strpos($_SERVER['SCRIPT_FILENAME'], 'index.php') === FALSE
|| variable_get('site_offline', 0)
|| $_SERVER['REQUEST_METHOD'] != 'GET'
|| $_SERVER['SERVER_SOFTWARE'] === 'PHP CLI'
|| !BOOST_ENABLED
|| isset($_GET['nocache'])
|| !boost_is_cacheable($GLOBALS['_boost_path'])
Mike Carper
committed
|| !empty($user->uid)
|| !module_exists('statistics')
)) {
$block = array();
$block['subject'] = 'Popular content';
$block['content'] = '<div id="boost-stats"></div>' . boost_stats_generate($filename);
}
elseif (!variable_get('boost_block_show_stats', FALSE)) {
$block['content'] .= '<div id="boost-stats"></div>';
drupal_add_js('$("#boost-stats").parent().parent().hide();', 'inline', 'footer');
Mike Carper
committed
}
}
return $block;
}
}
function boost_block_flush_form() {
$GLOBALS['_boost_router_item'] = isset($GLOBALS['_boost_router_item']) ? $GLOBALS['_boost_router_item'] : _boost_get_menu_router();
$router_item = $GLOBALS['_boost_router_item'];
$form['boost_clear']['page_callback'] = array(
'#type' => 'hidden',
'#value' => $router_item['page_callback'],
);
$form['boost_clear']['page_type'] = array(
'#type' => 'hidden',
'#value' => $router_item['page_type'],
);
$form['boost_clear']['page_id'] = array(
'#type' => 'hidden',
'#value' => $router_item['page_id'],
);
Mike Carper
committed
$form['boost_clear']['path'] = array(
'#type' => 'hidden',
'#value' => $GLOBALS['_boost_path'],
);
$form['boost_cache']['clear'] = array(
'#type' => 'submit',
'#value' => t('Flush Page'),
'#submit' => array('boost_block_form_flush_submit'),
);
return ($form);
}
function boost_block_form_flush_submit(&$form_state, $form) {
// Special front page handling
if ($form['values']['page_callback'] == 'node_page_default' && BOOST_CACHE_XML) {
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'node_feed');
}
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => $form['values']['page_callback'], 'page_type' => $form['values']['page_type'], 'page_id' => $form['values']['page_id']);
if ($data) {
$flushed += boost_cache_expire_router($data, TRUE);
}
Mike Carper
committed
if (array_key_exists('path', $form['values'])) {
$flushed += boost_cache_expire_derivative(array($form['values']['path']), TRUE, TRUE);
if (BOOST_VERBOSE >= 7) {
watchdog('boost', 'Debug: boost_block_form_flush_submit() <br />Page !path was deleted resulting in !flushed pages being expired from the cache', array('!path' => $form['values']['path'], '!flushed' => $flushed));
}
}
function boost_block_db_settings_form() {
// set info
$period = drupal_map_assoc(array(-1, 0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 64800, 86400, 2*86400, 3*86400, 4*86400, 5*86400, 6*86400, 604800, 2*604800, 3*604800, 4*604800, 8*604800, 16*604800, 52*604800), 'format_interval');
$period[0] = '<' . t('none') . '>';
$period[-1] = t('default');
//$info = boost_get_db(boost_file_path($GLOBALS['_boost_path']));
$GLOBALS['_boost_router_item'] = isset($GLOBALS['_boost_router_item']) ? $GLOBALS['_boost_router_item'] : _boost_get_menu_router();
$router_item = $GLOBALS['_boost_router_item'];
$settings = boost_get_settings_db($router_item);
$default = 0;
foreach ($settings as $key => $value) {
if ($value != NULL) {
$info = $value;
$default = $key;
break;
}
}
if (!isset($info)) {
$info['lifetime'] = -1;
$info['push'] = -1;
}
// create form
$form['boost_db_settings']['lifetime'] = array(
'#type' => 'select',
'#title' => t('Maximum cache lifetime'),
'#default_value' => $info['lifetime'],
'#options' => $period,
'#description' => t('Default: %default', array('%default' => format_interval(BOOST_CACHE_LIFETIME))),
);
$form['boost_db_settings']['push'] = array(
'#type' => 'select',
'#title' => t('Preemptive Cache'),
'#default_value' => $info['push'],