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', FALSE));
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_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));
define('BOOST_AGGRESSIVE_GZIP', variable_get('boost_aggressive_gzip', TRUE));
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_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) : TRUE);
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() {
// 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_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
&& floatval(VERSION) <= 6.13
&& 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);
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);
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;
switch ($op) {
case 'insert':
if (BOOST_FLUSH_VIEWS_INSERT && module_exists('views')) {
$GLOBALS['_boost_nid'] = $node->nid;
register_shutdown_function('_boost_view_insert');
}
Arto Bendiken
committed
case 'update':
case 'delete':
Mike Carper
committed
boost_expire_node($node);
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);
$data = array();
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']);
}
}
}
boost_cache_expire_router($data);
}
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);
}
}
/**
* 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);
}
}
/**
* Expires a node from the cache.
*
* @param $node
* node object
*/
function boost_expire_node($node) {
if (empty($node->nid)) {
return FALSE;
}
$data = array();
$paths = array();
Mike Carper
committed
// Expire all relevant node pages from the static page cache to prevent serving stale content:
if ($node->promote == 1) {
$paths[] = '<front>';
}
if (BOOST_NO_DATABASE) {
$paths[] = 'node/' . $node->nid;
}
else {
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'node', 'page_id' => $node->nid);
Mike Carper
committed
}
// Get terms and flush their page
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) {
if (BOOST_NO_DATABASE) {
$paths[] = 'taxonomy/term/' . $tid;
}
else {
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'taxonomy', 'page_id' => $tid);
}
Mike Carper
committed
}
}
// Get CCK References and flush.
if (BOOST_FLUSH_CCK_REFERENCES && module_exists('nodereference')) {
$nids = array();
$type = content_types($node->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 (BOOST_NO_DATABASE) {
$paths[] = 'node/' . $nid;
}
else {
$data[] = 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) {
if (BOOST_NO_DATABASE) {
$paths[] = 'node/' . $nid['nid'];
}
else {
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'node', 'page_id' => $nid['nid']);
}
}
}
}
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']);
$data[] = boost_cache_get_node_relationships($relationship);
boost_cache_expire_derivative($paths, TRUE);
boost_cache_expire_router($data);
Mike Carper
committed
}
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;
boost_cache_expire_derivative($paths, TRUE);
}
else {
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => 'user', 'page_id' => $account->uid);
boost_cache_expire_router($data);
}
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_has_site_changed()) {
$output .= t('Globally the site has changed.') . ' ';
}
$output .= t('This page is being served to anonymous visitors from the static page cache. Took %time seconds to generate this cached page.', array('%time' => round($generate, 3))) . ' ';
if ($ttl < 0) {
$output .= t('<strong>The cached copy expired %interval ago.</strong>', array('%interval' => format_interval(abs($ttl))));
}
else {
$output .= t('The cached copy will expire in %interval.', array('%interval' => format_interval(abs($ttl))));
}
$output .= drupal_get_form('boost_block_flush_form');
}
Mike Carper
committed
$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']);
Mike Carper
committed
if ((BOOST_HALT_ON_ERRORS && $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.') . ' ';
if (BOOST_HALT_ON_ERRORS && $error) {
$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']);
boost_cache_expire_router($data, TRUE);
boost_cache_expire_derivative(array($form['values']['path']));
}
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('Minimum 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'],
'#options' => array(
-1 => 'default',
0 => 'No',
1 => 'Yes',
),
$form['boost_db_settings']['selection'] = array(
'#type' => 'select',
'#title' => t('Scope'),
'#default_value' => $default,
'#options' => array(
0 => 'Page ID: ' . $router_item['page_id'],
1 => 'Content Type: ' . $router_item['page_type'],
2 => 'Content Container: ' . $router_item['page_callback'],
),
$form['boost_db_settings']['send'] = array(
'#type' => 'submit',
'#value' => t('Set Configuration'),
'#submit' => array('boost_block_db_settings_form_submit'),
);
$form['boost_db_rm_settings']['id'] = array(
'#type' => 'checkbox',
'#title' => t('Page ID'),
'#default_value' => $settings[0] != NULL ? FALSE : TRUE,
'#disabled' => $settings[0] != NULL ? FALSE : TRUE,
'#description' => $period[$settings[0]['lifetime']] . ' - ' . $router_item['page_id'],
);
$form['boost_db_rm_settings']['id_value'] = array(
'#type' => 'hidden',
'#title' => t('id_value'),
'#default_value' => $settings[0] != NULL ? $settings[0]['csid'] : FALSE,
'#disabled' => $settings[0] != NULL ? FALSE : TRUE,
);
$form['boost_db_rm_settings']['type'] = array(
'#type' => 'checkbox',
'#title' => t('Content Type'),
'#default_value' => $settings[1] != NULL ? FALSE : TRUE,
'#disabled' => $settings[1] != NULL ? FALSE : TRUE,
'#description' => $period[$settings[1]['lifetime']] . ' - ' . $router_item['page_type'],
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
);
$form['boost_db_rm_settings']['type_value'] = array(
'#type' => 'hidden',
'#title' => t('type_value'),
'#default_value' => $settings[1] != NULL ? $settings[1]['csid'] : FALSE,
'#disabled' => $settings[1] != NULL ? FALSE : TRUE,
);
$form['boost_db_rm_settings']['container'] = array(
'#type' => 'checkbox',
'#title' => t('Content Container'),
'#default_value' => $settings[2] != NULL ? FALSE : TRUE,
'#disabled' => $settings[2] != NULL ? FALSE : TRUE,
'#description' => $period[$settings[2]['lifetime']] . ' - ' . $router_item['page_callback'],
);
$form['boost_db_rm_settings']['container_value'] = array(
'#type' => 'hidden',
'#title' => t('container_value'),
'#default_value' => $settings[2] != NULL ? $settings[2]['csid'] : FALSE,
'#disabled' => $settings[2] != NULL ? FALSE : TRUE,
);
$form['boost_db_rm_settings']['send'] = array(
'#type' => 'submit',
'#value' => t('Delete Configuration'),
'#submit' => array('boost_block_db_rm_settings_form_submit'),
'#description' => t('Check the box to delete it'),
);
return $form;
}
/**
* Sets page specific settings in the boost cache database.
*/
function boost_block_db_settings_form_submit(&$form_state, $form) {
boost_set_db_page_settings($form['values']['lifetime'], $form['values']['push'], $form['values']['selection']);
/**
* Sets page specific settings in the boost cache database.
*/
function boost_block_db_rm_settings_form_submit(&$form_state, $form) {
$GLOBALS['_boost_router_item'] = isset($GLOBALS['_boost_router_item']) ? $GLOBALS['_boost_router_item'] : _boost_get_menu_router();
$router_item = $GLOBALS['_boost_router_item'];
$data = array();
if (is_int($form['values']['id'])) {
boost_remove_settings_db($form['values']['id_value']);
$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']);
}
if (is_int($form['values']['type'])) {
boost_remove_settings_db($form['values']['type_value']);
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => $router_item['page_callback'], 'page_type' => $router_item['page_type']);
}
if (is_int($form['values']['container'])) {
boost_remove_settings_db($form['values']['container_value']);
$data[] = array('base_dir' => BOOST_FILE_PATH, 'page_callback' => $router_item['page_callback']);
}
boost_cache_expire_router($data);
}
Mike Carper
committed
/**
* Generate js/html for boost stat counter.
*
* NOTE HTML code could be added to the $buffer directly. Would prevent 2x
* counts on first view. Would be hard to do though.
*
* @param $filename
* Name of boost's statistics php file.
*/
function boost_stats_generate($filename) {
Global $base_path;
// is node & node count enabled.
if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == '' && variable_get('statistics_count_content_views', 0)) {
$nid = arg(1);;
Mike Carper
committed
}
else {
$nid = 'NULL';
Mike Carper
committed
}
// access log enabled.
if ((variable_get('statistics_enable_access_log', 0)) && (module_invoke('throttle', 'status') == 0)) {
$title = drupal_urlencode(strip_tags(drupal_get_title()));
$q = $_GET['q'];
Mike Carper
committed
}
else {
$title = 'NULL';
$q = 'NULL';
Mike Carper
committed
}
$page_js = array(
'boost' => array(
'nid' => $nid,
'q' => $q,
'title' => $title,
),
);
$site_js = <<<ETO
$.getJSON(Drupal.settings.basePath + "$filename", {nocache: "1", js: "1", nid: Drupal.settings.boost.nid, qq: Drupal.settings.boost.q, title: Drupal.settings.boost.title, referer: document.referrer}, function(response) {
$.each(response, function(id, contents) {
if (contents == 'NULL') {
$(id).parent().parent().hide();
}
else {
$(id).html(contents);
}
});
});
ETO;
Mike Carper
committed
// page specific variables
drupal_add_js($page_js, 'setting', 'header');
// site-wide code
drupal_add_js($site_js, 'inline', 'footer');
// no script code
$page_ns = '<noscript><div style="display:inline;"><img src="' . $base_path . $filename . '?nocache=1' . '&nid='. $nid . '&title='. $title . '&q='. $q . '" alt="" /></div></noscript>';
Mike Carper
committed
return $page_ns;
}