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_PATH', dirname(__FILE__));
define('BOOST_ENABLED', variable_get('boost_enabled', CACHE_DISABLED));
define('BOOST_ROOT_CACHE_PATH', 'cache');
define('BOOST_MULTISITE_SINGLE_DB', variable_get('boost_multisite_single_db', FALSE));
define('BOOST_FILE_PATH', BOOST_MULTISITE_SINGLE_DB ? boost_cache_directory(NULL, FALSE) : variable_get('boost_file_path', boost_cache_directory(NULL, FALSE)));
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_MAX_PATH_DEPTH', 10);
Arto Bendiken
committed
define('BOOST_CACHEABILITY_OPTION', variable_get('boost_cacheability_option', 0));
define('BOOST_CACHEABILITY_PAGES', variable_get('boost_cacheability_pages', ''));
define('BOOST_FETCH_METHOD', variable_get('boost_fetch_method', 'php'));
define('BOOST_PRE_PROCESS_FUNCTION', variable_get('boost_pre_process_function', ''));
define('BOOST_POST_UPDATE_COMMAND', variable_get('boost_post_update_command', ''));
define('BOOST_CRON_LIMIT', variable_get('boost_cron_limit', 100));
define('BOOST_ONLY_ASCII_PATH', variable_get('boost_only_ascii_path', TRUE));
Mike Carper
committed
define('BOOST_GZIP', variable_get('page_compression', TRUE));
Mike Carper
committed
define('BOOST_GZIP_FILE_PATH', str_replace(BOOST_ROOT_CACHE_PATH . '/', BOOST_ROOT_CACHE_PATH . '/gz/', BOOST_FILE_PATH));
Mike Carper
committed
define('BOOST_CLEAR_CACHE_OFFLINE', variable_get('boost_clear_cache_offline', TRUE));
Mike Carper
committed
define('BOOST_HALT_ON_ERRORS', variable_get('boost_halt_on_errors', FALSE));
Mike Carper
committed
define('BOOST_ROOT_FILE', variable_get('boost_root_file', '.boost'));
define('BOOST_FLUSH_DIR', variable_get('boost_flush_dir', TRUE));
define('BOOST_CACHE_XML', variable_get('boost_cache_xml', TRUE));
define('BOOST_CACHE_LIFETIME', variable_get('boost_cache_lifetime', 3600));
define('BOOST_TIME', time());
define('BOOST_CACHE_QUERY', variable_get('boost_cache_query', TRUE));
Mike Carper
committed
define('BOOST_IGNORE_FLUSH', variable_get('boost_ignore_flush', 0));
define('BOOST_CACHE_CSS', variable_get('boost_cache_css', FALSE));
define('BOOST_CACHE_JS', variable_get('boost_cache_js', FALSE));
define('BOOST_PERMISSIONS_FILE', variable_get('boost_permissions_file', ''));
define('BOOST_PERMISSIONS_DIR', variable_get('boost_permissions_dir', ''));
Arto Bendiken
committed
// 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):
Arto Bendiken
committed
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 -->\n"));
Arto Bendiken
committed
// This is needed since the $user object is already destructed in _boost_ob_handler():
define('BOOST_USER_ID', @$GLOBALS['user']->uid);
Arto Bendiken
committed
Mike Carper
committed
//////////////////////////////////////////////////////////////////////////////
// Global variables
//$GLOBALS['_boost_path'] = '';
//$GLOBALS['_boost_query'] = '';
//$GLOBALS['_boost_message_count'] = '';
//$GLOBALS['_boost_cache_this'] = '';
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_init(). Performs page setup tasks if page not cached.
Arto Bendiken
committed
*/
function boost_init() {
global $user, $base_path;
//set variables
$GLOBALS['_boost_path'] = $_REQUEST['q'];
// Make the proper filename for our query
$GLOBALS['_boost_query'] = '_';
foreach ($_GET as $key => $val) {
if ($key != 'q' && $key != 'destination') {
$GLOBALS['_boost_query'] .= (($GLOBALS['_boost_query'] == '_') ? '' : '&') . $key . '=' . $val;
Arto Bendiken
committed
}
}
// 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
|| isset($_GET['nocache'])
|| !boost_is_cacheable($GLOBALS['_boost_path'])
) {
Mike Carper
committed
$GLOBALS['_boost_cache_this'] = FALSE;
return;
}
// For authenticated users, set a special cookie to prevent them
// inadvertently getting served pages from the static page cache.
if (!empty($user->uid)) {
boost_set_cookie($user);
}
// We only generate cached pages for anonymous visitors.
else {
Mike Carper
committed
if (BOOST_ENABLED != CACHE_AGGRESSIVE) {
$GLOBALS['conf']['cache'] = CACHE_DISABLED;
}
Mike Carper
committed
$GLOBALS['_boost_cache_this'] = TRUE;
ob_start('_boost_ob_handler');
}
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);
$query_parts['path'] = ($query_parts['path'] == base_path() ? '' : substr($query_parts['path'], strlen(base_path())));
// Add a nocache parameter to query. Such pages will never be cached
$query_parts['query'] .= (empty($query_parts['query']) ? '' : '&') . 'nocache=1';
$destination = url($query_parts['path'], $query_parts);
// 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_performace_page'),
'access arguments' => array('administer site configuration'),
'weight' => 10,
'type' => MENU_LOCAL_TASK,
'file' => 'boost.admin.inc',
);
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['page_cache']['#title'] = t('Anonymous page caching');
Mike Carper
committed
$form['page_cache']['#description'] = t('Enabling the page cache will offer a significant performance boost. Drupal can store and send compressed cached pages requested by <em>anonymous</em> users. By caching the first request to the page, Drupal does not have to construct the page each time it is viewed. The page must first be visited by an anonymous user in order for the cache to work on subsequent requests for that page. Boost & Core caching do not work for logged in users.');
$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
if (variable_get('preprocess_css', FALSE)==TRUE) {
if (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).'), 'warning');
}
}
Arto Bendiken
committed
}
}
/**
* Implementation of hook_cron(). Performs periodic actions.
*/
function boost_cron() {
Mike Carper
committed
if (!BOOST_ENABLED) {
return;
}
Arto Bendiken
committed
Mike Carper
committed
// Expire old content
if (variable_get('boost_expire_cron', TRUE) && boost_cache_db_expire()) {
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']);
}
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'])) {
boost_cache_expire_derivative('node/' . $comment['nid'], TRUE);
Arto Bendiken
committed
break;
Arto Bendiken
committed
case 'publish':
case 'unpublish':
case 'delete':
if (!empty($comment->nid)) {
boost_cache_expire_derivative('node/' . $comment->nid, TRUE);
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':
case 'update':
case 'delete':
// Expire all relevant node pages from the static page cache to prevent serving stale content:
if (!empty($node->nid)) {
boost_cache_expire_derivative('node/' . $node->nid, TRUE);
Arto Bendiken
committed
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
break;
}
}
/**
* 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 '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);
Arto Bendiken
committed
// 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(
Mike Carper
committed
'info' => t('Boost: Pages cache status'),
'region' => 'right',
'weight' => 10,
'cache' => BLOCK_NO_CACHE,
),
'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['items'] = array(
'#type' => 'checkbox',
'#title' => t('Display Statistics'),
'#default_value' => variable_get('boost_block_show_stats', FALSE),
);
return $form;
}
case 'save':
if ($delta == 'stats') {
variable_set('boost_block_show_stats', $edit['items']);
}
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/*).
Mike Carper
committed
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'])) {
$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 ?
'<strong>The cached copy expired %interval ago.</strong>' :
'The cached copy will expire in %interval.',
array('%interval' => format_interval(abs($ttl))));
$output .= drupal_get_form('boost_block_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 || $drupal_msg != 0)) {
$output = t('There are <strong>php errors</strong> or <strong>drupal messages</strong> on this page, preventing boost from caching.');
Mike Carper
committed
$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 ($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;
Mike Carper
committed
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
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'])
|| !empty($user->uid)
|| !file_exists($filename)
|| !module_exists('statistics')
)) {
if (variable_get('boost_block_show_stats', FALSE)) {
$block = array();
$block['subject'] = 'Popular content';
$block['content'] = '<div id="boost-stats"></div>' . boost_stats_generate($filename);
}
else {
$block = array();
$block['content'] = '<div id="boost-stats" style="display:none;"></div>' . boost_stats_generate($filename);
}
}
elseif (!variable_get('boost_block_show_stats', FALSE)) {
$block = array();
}
}
return $block;
}
}
function boost_block_form() {
$form['clear_cache']['path'] = array(
'#type' => 'hidden',
'#value' => $GLOBALS['_boost_path'],
);
$form['clear_cache']['clear'] = array(
'#type' => 'submit',
'#value' => t('Flush Page'),
'#submit' => array('boost_block_form_submit'),
);
return ($form);
}
function boost_block_form_submit(&$form_state, $form) {
boost_cache_expire_derivative($form['values']['path'], TRUE);
}
Mike Carper
committed
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
/**
* 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 = 'nid=' . arg(1);;
}
else {
$nid = 'nid=NULL';
}
// access log enabled.
if ((variable_get('statistics_enable_access_log', 0)) && (module_invoke('throttle', 'status') == 0)) {
$title = 'title=' . drupal_urlencode(strip_tags(drupal_get_title()));
$q = 'q=' . $_GET['q'];
}
else {
$title = 'title=NULL';
$q = 'q=NULL';
}
$page_js = array(
'boost' => array(
'nid' => $nid,
'q' => $q,
'title' => $title,
),
);
$site_js = '$("#boost-stats").load(Drupal.settings.basePath + "boost_stats.php?js=2" + "&" + Drupal.settings.boost.nid + "&" + Drupal.settings.boost.q + "&" + Drupal.settings.boost.title + "&referer=" + document.referrer);';
// 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 . '?js=0' . '&'. $nid . '&'. $title . '&'. $q . '" alt="" /></div></noscript>';
return $page_ns;
}
/**
* Implementation of hook_theme().
*/
function boost_theme() {
return array(
'boost_cache_status' => array(
'arguments' => array('ttl' => NULL, 'text' => NULL),
),
);
}
function theme_boost_cache_status($ttl, $text) {
return '<span class="boost cache-status" content="' . $ttl . '"><small>' . $text . '</small></span>';
}
Arto Bendiken
committed
//////////////////////////////////////////////////////////////////////////////
// Output buffering callback
Arto Bendiken
committed
/**
* PHP output buffering callback for static page caching.
Arto Bendiken
committed
*
* NOTE: objects have already been destructed so $user is not available.
Mike Carper
committed
*
* @param $buffer
* Pages contents
Arto Bendiken
committed
*/
function _boost_ob_handler($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']));
Mike Carper
committed
if (function_exists('error_get_last')) {
if (BOOST_HALT_ON_ERRORS && $error = error_get_last()) {
switch ($error['type']) {
Mike Carper
committed
case E_NOTICE: //Ignore run-time notices
case E_USER_NOTICE: //Ignore user-generated notice message
Mike Carper
committed
//case E_DEPRECATED: //Ignore run-time notices
//case E_USER_DEPRECATED: //Ignore user-generated notice message
Mike Carper
committed
break;
Mike Carper
committed
default: //Do not cache page on all other errors
Mike Carper
committed
watchdog('boost', 'There are <strong>php errors</strong> on this page, preventing boost from caching. 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')), WATCHDOG_WARNING);
Mike Carper
committed
return $buffer;
}
}
}
Mike Carper
committed
if (BOOST_HALT_ON_ERRORS && $GLOBALS['_boost_message_count'] != 0) {
watchdog('boost', 'There are <strong>drupal messages</strong> on this page, preventing boost from caching. MESSAGES: %msg <br /> !performance', array('%msg' => $GLOBALS['_boost_message_count'], '!performance' => l(t('Turn Off Error Checking'), 'admin/settings/performance')), WATCHDOG_WARNING);
Mike Carper
committed
return $buffer;
}
// Check the currently set content type (at present we can't deal with
// anything else than HTML) and the HTTP response code. We're going to be
// exceedingly conservative here and only cache 'text/html' pages that
// were output with a 200 OK status. Anything more is simply asking for
// loads of trouble.
Mike Carper
committed
if ($GLOBALS['_boost_cache_this'] && _boost_get_http_status() == 200 && strlen($buffer) > 0) {
switch (_boost_get_content_type()) {
case 'text/html':
boost_cache_set($GLOBALS['_boost_path'], $buffer);
Mike Carper
committed
boost_cache_css_js_files($buffer);
break;
case 'application/rss';
case 'text/xml';
case 'application/rss+xml';
boost_cache_set($GLOBALS['_boost_path'], $buffer, BOOST_XML_EXTENSION);
break;
Arto Bendiken
committed
}
// Allow the page request to finish up normally
return $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.
Mike Carper
committed
*
* @param $default
* Return this value if it can't be found
Arto Bendiken
committed
*/
function _boost_get_content_type($default = NULL) {
static $regex = '!^Content-Type:\s*([\w\d\/\-]+)!i';
return _boost_get_http_header($regex, $default);
}
/**
* Determines the HTTP response code that the current page request will be
* returning by examining the HTTP headers that have been output so far.
Mike Carper
committed
*
* @param $default
* Look for a 200 status
*/
function _boost_get_http_status($default = 200) {
static $regex = '!^HTTP/1.1\s+(\d+)!';
return (int)_boost_get_http_header($regex, $default);
}
Arto Bendiken
committed
Mike Carper
committed
/**
* Get HTTP header
*
* @param $regex
* Regular expression to get HTTP Header Line
* @param $default
* Return this value if it can't be found
*/
function _boost_get_http_header($regex, $default = NULL) {
// The last header is the one that counts:
Arto Bendiken
committed
$headers = preg_grep($regex, explode("\n", drupal_set_header()));
if (!empty($headers) && preg_match($regex, array_pop($headers), $matches)) {
Arto Bendiken
committed
return $matches[1]; // found it
return $default; // no such luck
Arto Bendiken
committed
}
//////////////////////////////////////////////////////////////////////////////
// Boost API implementation
/**
* Determines whether a given Drupal page can be cached or not.
*
* To avoid potentially troublesome situations, the user login page is never
* cached, nor are any admin pages. At present, we also refuse to cache any
* RSS feeds provided by Drupal, since they would require special handling
* in the mod_rewrite ruleset as they shouldn't be sent out using the
* text/html content type.
Mike Carper
committed
* TODO: don't cache pages with unacceptable symbols
Mike Carper
committed
*
* @param $path
* Current URL
*/
function boost_is_cacheable($path) {
$path = (empty($path)) ? variable_get('site_frontpage', 'node') : $path;
Mike Carper
committed
$normal_path = drupal_get_normal_path($path); // normalize path
// Never cache the basic user login/registration pages or any administration pages
// RSS feeds are not cacheable due to content type restrictions
// Don't cache comment reply pages
if ( $normal_path == 'user'
|| preg_match('!^user/(login|register|password)!', $normal_path)
|| preg_match('!^admin!', $normal_path)
|| preg_match('!comment/reply$!', $normal_path)
) {
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'] != '_') {
return FALSE;
}
// Match the user's cacheability settings against the path
if (BOOST_CACHEABILITY_OPTION == 2) {
$result = drupal_eval(BOOST_CACHEABILITY_PAGES);
return !empty($result);
}
$regexp = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\<front\\\\>($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote(BOOST_CACHEABILITY_PAGES, '/')) .')$/';
Mike Carper
committed
return !(BOOST_CACHEABILITY_OPTION xor preg_match($regexp, $path));
// TODO: investigate if $path or $normal_path should be used on the above line. $normal_path was introduced in a recent patch - around http://drupal.org/node/174380#comment-1477658
// TODO: document the above fat $regexp
}
/**
* Determines whether a given Drupal page is currently cached or not.
Mike Carper
committed
*
* @param $path
* Current URL
*/
function boost_is_cached($path) {
Mike Carper
committed
// 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));
}
/**
Mike Carper
committed
* Deletes all files currently in the cache.
*/
function boost_cache_clear_all() {
Mike Carper
committed
if (BOOST_IGNORE_FLUSH == 0) {
boost_cache_clear_all_db();
boost_cache_delete(TRUE);
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.
* OLD FUNCTION
function boost_cache_expire_all() {
Mike Carper
committed
boost_cache_delete(FALSE);
return TRUE;
}
/**
* Resets all entries in database.
*/
function boost_cache_clear_all_db() {
db_query("UPDATE {boost_cache} SET expire = %d", 0);
}
Mike Carper
committed
/**
* Deletes files in the cache.
Mike Carper
committed
*
* @param $flush
* If true clear the entire cache directory.
Mike Carper
committed
*/
function boost_cache_delete($flush = FALSE) {
clearstatcache();
Mike Carper
committed
//recreate dirs
_boost_mkdir_p(BOOST_FILE_PATH);
_boost_mkdir_p(BOOST_GZIP_FILE_PATH);
//add in .boost root id file
file_put_contents(BOOST_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
file_put_contents(BOOST_GZIP_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_GZIP_FILE_PATH);
//Flush Cache
if (file_exists(BOOST_FILE_PATH)) {
Mike Carper
committed
_boost_rmdir_rf(BOOST_FILE_PATH, $flush, TRUE);
Mike Carper
committed
if (file_exists(BOOST_GZIP_FILE_PATH)) {
Mike Carper
committed
_boost_rmdir_rf(BOOST_GZIP_FILE_PATH, $flush, TRUE);
Mike Carper
committed
}
Mike Carper
committed
//recreate dirs
_boost_mkdir_p(BOOST_FILE_PATH);
_boost_mkdir_p(BOOST_GZIP_FILE_PATH);
Mike Carper
committed
//add in .boost root id file
file_put_contents(BOOST_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
file_put_contents(BOOST_GZIP_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_GZIP_FILE_PATH);
/**
Mike Carper
committed
* Finds all possible paths/redirects/aliases given the root path.
*
* @param $path
* Current URL
* @param $wildcard
* If true get all chached files that start with this path.
*/
function boost_cache_expire_derivative($path, $wildcard = FALSE) {
Mike Carper
committed
global $base_path;
//path alias
$path_alias = url($path, array('absolute' => FALSE));
Mike Carper
committed
if ($base_path != '/') {
$path_alias = implode('/', array_diff_assoc(array_filter(explode('/', $path_alias)), array_filter(explode('/', $base_path))));
}
Mike Carper
committed
//path redirects
if (module_exists('path_redirect')) {
$path_redirects = boost_path_redirect_load(array('redirect' => $path));
}
Mike Carper
committed
//flush caches
boost_cache_expire($path_alias, $wildcard);
if (isset($path_redirects)) {
foreach ($path_redirects as $path_redirect) {
boost_cache_expire($path_redirect['path'], $wildcard);
}
}
boost_cache_expire($path, $wildcard);
}
/**
* Expires the static file cache for a given page, or multiple pages
* matching a wildcard.
Mike Carper
committed
*
* @param $path
* Current URL
* @param $wildcard
* If true get all chached files that start with this path.
*
* TODO: Replace glob() with a database opperation.
*/
function boost_cache_expire($path, $wildcard = FALSE) {
// Sanity check
if (boost_file_path($path, FALSE) === FALSE) {
return FALSE;
}
// Get list of related files
$tempA = glob(boost_file_path($path, FALSE, NULL) . (($wildcard) ? '*' : '') . BOOST_FILE_EXTENSION, GLOB_NOSORT);
$tempB = glob(boost_file_path($path, FALSE, NULL) . (($wildcard) ? '*' : '') . BOOST_XML_EXTENSION, GLOB_NOSORT);
if (!empty($tempA) && !empty($tempB)) {
$filenames = array_filter(array_merge($tempA, $tempB));
}
elseif (!empty($tempA)) {
$filenames = $tempA;
}
elseif (!empty($tempB)) {
$filenames = $tempB;
}
else {
return FALSE;
}
if (empty($filenames)) {
return FALSE;
}
// Flush expired files
foreach ($filenames as $filename) {
boost_cache_kill($filename);
}
return TRUE;
}
/**
* Deletes cached page from file system
Mike Carper
committed
*
* @param $filename
* Name of cached file; primary key in database
*/
function boost_cache_kill($filename) {
Mike Carper
committed
if (BOOST_IGNORE_FLUSH < 3 && strstr($filename, BOOST_FILE_PATH)) {
Mike Carper
committed
db_query("UPDATE {boost_cache} SET expire = 0 WHERE filename = '%s'", $filename);
if (file_exists($filename)) {
@unlink($filename);
}
$gz_filename = str_replace(BOOST_FILE_PATH, BOOST_GZIP_FILE_PATH, $filename) . '.gz';
if (file_exists($gz_filename)) {
@unlink($gz_filename);
}
/**
* Flushes all expired pages from database
Mike Carper
committed
*
* TODO del empty dirs if enabled
*/
function boost_cache_db_expire() {
Mike Carper
committed
if (BOOST_IGNORE_FLUSH < 2) {
$result = db_query('SELECT filename FROM {boost_cache} WHERE expire BETWEEN 1 AND %d', BOOST_TIME);
while ($boost = db_fetch_array($result)) {
boost_cache_kill($boost['filename']);
}
if (BOOST_FLUSH_DIR) {
// TO-DO: del empty dirs.
}
return TRUE;
Mike Carper
committed
return FALSE;
}
/**
* Returns the cached contents of the specified page, if available.
Mike Carper
committed
*
* @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;
}
/**
Mike Carper
committed
* 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) {
// Append the Boost footer with the relevant timestamps
$time = BOOST_TIME;
$cached_at = date('Y-m-d H:i:s', $time);
$expires_at = date('Y-m-d H:i:s', $time + BOOST_CACHE_LIFETIME);
$data = rtrim($data) . "\n" . str_replace(array('%cached_at', '%expires_at'), array($cached_at, $expires_at), BOOST_BANNER);
// Invoke hook_boost_preprocess($path, $data)
foreach (module_implements('boost_preprocess') as $module) {
if (($result = module_invoke($module, $path, $data)) != 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, $data);
Mike Carper
committed
// Create or update the static files as needed
if (($filename = boost_file_path($path, TRUE, $extension))) {
Mike Carper
committed
boost_cache_write($filename, $data);
Mike Carper
committed
if (BOOST_GZIP) {
boost_cache_write(str_replace(BOOST_FILE_PATH, BOOST_GZIP_FILE_PATH, $filename) . '.gz', gzencode($data, 9));
Mike Carper
committed
}
boost_db_prep($filename);
}
return TRUE;
}
/**
* Figure out what is going in the database & put it in
Mike Carper
committed
*
* @param $filename
* Name of cached file; primary key in database
*/
function boost_db_prep($filename) {
$router_item = _boost_get_menu_router();
$expire = BOOST_CACHE_LIFETIME + BOOST_TIME;
$lifetime = -1;
$push = -1;
$boost_settings_db = boost_get_settings_db($router_item);
$boost_db = boost_get_db($filename);
//get data from actual entry first, if this page has been cached before.
if ($boost_db) {
Mike Carper
committed
$expire = $boost_db['lifetime'] != -1 ? $boost_db['lifetime'] + BOOST_TIME : $expire;
$lifetime = $boost_db['lifetime'];
$push = $boost_db['push'];
}
//get data from settings table, if this page has not been put into the cache.
elseif ($boost_settings_db) {
Mike Carper
committed
$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);
}
/**
* Puts boost info into database.
Mike Carper
committed
*
* @param $filename
* Name of cached file; 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_arguments.
*/
function boost_put_db($filename, $expire, $lifetime, $push, $router_item) {
db_query("UPDATE {boost_cache} SET expire = %d, lifetime = %d, push = %d, page_callback = '%s', page_arguments = '%s' WHERE filename = '%s'", $expire, $lifetime, $push, $router_item['page_callback'], $router_item['page_arguments'], $filename);
if (!db_affected_rows()) {
@db_query("INSERT INTO {boost_cache} (filename, expire, lifetime, push, page_callback, page_arguments) VALUES ('%s', %d, %d, %d, '%s', '%s')", $filename, $expire, $lifetime, $push, $router_item['page_callback'], $router_item['page_arguments']);
}
}
/**
Mike Carper
committed
* Gets boost info from cache database.
*
* @param $filename
* Filename to be looked up in the database
*/
function boost_get_db($filename) {
return db_fetch_array(db_query_range("SELECT expire, lifetime, push FROM {boost_cache} WHERE filename = '%s'", $filename, 0, 1));
}
/**
Mike Carper
committed
* Gets boost settings from cache settings database.
*
* @param $router_item
* Array containing page_callback & page_arguments.
*/
function boost_get_settings_db($router_item) {