Skip to content
boost.module 106 KiB
Newer Older
 * Provides static file caching for Drupal text output. Pages, Feeds, ect...
 */

//////////////////////////////////////////////////////////////////////////////
define('BOOST_ENABLED',              variable_get('boost_enabled', CACHE_DISABLED));
define('BOOST_TIME',                 time());

// Dir & File Structure
define('BOOST_MULTISITE_SINGLE_DB',  variable_get('boost_multisite_single_db', FALSE));
define('BOOST_ROOT_CACHE_DIR',       variable_get('boost_root_cache_dir', 'cache'));
define('BOOST_NORMAL_DIR',           variable_get('boost_normal_dir', ''));
define('BOOST_GZIP_DIR',             variable_get('boost_gzip_dir', 'gz'));
define('BOOST_PERM_NORMAL_DIR',      variable_get('boost_perm_normal_dir', ''));
define('BOOST_PERM_GZ_DIR',          variable_get('boost_perm_gz_dir', 'gz'));
define('BOOST_CHAR',                 variable_get('boost_char', '_'));
define('BOOST_PERM_CHAR',            variable_get('boost_perm_char', '_'));
define('BOOST_HOST',                 variable_get('boost_host', ''));
define('BOOST_FILE_EXTENSION',       variable_get('boost_file_extension', '.html'));
define('BOOST_XML_EXTENSION',        variable_get('boost_xml_extension', '.xml'));
Mike Carper's avatar
Mike Carper committed
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_FLUSH_DIR',            variable_get('boost_flush_dir', TRUE));
define('BOOST_MAX_PATH_DEPTH',       10);

// Cacheing Options
define('BOOST_CACHE_XML',            variable_get('boost_cache_xml', FALSE));
define('BOOST_CACHE_JSON',           variable_get('boost_cache_json', FALSE));
define('BOOST_CACHE_HTML',           variable_get('boost_cache_html', TRUE));
define('BOOST_CACHE_CSS',            variable_get('boost_cache_css', TRUE));
define('BOOST_CACHE_JS',             variable_get('boost_cache_js', TRUE));
define('BOOST_PUSH_HTML',            variable_get('boost_push_html', FALSE));
define('BOOST_PUSH_XML',             variable_get('boost_push_xml', FALSE));
define('BOOST_PUSH_JSON',            variable_get('boost_push_json', FALSE));
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_CACHEABILITY_OPTION',  variable_get('boost_cacheability_option', 0));
define('BOOST_CACHEABILITY_PAGES',   variable_get('boost_cacheability_pages', ''));

// Advanced Settings
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_ASYNCHRONOUS_OUTPUT',  variable_get('boost_asynchronous_output', TRUE));
define('BOOST_ONLY_ASCII_PATH',      variable_get('boost_only_ascii_path', TRUE));
define('BOOST_GZIP',                 variable_get('page_compression', TRUE));
define('BOOST_AGGRESSIVE_GZIP',      variable_get('boost_aggressive_gzip', FALSE));
define('BOOST_CLEAR_CACHE_OFFLINE',  variable_get('boost_clear_cache_offline', TRUE));
define('BOOST_HALT_ON_ERRORS',       variable_get('boost_halt_on_errors', FALSE));
define('BOOST_HALT_ON_MESSAGES',     variable_get('boost_halt_on_messages', TRUE));
define('BOOST_CACHE_QUERY',          variable_get('boost_cache_query', TRUE));
define('BOOST_IGNORE_FLUSH',         variable_get('boost_ignore_flush', 0));
define('BOOST_PERMISSIONS_FILE',     variable_get('boost_permissions_file', ''));
define('BOOST_PERMISSIONS_DIR',      variable_get('boost_permissions_dir', ''));
define('BOOST_OVERWRITE_FILE',       variable_get('boost_overwrite_file', FALSE));
define('BOOST_DISABLE_CLEAN_URL',    variable_get('boost_disable_clean_url', FALSE));
define('BOOST_VERBOSE',              variable_get('boost_verbose', 5));
define('BOOST_FLUSH_NODE_TERMS',     variable_get('boost_flush_node_terms', TRUE));
define('BOOST_MAX_TIMESTAMP',        variable_get('boost_max_timestamp', BOOST_TIME));

// Crawler Settings
define('BOOST_CHECK_BEFORE_CRON_EXPIRE', variable_get('boost_check_before_cron_expire', FALSE));
define('BOOST_CRAWL_ON_CRON',        variable_get('boost_crawl_on_cron', FALSE));
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));
define('BOOST_MAX_THREADS',          8);
define('BOOST_LOOPBACK_BYPASS',      BOOST_OVERWRITE_FILE ? variable_get('boost_loopback_bypass', FALSE) : FALSE);
// 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"));
// 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));

//////////////////////////////////////////////////////////////////////////////
// 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'] = '';
//////////////////////////////////////////////////////////////////////////////

/**
 * Implementation of hook_help(). Provides online user help.
 */
function boost_help($path, $arg) {
  switch ($path) {
      if (file_exists($file = drupal_get_path('module', 'boost') . '/README.txt')) {
        return '<pre>' . implode("\n", array_slice(explode("\n", @file_get_contents($file)), 2)) . '</pre>';
    case 'admin/settings/performance/boost':
      return '<p>' . t('') . '</p>'; // TODO: add help text.
  //hack to get drupal_get_messages before they are destroyed.
  $GLOBALS['_boost_message_count'] = count(drupal_get_messages(NULL, FALSE));
 * Implementation of hook_init(). Performs page setup tasks if page not cached.
  // Disable all caches when nocache is set
  if (isset($_GET['nocache'])) {
    $GLOBALS['conf']['cache'] = CACHE_DISABLED;
    $GLOBALS['_boost_cache_this'] = FALSE;
    return;
  }

  global $user, $base_path;
  //set variables
  $GLOBALS['_boost_router_item'] = _boost_get_menu_router();
  $GLOBALS['_boost_path'] = $_REQUEST['q'];
  // Make the proper filename for our query
  $GLOBALS['_boost_query'] = BOOST_CHAR;
    if ($key != 'q' && $key != 'destination') {
      $GLOBALS['_boost_query'] .= (($GLOBALS['_boost_query'] == BOOST_CHAR) ? '' : '&') . $key . '=' . $val;
  if (!empty($user->uid)) {
    boost_set_cookie($user);
    if (BOOST_DISABLE_CLEAN_URL) {
      $GLOBALS['conf']['clean_url'] = 0;
      db_query('TRUNCATE {cache_filter}');
      db_query('TRUNCATE {cache_menu}');
      cache_clear_all('*', 'cache_menu');
      cache_clear_all('*', 'cache_filter');
    }
  }

  // Make sure the page is/should be cached according to our current configuration
  if (   strpos($_SERVER['SCRIPT_FILENAME'], 'index.php') === FALSE
      || variable_get('site_offline', 0)
      || $_SERVER['REQUEST_METHOD'] != 'GET'
      || $_SERVER['SERVER_SOFTWARE'] === 'PHP CLI'
      || !BOOST_ENABLED
      || !boost_is_cacheable($GLOBALS['_boost_path'])
      || $GLOBALS['_boost_router_item']['page_callback'] == 'search404_page'
  // We only generate cached pages for anonymous visitors.
    if (BOOST_ENABLED != CACHE_AGGRESSIVE) {
      $GLOBALS['conf']['cache'] = CACHE_DISABLED;
    }
    register_shutdown_function('_boost_ob_handler');
    ob_start();
/**
 * Implementation of hook_exit(). Performs cleanup tasks.
 *
 * For POST requests by anonymous visitors, this adds a dummy query string
 * to any URL being redirected to using drupal_goto().
 *
 * This is pretty much a hack that assumes a bit too much familiarity with
 * what happens under the hood of the Drupal core function drupal_goto().
 *
 * It's necessary, though, in order for any session messages set on form
 * submission to actually show up on the next page if that page has been
 * cached by Boost.
 */
function boost_exit($destination = NULL) {
  // Check that hook_exit() was invoked by drupal_goto() for a POST request:
  if (!empty($destination) && $_SERVER['REQUEST_METHOD'] == 'POST') {

    // Check that we're dealing with an anonymous visitor. and that some
    // session messages have actually been set during this page request:
    global $user;
    if (empty($user->uid) && ($messages = drupal_set_message())) {
      // FIXME: call any remaining exit hooks since we're about to terminate?

      $query_parts = parse_url($destination);
      // Add a nocache parameter to query. Such pages will never be cached
      $query_parts['query'] .= (empty($query_parts['query']) ? '' : '&') . 'nocache=1';

      // Rebuild the URL with the new query string.  Do not use url() since
      // destination has presumably already been run through url().
      $destination = boost_glue_url($query_parts);

      // Do what drupal_goto() would do if we were to return to it:
      exit(header('Location: ' . $destination));
/**
 * Implementation of hook_menu().
 */
function boost_menu() {
  $items['admin/settings/performance/default'] = array(
    'title' => 'Performance',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['admin/settings/performance/boost'] = array(
    'title' => 'Boost Settings',
    'description' => 'Advanced boost configuration.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boost_admin_boost_performance_page'),
    'access arguments' => array('administer site configuration'),
    'weight' => 10,
    'type' => MENU_LOCAL_TASK,
    'file' => 'boost.admin.inc',
  );
  $items['admin/settings/performance/boost-rules'] = array(
    'title' => 'Boost htaccess rules generation',
    'description' => 'htaccess boost rules.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boost_admin_htaccess_page'),
    'access arguments' => array('administer site configuration'),
    'weight' => 12,
    'type' => MENU_LOCAL_TASK,
    'file' => 'boost.admin.inc',
  );
  $items['boost_stats.php'] = array(
Mike Carper's avatar
Mike Carper committed
    'page callback' => 'boost_stats_ajax_callback',
    'type' => MENU_CALLBACK,
    'access arguments' => array('access content'),
    'file path' => drupal_get_path('module', 'boost'),
Mike Carper's avatar
Mike Carper committed
    'file' => 'stats/boost_stats.ajax.inc',
  );
  $items['boost-crawler'] = array(
    'type' => MENU_CALLBACK,
    'access callback' => 1,
    'access arguments' => array('access content'),
    'file path' => drupal_get_path('module', 'boost'),
  );
/**
 * Implementation of hook_form_alter(). Performs alterations before a form
 * is rendered.
 */
function boost_form_alter(&$form, $form_state, $form_id) {
  switch ($form_id) {

    // Alter Drupal's system performance settings form by hiding the default
    // cache enabled/disabled control (which will now always default to
    // CACHE_DISABLED), and inject our own settings in its stead.
    case 'system_performance_settings':
      module_load_include('inc', 'boost', 'boost.admin');
      $form['page_cache'] = boost_admin_performance_page($form['page_cache']);
      $form['#submit'][] = 'boost_admin_performance_page_submit';
      $form['clear_cache']['clear']['#submit'][0] = 'boost_admin_clear_cache_submit';
      break;

    // Alter Drupal's site maintenance settings form in order to ensure that
    // the static page cache gets wiped if the administrator decides to take
    // the site offline.
    case 'system_site_maintenance_settings':
      module_load_include('inc', 'boost', 'boost.admin');
      $form['#submit'][] = 'boost_admin_site_offline_submit';
      break;

    // Alter Drupal's modules build form in order to ensure that
    // the static page cache gets wiped if the administrator decides to
    // change enabled modules
    case 'system_modules':
      module_load_include('inc', 'boost', 'boost.admin');
      $form['#submit'][] = 'boost_admin_modules_submit';
      break;

    // Alter Drupal's theme build form in order to ensure that
    // the static page cache gets wiped if the administrator decides to
    // change theme
    case 'system_themes_form':
      module_load_include('inc', 'boost', 'boost.admin');
      $form['#submit'][] = 'boost_admin_themes_submit';

      // Added below due to this bug: http://drupal.org/node/276615
      if (   variable_get('preprocess_css', FALSE)==TRUE
          && floatval(VERSION) <= 6.13
          && boost_cache_clear_all()
          ) {
        drupal_set_message(t('Boost: Static page cache cleared. See <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');
  }
}

/**
 * Implementation of hook_cron(). Performs periodic actions.
 */
function boost_cron() {
  $expire = TRUE;
  if (BOOST_CHECK_BEFORE_CRON_EXPIRE) {
    $expire = boost_has_site_changed();
  }
  if (!BOOST_LOOPBACK_BYPASS && variable_get('boost_expire_cron', TRUE) && $expire && boost_cache_db_expire()) {
    if (BOOST_VERBOSE >= 5) {
      watchdog('boost', 'Expired stale files from static page cache.', array(), WATCHDOG_NOTICE);
    }

  // Update Stats
  if (module_exists('statistics') && variable_get('boost_block_show_stats', FALSE)) {
    $block = module_invoke('statistics', 'block', 'view', 0);
    variable_set('boost_statistics_html', $block['content']);
  }
  if (BOOST_CRAWL_ON_CRON && !variable_get('site_offline', 0)) {
    boost_crawler_run((int)$expire);
/*
 * Implementation of hook_flush_caches(). Deletes all static files.
 */
function boost_flush_caches() {
  if (variable_get('cron_semaphore', FALSE)==FALSE && (variable_get('preprocess_css', FALSE)==TRUE || variable_get('preprocess_js', FALSE)==TRUE)) {
/**
 * Implementation of hook_comment(). Acts on comment modification.
 */
function boost_comment($comment, $op) {
  if (!BOOST_ENABLED) return;

  switch ($op) {
    case 'insert':
    case 'update':
      // Expire the relevant node page from the static page cache to prevent serving stale content:
      if (!empty($comment['nid'])) {
        $node = node_load($comment['nid']);
        boost_expire_node($node);
    case 'publish':
    case 'unpublish':
    case 'delete':
      if (!empty($comment->nid)) {
        $node = node_load($comment->nid);
        boost_expire_node($node);
  }
}

/**
 * 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':
/**
 * Implementation of hook_votingapi_insert().
 *
 * @param $votes
 *  array of votes
 */
function boost_votingapi_insert($votes) {
  if (!BOOST_ENABLED) return;

    $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;

    $node = node_load($vote['content_id']);
    boost_expire_node($node);
  }
}

/**
 * Expires a node from the cache.
 *
 * @param $node
 *  node object
 */
function boost_expire_node($node) {
  // Expire all relevant node pages from the static page cache to prevent serving stale content:
  if (!empty($node->nid)) {
    if ($node->promote == 1) {
      boost_cache_expire_derivative('<front>');
    }
    boost_cache_expire_derivative('node/' . $node->nid, TRUE);
  }

  // get terms and flush their page
  if (module_exists('taxonomy') && BOOST_FLUSH_NODE_TERMS) {
    $tids = boost_taxonomy_node_get_tids($node->nid);
    $filenames = array();
      $filenames = array_merge($filenames, boost_get_db_term($tid));
    }
    foreach ($filenames as $filename) {
/**
 * Return taxonomy terms given a nid.
 *
 * Needed because of a weird bug with CCK & node_load()
 *  http://drupal.org/node/545922
 */
function boost_taxonomy_node_get_tids($nid) {
  $vid = db_result(db_query('SELECT vid FROM {node} WHERE nid = %d', $nid));
  $result = db_query(db_rewrite_sql('SELECT t.tid FROM {term_node} r INNER JOIN {term_data} t ON r.tid = t.tid INNER JOIN {vocabulary} v ON t.vid = v.vid WHERE r.vid = %d ORDER BY v.weight, t.weight, t.name', 't', 'tid'), $vid);
  $tids = array();
  while ($term = db_result($result)) {
    $tids[] = $term;
  }
  return $tids;
}

/**
 * Implementation of hook_taxonomy(). Acts on taxonomy changes.
 */
function boost_taxonomy($op, $type, $term = NULL) {
  if (!BOOST_ENABLED) return;

  switch ($op) {
    case 'insert':
    case 'update':
    case 'delete':
      // TODO: Expire all relevant taxonomy pages from the static page cache to prevent serving stale content.
      break;
  }
}

/**
 * Implementation of hook_user(). Acts on user account actions.
 */
function boost_user($op, &$edit, &$account, $category = NULL) {
  if (!BOOST_ENABLED) return;

  global $user;
  switch ($op) {
    case 'login':
      // Set a special cookie to prevent authenticated users getting served
      // pages from the static page cache.
      boost_set_cookie($user, BOOST_TIME - 86400);
      break;
    case 'insert':
      // TODO: create user-specific cache directory.
      break;
    case 'delete':
      // Expire the relevant user page from the static page cache to prevent serving stale content:
      if (!empty($account->uid)) {
        boost_cache_expire_derivative('user/' . $account->uid);
      // TODO: recursively delete user-specific cache directory.
      break;
  }
}

/**
 * Implementation of hook_block().
 */
function boost_block($op = 'list', $delta = 0, $edit = array()) {
  global $user;

  switch ($op) {
    case 'list':
      return array(
        'status' => array(
          'info'   => t('Boost: Pages cache status'),
          'region' => 'right',
          'weight' => 10,
          'cache'  => BLOCK_NO_CACHE,
        ),
        'config' => array(
          'info'   => t('Boost: Pages cache configuration'),
          'region' => 'right',
          'weight' => 10,
          'cache'  => BLOCK_NO_CACHE,
        ),
        'stats' => array(
          'info'   => t('Boost: AJAX core statistics'),
          'region' => 'right',
          'weight' => 10,
          'cache'  => BLOCK_NO_CACHE,
        ),
      );
        $form['boost_block_show_stats'] = array(
Mike Carper's avatar
Mike Carper committed
        '#title' => t('Display Statistics.'),
        '#default_value' => variable_get('boost_block_show_stats', FALSE),
Mike Carper's avatar
Mike Carper committed
        '#description' => t('If false, uses Javascript to hide the block via "parent().parent().hide()".'),
        $form['boost_block_cache_stats_block'] = array(
        '#type' => 'checkbox',
        '#title' => t('Cache Statistics Block'),
        '#default_value' => variable_get('boost_block_cache_stats_block', FALSE),
        );
        variable_set('boost_block_show_stats', $edit['boost_block_show_stats']);
        variable_set('boost_block_cache_stats_block', $edit['boost_block_cache_stats_block']);
    case 'view':
      $block = array();
      switch ($delta) {
        case 'status':
          // Don't show the block to anonymous users, nor on any pages that
          // aren't even cacheable to begin with (e.g. admin/*).
          if (!empty($user->uid) && boost_is_cacheable($GLOBALS['_boost_path'])) {
            $output = t('This page is being served <strong>live</strong> to anonymous visitors, as it is not currently in the static page cache.');

            if (boost_is_cached($GLOBALS['_boost_path'])) {
              $ttl = boost_db_get_ttl(boost_file_path($GLOBALS['_boost_path']));
              $output = t('This page is being served to anonymous visitors from the static page cache.') . ' ';
              $output .= $ttl < 0 ? t('<strong>The cached copy expired %interval ago.</strong>', array('%interval' => format_interval(abs($ttl)))) : t('The cached copy will expire in %interval.', array('%interval' => format_interval(abs($ttl))));
              $output .=  drupal_get_form('boost_block_flush_form');
            $error = FALSE;
            if (function_exists('error_get_last')) {
              $error = error_get_last();
            }
            $drupal_msg = max(count(drupal_get_messages(NULL, FALSE)), $GLOBALS['_boost_message_count']);
            if ((BOOST_HALT_ON_ERRORS || BOOST_HALT_ON_MESSAGES) && ($error || $drupal_msg != 0)) {
              $output = t('There are <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')));
                $output .= t(' MESSAGES: %msg <br /> !performance', array('%msg' => $drupal_msg, '!performance' => l(t('Turn Off Error Checking'), 'admin/settings/performance')));

            $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'])) {
            $block['subject'] = '';
            $block['content'] = theme('boost_cache_status', -1, drupal_get_form('boost_block_db_settings_form'));
          }
          break;
        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)
              || !module_exists('statistics')
              )) {
Mike Carper's avatar
Mike Carper committed
            $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');
function boost_block_flush_form() {
  $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'),
function boost_block_form_flush_submit(&$form_state, $form) {
  boost_cache_expire_derivative($form['values']['path'], TRUE);
}

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']));
  $router_item = isset($GLOBALS['_boost_router_item']) ? $GLOBALS['_boost_router_item'] : _boost_get_menu_router();
  $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(
    '#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_arguments'],
      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_arguments'],
  );
  $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) {
  if ($form['values']['id']) {
    boost_remove_settings_db($form['values']['id_value']);
  }
  if ($form['values']['type']) {
    boost_remove_settings_db($form['values']['type_value']);
  }
  if ($form['values']['container']) {
    boost_remove_settings_db($form['values']['container_value']);
  }
}


/**
 * 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)) {
  }

  // 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'];
  }

  $page_js = array(
    'boost' => array(
      'nid' => $nid,
      'q' => $q,
      'title' => $title,
    ),
  );
$.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) {
Mike Carper's avatar
Mike Carper committed
    if (contents == 'NULL') {
      $(id).parent().parent().hide();
    }
    else {
      $(id).html(contents);
    }

  // 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' . '&amp;nid='. $nid . '&amp;title='. $title . '&amp;q='. $q . '" alt="" /></div></noscript>';
/**
 * 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>';
}

//////////////////////////////////////////////////////////////////////////////
// Output buffering callback
 * PHP output buffering callback for static page caching.
function _boost_ob_handler() {
  $buffer = ob_get_contents();

  // Ensure we're in the correct working directory, since some web servers (e.g. Apache) mess this up here.
  chdir(dirname($_SERVER['SCRIPT_FILENAME']));

  if (function_exists('error_get_last')) {
    if (BOOST_HALT_ON_ERRORS && $error = error_get_last()) {
    switch ($error['type']) {
      case E_NOTICE: //Ignore run-time notices
      case E_USER_NOTICE: //Ignore user-generated notice message
      //case E_DEPRECATED: //Ignore run-time notices
      //case E_USER_DEPRECATED: //Ignore user-generated notice message
      default: //Do not cache page on all other errors
        if (BOOST_VERBOSE >= 3) {
          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);
        }
  if (BOOST_HALT_ON_MESSAGES && $GLOBALS['_boost_message_count'] != 0) {
    if (BOOST_VERBOSE >= 3) {
      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);
    }
  // Check the currently set content type and the HTTP response code. only cache
  // 'text/*' pages that were output with a 200 OK status. If it didn't get a
  // 200 then TODO: remove that entry from the cache.
  if ($GLOBALS['_boost_cache_this'] && !empty($buffer) > 0) {
    switch (_boost_get_content_type()) {
      case 'text/html':
        if (boost_check_http_status()) {
          boost_cache_set($GLOBALS['_boost_path'], $buffer, BOOST_FILE_EXTENSION);
          boost_cache_css_js_files($buffer);
        }
        else {
//           boost_cache_expire_derivative($path);
//           $filename = boost_file_path($path, TRUE, BOOST_FILE_EXTENSION);
//           if ($filename) {
//             boost_cache_kill($filename);
//             boost_remove_db($filename);
//           }
      case 'application/rss':
      case 'text/xml':
      case 'application/rss+xml':
        if (boost_check_http_status()) {
          boost_cache_set($GLOBALS['_boost_path'], $buffer, BOOST_XML_EXTENSION);
        }
        else {
//           boost_cache_expire_derivative($path);
//           $filename = boost_file_path($path, TRUE, BOOST_XML_EXTENSION);
//           if ($filename) {
//             boost_cache_kill($filename);
//             boost_remove_db($filename);
//           }
        if (boost_check_http_status()) {
          boost_cache_set($GLOBALS['_boost_path'], $buffer, BOOST_JSON_EXTENSION);
//           boost_cache_expire_derivative($path);
//           $filename = boost_file_path($path, TRUE, BOOST_JSON_EXTENSION);
//           if ($filename) {
//             boost_cache_kill($filename);
//             boost_remove_db($filename);
//           }
/**
 * See's if this http status was returned.
 *
 * @param $default
 *   Look for a 200 status
 */
function boost_check_http_status($status = 200) {
  if (_boost_get_http_status() != $status || _boost_get_pressflow_http_status() != $status) {
    return FALSE;
  }
  else {
    return TRUE;
  }
}

/**
 * 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.
 *
 * @param $default
 *   Return this value if it can't be found
 */
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.
 *   If none found, return 200
 */
function _boost_get_http_status($default = 200) {
  static $regex = '!^HTTP/1.1\s+(\d+)!';
  return (int)_boost_get_http_header($regex, $default);
}