Skip to content
boost.module 91.8 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_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_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_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', '.js'));
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_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_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));
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_ROOT_FILE',            variable_get('boost_root_file', '.boost'));
define('BOOST_FLUSH_DIR',            variable_get('boost_flush_dir', TRUE));
define('BOOST_TIME',                 time());
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));
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_CRAWLER_BATCH_SIZE',   variable_get('boost_crawler_batch_size', 25));
define('BOOST_CRAWL_URL_ALIAS',      variable_get('boost_crawl_url_alias', FALSE));
global $base_url;
define('BOOST_CRAWLER_SELF',         $base_url . '/' . 'boost-crawler?nocache=1&key=' . variable_get('boost_crawler_key', FALSE));
define('BOOST_MAX_THREAD_TIME',      1.5 * boost_average_time() * BOOST_CRAWLER_THREADS * BOOST_CRAWLER_BATCH_SIZE);
define('BOOST_MAX_THREADS',          8);
// 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"));

// This is needed since the $user object is already destructed in _boost_ob_handler():
define('BOOST_USER_ID',              @$GLOBALS['user']->uid);
//////////////////////////////////////////////////////////////////////////////
// Global variables

//$GLOBALS['_boost_path'] = '';
//$GLOBALS['_boost_query'] = '';
//$GLOBALS['_boost_message_count'] = '';
//$GLOBALS['_boost_cache_this'] = '';

//////////////////////////////////////////////////////////////////////////////

/**
 * 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.
  global $user, $base_path;
  //set variables
  $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
      || isset($_GET['nocache'])
      || !boost_is_cacheable($GLOBALS['_boost_path'])
  // We only generate cached pages for anonymous visitors.
    if (BOOST_ENABLED != CACHE_AGGRESSIVE) {
      $GLOBALS['conf']['cache'] = CACHE_DISABLED;
    }
/**
 * 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['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['page_cache']['#title'] = t('Anonymous page caching');
      $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';
      $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) {
        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');
        }
      }
  }
}

/**
 * Implementation of hook_cron(). Performs periodic actions.
 */
function boost_cron() {
  $expire = TRUE;
  if (BOOST_CHECK_BEFORE_CRON_EXPIRE) {
    $expire = boost_has_site_changed();
  }
  if (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)) {
/*
 * 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'])) {
        boost_cache_expire_derivative('node/' . $comment['nid'], TRUE);
    case 'publish':
    case 'unpublish':
    case 'delete':
      if (!empty($comment->nid)) {
        boost_cache_expire_derivative('node/' . $comment->nid, TRUE);
  }
}

/**
 * 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 ($node->promote == 1) {
          boost_cache_expire_derivative('<front>');
        }
        boost_cache_expire_derivative('node/' . $node->nid, TRUE);
      if (module_exists('taxonomy') && BOOST_FLUSH_NODE_TERMS) {
        $tids = boost_taxonomy_node_get_tids($node->nid);
        foreach($tids as $tid) {
          $filenames = array_merge($filenames, boost_get_db_term($tid));
        }
        foreach($filenames as $filename) {
          boost_cache_kill($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 .= 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_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 && ($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 = _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.
 *
 * 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']));

  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_ERRORS && $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. We're
  // going to be exceedingly conservative here and only cache 'text' pages that
  // were output with a 200 OK status. If it didn't get a 200 then remove that
  // entry from the cache.
  if ($GLOBALS['_boost_cache_this'] && strlen($buffer) > 0) {
    switch (_boost_get_content_type()) {
      case 'text/html':
        if (_boost_get_http_status() == 200) {
          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_get_http_status() == 200) {
          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_get_http_status() == 200) {
          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);
//           }
  }

  // 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.
 *
 * @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.
 */
function _boost_get_http_status($default = 200) {
  static $regex = '!^HTTP/1.1\s+(\d+)!';
  return (int)_boost_get_http_header($regex, $default);
}
/**
 * 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:
  $headers = preg_grep($regex, explode("\n", drupal_get_headers()));
  if (!empty($headers) && preg_match($regex, array_pop($headers), $matches)) {

//////////////////////////////////////////////////////////////////////////////
// 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.
 *
 * $path = $GLOBALS['_boost_path'] most of the time
function boost_is_cacheable($path) {
  $path = (empty($path)) ? variable_get('site_frontpage', 'node') : $path;
  $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
  // Under no circumstances should the incoming path contain '..' or null
  // bytes; we also limit the maximum directory nesting depth of the path
  if (   $normal_path == 'user'
      || preg_match('!^user/(login|register|password)!', $normal_path)
      || preg_match('!^admin!', $normal_path)
      || preg_match('!comment/reply$!', $normal_path)
      || strpos($path, '..') !== FALSE
      || strpos($path, "\0") !== FALSE
      || count(explode('/', $path)) > BOOST_MAX_PATH_DEPTH
  if (!BOOST_CACHE_XML && (preg_match('!/feed$!', $normal_path) || preg_match('!\.xml$!', $normal_path))) {
    return FALSE;
  }
  if (!BOOST_CACHE_QUERY && $GLOBALS['_boost_query'] != BOOST_CHAR) {
  // Don't cache path if it can't be served by apache.
  if (BOOST_ONLY_ASCII_PATH) {
    if (preg_match('@[^/a-z0-9_\-&=,\.:]@i', $path)) {
      return FALSE;
    }
  }

  // Check for reserved characters if on windows
  // http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
  // " * : < > |
  $chars = '"*:<>|';
  if (stristr(PHP_OS, 'WIN') && preg_match("/[".$chars."]/", $normal_path)) {
    return FALSE;
  }

  // See http://api.drupal.org/api/function/block_list/6
  // Match the user's cacheability settings against the path
  if (BOOST_CACHEABILITY_PAGES) {
    if (BOOST_CACHEABILITY_OPTION < 2) {
      $page_match = drupal_match_path($path, BOOST_CACHEABILITY_PAGES);
      if ($path != $_GET['q']) {
        $page_match = $page_match || drupal_match_path($_GET['q'], BOOST_CACHEABILITY_PAGES);
      }
      // When BOOST_CACHEABILITY_OPTION has a value of 0, boost will cache
      // all pages except those listed in BOOST_CACHEABILITY_PAGES. When set
      // to 1, boost will cache only on those pages listed in BOOST_CACHEABILITY_PAGES.
      $page_match =  !(BOOST_CACHEABILITY_OPTION xor $page_match);