boost.module in Boost 5
Same filename and directory in other branches
Provides static page caching for Drupal.
File
boost.moduleView source
<?php
/**
* @file
* Provides static page caching for Drupal.
*/
//////////////////////////////////////////////////////////////////////////////
// BOOST SETTINGS
define('BOOST_PATH', dirname(__FILE__));
define('BOOST_FRONTPAGE', drupal_get_normal_path(variable_get('site_frontpage', 'node')));
define('BOOST_ENABLED', variable_get('boost', CACHE_DISABLED));
define('BOOST_FILE_PATH', variable_get('boost_file_path', 'cache'));
define('BOOST_FILE_EXTENSION', variable_get('boost_file_extension', '.html'));
define('BOOST_MAX_PATH_DEPTH', 10);
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));
// This cookie is set for all logged-in 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 at %cached_at, expires at %expires_at -->\n"));
// This is needed since the $user object is already destructed in _boost_ob_handler():
define('BOOST_USER_ID', $GLOBALS['user']->uid);
//////////////////////////////////////////////////////////////////////////////
// BOOST INCLUDES
require_once BOOST_PATH . '/boost.helpers.inc';
require_once BOOST_PATH . '/boost.api.inc';
//////////////////////////////////////////////////////////////////////////////
// DRUPAL API HOOKS
/**
* Implementation of hook_help(). Provides online user help.
*/
function boost_help($section) {
switch ($section) {
case 'admin/modules#name':
return t('boost');
case 'admin/modules#description':
return t('Provides a performance and scalability boost through caching Drupal pages as static HTML files.');
case 'admin/help#boost':
$file = drupal_get_path('module', 'boost') . '/README.txt';
if (file_exists($file)) {
return '<pre>' . implode("\n", array_slice(explode("\n", @file_get_contents($file)), 2)) . '</pre>';
}
break;
case 'admin/settings/boost':
return '<p>' . '</p>';
}
}
/**
* Implementation of hook_perm(). Defines user permissions.
*/
function boost_perm() {
return array(
'administer cache',
);
}
/**
* Implementation of hook_menu(). Defines menu items and page callbacks.
*/
function boost_menu($may_cache) {
$access = user_access('administer cache');
$items = array();
if ($may_cache) {
$items[] = array(
'path' => 'admin/settings/performance/boost',
'title' => t('Boost'),
'description' => t('Enable or disable page caching for anonymous users and set CSS and JS bandwidth optimization options.'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'boost_settings',
),
'access' => user_access('administer site configuration'),
);
// TODO: define menu actions for cache administration.
}
return $items;
}
/**
* Implementation of hook_init(). Performs page setup tasks.
*/
function boost_init() {
// Stop right here unless we're being called for an ordinary page request
if (strpos($_SERVER['SCRIPT_FILENAME'], 'index.php') === FALSE) {
return;
}
// TODO: check interaction with other modules that use ob_start(); this
// may have to be moved to an earlier stage of the page request.
if (!variable_get('cache', CACHE_DISABLED) && BOOST_ENABLED) {
// We only support GET requests by anonymous visitors:
global $user;
if (empty($user->uid) && $_SERVER['REQUEST_METHOD'] == 'GET') {
// Make sure no query string (in addition to ?q=) was set, and that
// the page is cacheable according to our current configuration:
if (count($_GET) == 1 && boost_is_cacheable($_GET['q'])) {
// In the event of errors such as drupal_not_found(), GET['q'] is
// changed before _boost_ob_handler() is called. Apache is going to
// look in the cache for the original path, however, so we need to
// preserve it.
$GLOBALS['_boost_path'] = $_GET['q'];
ob_start('_boost_ob_handler');
}
}
}
// Executed when saving Drupal's settings:
if (!empty($_POST['edit']) && $_GET['q'] == 'admin/settings') {
// Forcibly disable Drupal's built-in SQL caching to prevent any conflicts of interest:
variable_set('cache', CACHE_DISABLED);
// TODO: handle 'offline' site maintenance settings.
$old = variable_get('boost', '');
if (!empty($_POST['edit']['boost'])) {
// Ensure the cache directory exists or can be created
file_check_directory($_POST['edit']['boost_file_path'], FILE_CREATE_DIRECTORY, 'boost_file_path');
}
else {
if (!empty($old)) {
// the cache was previously enabled
if (boost_cache_expire_all()) {
drupal_set_message('Static cache files deleted.');
}
}
}
}
}
/**
* 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())) {
// Check that the page we're redirecting to really necessitates
// special handling, i.e. it doesn't have a query string:
extract(parse_url($destination));
$path = $path == base_path() ? '' : substr($path, strlen(base_path()));
if (empty($query)) {
// FIXME: call any remaining exit hooks since we're about to terminate?
// If no query string was previously set, add one just to ensure we
// don't serve a static copy of the page we're redirecting to, which
// would prevent the session messages from showing up:
$destination = url($path, 't=' . time(), $fragment, TRUE);
// Do what drupal_goto() would do if we were to return to it:
exit(header('Location: ' . $destination));
}
}
}
}
/**
* Implementation of hook_form_alter(). Performs alterations before a form
* is rendered.
*/
function boost_form_alter($form_id, &$form) {
// Alter Drupal's settings form by hiding the default cache enabled/disabled control (which will now always default to CACHE_DISABLED), and add our own control instead.
if ($form_id == 'system_performance_settings') {
require_once BOOST_PATH . '/boost.admin.inc';
$form['page_cache'] = boost_system_settings_form($form['page_cache']);
}
}
/**
* Implementation of hook_cron(). Performs periodic actions.
*/
function boost_cron() {
if (!BOOST_ENABLED) {
return;
}
if (boost_cache_expire_all()) {
watchdog('boost', t('Expired stale files from static page cache.'), WATCHDOG_NOTICE);
}
}
/**
* 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('node/' . $comment['nid'], TRUE);
}
break;
case 'publish':
case 'unpublish':
case 'delete':
if (!empty($comment->nid)) {
boost_cache_expire('node/' . $comment->nid, TRUE);
}
break;
}
}
/**
* Implementation of hook_nodeapi(). Acts on nodes defined by other modules.
*/
function boost_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
if (!BOOST_ENABLED) {
return;
}
switch ($op) {
case 'insert':
case 'update':
case 'delete':
// Expire all relevant node pages from the static page cache to prevent serving stale content:
if (!empty($node->nid)) {
boost_cache_expire('node/' . $node->nid, TRUE);
}
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 special cookie to prevent logged-in users getting served pages from the static page cache.
$expires = ini_get('session.cookie_lifetime');
$expires = !empty($expires) && is_numeric($expires) ? time() + (int) $expires : 0;
setcookie(BOOST_COOKIE, $user->uid, $expires, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure') == '1');
break;
case 'logout':
setcookie(BOOST_COOKIE, FALSE, time() - 86400, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure') == '1');
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('user/' . $account->uid);
}
// TODO: recursively delete user-specific cache directory.
break;
}
}
/**
* Implementation of hook_settings(). Declares administrative settings for a module.
*
* @deprecated in Drupal 5.0.
*/
function boost_settings() {
require_once BOOST_PATH . '/boost.admin.inc';
return system_settings_form(boost_settings_form());
}
//////////////////////////////////////////////////////////////////////////////
// OUTPUT BUFFERING CALLBACK
/**
* PHP output buffering callback for static page caching.
*
* NOTE: objects have already been destructed so $user is not available.
*/
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']));
// Check the currently set content type; at present we can't deal with anything else than HTML.
if (_boost_get_content_type() == 'text/html' && _boost_get_http_status() == 200) {
if (strlen($buffer) > 0) {
// Sanity check
boost_cache_set($GLOBALS['_boost_path'], $buffer);
}
}
// 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.
*/
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.
*/
function _boost_get_http_status($default = 200) {
static $regex = '!^HTTP/1.1\\s+(\\d+)!';
return (int) _boost_get_http_header($regex, $default);
}
function _boost_get_http_header($regex, $default = NULL) {
// The last header is the one that counts:
$headers = preg_grep($regex, explode("\n", drupal_set_header()));
if (!empty($headers) && preg_match($regex, array_pop($headers), $matches)) {
return $matches[1];
// found it
}
return $default;
// no such luck
}
//////////////////////////////////////////////////////////////////////////////
Functions
Name | Description |
---|---|
boost_comment | Implementation of hook_comment(). Acts on comment modification. |
boost_cron | Implementation of hook_cron(). Performs periodic actions. |
boost_exit | Implementation of hook_exit(). Performs cleanup tasks. |
boost_form_alter | Implementation of hook_form_alter(). Performs alterations before a form is rendered. |
boost_help | Implementation of hook_help(). Provides online user help. |
boost_init | Implementation of hook_init(). Performs page setup tasks. |
boost_menu | Implementation of hook_menu(). Defines menu items and page callbacks. |
boost_nodeapi | Implementation of hook_nodeapi(). Acts on nodes defined by other modules. |
boost_perm | Implementation of hook_perm(). Defines user permissions. |
boost_settings Deprecated | Implementation of hook_settings(). Declares administrative settings for a module. |
boost_taxonomy | Implementation of hook_taxonomy(). Acts on taxonomy changes. |
boost_user | Implementation of hook_user(). Acts on user account actions. |
_boost_get_content_type | Determines the MIME content type of the current page response based on the currently set Content-Type HTTP header. |
_boost_get_http_header | |
_boost_get_http_status | Determines the HTTP response code that the current page request will be returning by examining the HTTP headers that have been output so far. |
_boost_ob_handler | PHP output buffering callback for static page caching. |