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

//////////////////////////////////////////////////////////////////////////////
define('BOOST_TIME',                 time());
Mike Carper's avatar
Mike Carper committed
define('BOOST_MAX_TIMESTAMP',        variable_get('boost_max_timestamp', BOOST_TIME));
define('BOOST_ENABLED',              variable_get('boost_enabled', CACHE_NORMAL));
define('BOOST_GZIP',                 function_exists('gzencode') ? variable_get('page_compression', TRUE) : FALSE);
Mike Carper's avatar
Mike Carper 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):
define('BOOST_COOKIE',               variable_get('boost_cookie', 'DRUPAL_UID'));

// This line is appended to the generated static files; it is very useful
// for troubleshooting (e.g. determining whether one got the dynamic or
// static version):
define('BOOST_BANNER',               variable_get('boost_banner', "Page cached by Boost @ %cached_at, expires @ %expires_at"));

// Caching Options
define('BOOST_CACHE_LIFETIME',       variable_get('boost_cache_lifetime', 3600));
define('BOOST_CACHE_XML_LIFETIME',   variable_get('boost_cache_xml_lifetime', 3600));
define('BOOST_CACHE_JSON_LIFETIME',  variable_get('boost_cache_json_lifetime', 3600));
define('BOOST_CACHE_QUERY',          variable_get('boost_cache_query', TRUE));
define('BOOST_CACHE_HTML',           variable_get('boost_cache_html', TRUE));
define('BOOST_CACHE_XML',            variable_get('boost_cache_xml', FALSE));
define('BOOST_CACHE_JSON',           variable_get('boost_cache_json', FALSE));
define('BOOST_CACHE_CSS',            variable_get('boost_cache_css', TRUE));
define('BOOST_CACHE_JS',             variable_get('boost_cache_js', TRUE));
define('BOOST_CACHEABILITY_OPTION',  variable_get('boost_cacheability_option', 0));
define('BOOST_CACHEABILITY_PAGES',   variable_get('boost_cacheability_pages', ''));
// Views
define('BOOST_VIEWS_LIST_BEHAVIOR',  variable_get('boost_views_list_behavior', 0));

// Dir & File Structure
define('BOOST_ROOT_CACHE_DIR',       variable_get('boost_root_cache_dir', 'cache'));
define('BOOST_MULTISITE_SINGLE_DB',  variable_get('boost_multisite_single_db', TRUE));
define('BOOST_NORMAL_DIR',           variable_get('boost_normal_dir', 'normal'));
define('BOOST_GZIP_DIR',             variable_get('boost_gzip_dir', 'normal'));
define('BOOST_PERM_NORMAL_DIR',      variable_get('boost_perm_normal_dir', 'perm'));
define('BOOST_PERM_GZ_DIR',          variable_get('boost_perm_gz_dir', 'perm'));
define('BOOST_CHAR',                 variable_get('boost_char', '_'));
define('BOOST_PERM_CHAR',            variable_get('boost_perm_char', '_'));
define('BOOST_HOST',                 variable_get('boost_host', ''));
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_MAX_PATH_DEPTH',       10);

// Advanced Settings
Mike Carper's avatar
Mike Carper committed
define('BOOST_CHECK_BEFORE_CRON_EXPIRE', variable_get('boost_check_before_cron_expire', FALSE));
define('BOOST_PRE_PROCESS_FUNCTION', variable_get('boost_pre_process_function', ''));
define('BOOST_EXIT_IN_HOOK_EXIT',    variable_get('boost_exit_in_hook_exit', TRUE));
define('BOOST_FLUSH_ALL_MULTISITE',  variable_get('boost_flush_all_multisite', TRUE));
define('BOOST_ONLY_ASCII_PATH',      variable_get('boost_only_ascii_path', TRUE));
define('BOOST_SET_FILE_ENCODING',    variable_get('boost_set_file_encoding', ''));
Mike Carper's avatar
Mike Carper committed
define('BOOST_AGGRESSIVE_COOKIE',     variable_get('boost_aggressive_cookie', TRUE));
Mike Carper's avatar
Mike Carper committed
define('BOOST_ASYNCHRONOUS_OUTPUT',  variable_get('boost_asynchronous_output', TRUE));
define('BOOST_PAGER_CLEAN',          variable_get('boost_pager_clean', FALSE));
define('BOOST_FLUSH_DIR',            variable_get('boost_flush_dir', FALSE));
Mike Carper's avatar
Mike Carper committed
define('BOOST_FLUSH_CCK_REFERENCES', variable_get('boost_flush_cck_references', TRUE));
define('BOOST_FLUSH_FRONT',          variable_get('boost_flush_front', TRUE));
Mike Carper's avatar
Mike Carper committed
define('BOOST_FLUSH_NODE_TERMS',     variable_get('boost_flush_node_terms', TRUE));
define('BOOST_FLUSH_MENU_ITEMS',     variable_get('boost_flush_menu_items', 0));
define('BOOST_FLUSH_VIEWS',          variable_get('boost_flush_views', TRUE));
define('BOOST_FLUSH_VIEWS_INSERT',   variable_get('boost_flush_views_insert', TRUE));
define('BOOST_FLUSH_VIEWS_UPDATE',   variable_get('boost_flush_views_update', FALSE));
define('BOOST_CLEAR_CACHE_OFFLINE',  variable_get('boost_clear_cache_offline', FALSE));
Mike Carper's avatar
Mike Carper committed
define('BOOST_OVERWRITE_FILE',       variable_get('boost_overwrite_file', FALSE));
define('BOOST_HALT_ON_ERRORS',       variable_get('boost_halt_on_errors', FALSE));
define('BOOST_HALT_ON_MESSAGES',     variable_get('boost_halt_on_messages', TRUE));
Mike Carper's avatar
Mike Carper committed
define('BOOST_DISABLE_CLEAN_URL',    variable_get('boost_disable_clean_url', FALSE));
define('BOOST_AGGRESSIVE_GZIP',      BOOST_GZIP ? variable_get('boost_aggressive_gzip', TRUE) : FALSE);
define('BOOST_PERMISSIONS_FILE',     variable_get('boost_permissions_file', ''));
define('BOOST_PERMISSIONS_DIR',      variable_get('boost_permissions_dir', ''));
define('BOOST_EXPIRE_NO_FLUSH',      variable_get('boost_expire_no_flush', FALSE));
Mike Carper's avatar
Mike Carper committed
define('BOOST_VERBOSE',              variable_get('boost_verbose', 5));
define('BOOST_IGNORE_SAFE_WARNING',  variable_get('boost_ignore_safe_warning', FALSE));
define('BOOST_IGNORE_SUBDIR_LIMIT',  variable_get('boost_ignore_subdir_limit', TRUE));
define('BOOST_IGNORE_HTACCESS_WARNING',  variable_get('boost_ignore_htaccess_warning', FALSE));
define('BOOST_NO_DATABASE',          variable_get('boost_no_database', FALSE));
// Domain Whitelist/Blacklist Settings
define('BOOST_DOMAIN_NO_LISTS',      0);
define('BOOST_DOMAIN_WHITELIST_ONLY',1);
define('BOOST_DOMAIN_BLACKLIST_ONLY',2);
define('BOOST_DOMAIN_BOTH_LISTS',    3);

// Crawler Settings
define('BOOST_CRAWL_ON_CRON',        variable_get('boost_crawl_on_cron', FALSE));
define('BOOST_LOOPBACK_BYPASS',      BOOST_CRAWL_ON_CRON ? variable_get('boost_loopback_bypass', FALSE) : FALSE);
define('BOOST_PUSH_HTML',            variable_get('boost_push_html', TRUE));
define('BOOST_PUSH_XML',             variable_get('boost_push_xml', TRUE));
define('BOOST_PUSH_JSON',            variable_get('boost_push_json', TRUE));
define('BOOST_CRAWL_URL_ALIAS',      variable_get('boost_crawl_url_alias', FALSE));
define('BOOST_CRAWL_DB_IMPORT_SIZE', variable_get('boost_crawl_db_import_size', 10000));
define('BOOST_CRAWLER_THROTTLE',     variable_get('boost_crawler_throttle', 0));
define('BOOST_CRAWLER_THREADS',      variable_get('boost_crawler_threads', 2));
define('BOOST_MAX_THREADS',          8);
// 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') + 0.1)/(2 * boost_average_time()))));
define('BOOST_MAX_THREAD_TIME',      max(300, 2 * boost_average_time() * BOOST_CRAWLER_THREADS * BOOST_CRAWLER_BATCH_SIZE));

// define doesn't work with an array
$GLOBALS['_boost']['verbose_option_defaults'] = array(
  'boost_init_404',
  'boost_exit_headers',
  'boost_cron_expire',
  'boost_view_insert',
  'boost_expire_node',
  'boost_user_op',
  'boost_user_delete',
  'boost_block_flush_submit',
  'boost_block_settings_submit',
  'boost_block_rm_settings_submit',
  'boost_ob_handler_info',
  'boost_ob_handler_redirect',
  'boost_cache_clear_all',
  'boost_cache_flush_filename',
  'boost_cache_expire_router',
  'boost_cache_expire_all_db_list',
  'boost_cache_expire_all_db_count',
  'boost_cache_set_node_relationships',
  'boost_crawler_run_stop',
  'boost_crawler_run_rogue',
  'boost_crawler_run_sleep',
  'boost_crawler_run_shutdown',
  'boost_crawler_run_kill',
  'boost_crawler_run_startup',
  'boost_crawler_run_restart',
  'boost_crawler_run_done',
  'boost_crawler_run_start',
);
$GLOBALS['_boost']['verbose_option_selected'] = array_flip(variable_get('boost_verbose_refined', $GLOBALS['_boost']['verbose_option_defaults']));
//////////////////////////////////////////////////////////////////////////////
// Global variables

//$GLOBALS['_boost_path'] = '';
//$GLOBALS['_boost_query'] = '';
//$GLOBALS['_boost_message_count'] = '';
//$GLOBALS['_boost_cache_this'] = '';
//$GLOBALS['_boost_max_execution_time'] = '';
//$GLOBALS['_boost_output_buffering'] = '';
//$GLOBALS['_boost_default_socket_timeout'] = '';
//$GLOBALS['_boost_router_item'] = '';
//$GLOBALS['_boost_relationships'] = '';
//$GLOBALS['_boost_nid'] = '';
//////////////////////////////////////////////////////////////////////////////

/**
 * 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>' . '' . '</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));
/**
 * Get some basic node attribues from the database given node id.
 *
 * @param $nid
 *  node ID
 * @return node object
 *  node->nid
 *  node->type
 *  node->language
 *  node->path
 *  node->domain
 */
function boost_node_get_basics($nid) {
  static $nodes = array();

  // Is the node statically cached?
  if (isset($nodes[$nid])) {
    return $nodes[$nid];
  }

  // Retrieve node nid, type, language.
  $op = 'load';
  $node = db_fetch_object(db_query("SELECT nid, type, language FROM {node} WHERE nid = %d", $nid));
  // Get path info
  if (module_exists('path')) {
    path_nodeapi($node, $op, NULL, NULL);
  }
  // Get domain access info
  if (module_exists('domain')) {
    domain_nodeapi($node, $op, NULL, NULL);
  }
  // Get taxonomy tid info
  if (module_exists('taxonomy')) {
    $node->tids = boost_taxonomy_node_get_tids($node->nid);
  }
  $nodes[$nid] = $node;
  return $nodes[$nid];
}

/**
 * Implementation of hook_ctools_render_alter().
 *
 * Needed to get the nodes that are inside of a panel's contex
 */
function boost_ctools_render_alter($info, $page, $args, $contexts, $task, $subtask, $handler) {
  // return if page not going to be cached or if database turned off
  if (!$GLOBALS['_boost_cache_this'] || BOOST_NO_DATABASE) {
    return;
  }

  foreach ($handler->conf['display']->context as $context) {
    if ($context->type == 'node') {
      $node = $context->data;
      // skip if node type is not set
      if (!isset($node->type)) {
        continue;
      }
      // set data
      $relationship = array(
        'child_page_callback' => 'node',
        'child_page_type' => $node->type,
        'child_page_id' => $node->nid,
      );
      if (BOOST_VERBOSE >= 9) {
        $relationship['debug'] = array(
          'panel-task' => $handler->task,
          'panel-subtask' => $handler->subtask,
          'node-path' => $node->path,
        );
      }
      // send to global
      $GLOBALS['_boost_relationships'][] = $relationship;
    }
  }
}

/**
 * Implementation of hook_views_pre_render().
 *
 * This is called right before the render process. Used to grab the NID's listed
 * in this view, and set the view node relationship in the database.
 *
 * @param &$view
 *  reference to the view being worked on
 */
function boost_views_pre_render(&$view) {
  // return if not a view, page not going to be cached, or if database turned off
  if (is_null($view) || !$GLOBALS['_boost_cache_this'] || BOOST_NO_DATABASE) {
    return;
  }

  // return if view doesn't belong in the set of views to search
  $views = boost_views_get_valid_array();
  $hash = $view->name . ' - ' . $view->current_display;
  if (!array_key_exists($hash, $views)) {
    return;
  }

  foreach ($view->result as $item) {
    // skip if nid is not a number
    if (!is_numeric($item->nid)) {
      continue;
    }

    $node = boost_node_get_basics($item->nid);
    // skip if node type is not set
    if (!isset($node->type)) {
      continue;
    }
    // set data
      'child_page_callback' => 'node',
      'child_page_type' => $node->type,
      'child_page_id' => $item->nid,
    );
    if (BOOST_VERBOSE >= 9) {
        'view-name' => $view->name,
        'view-display' => $view->current_display,
        'node-path' => $node->path,
      );
    }
    // send to global
    $GLOBALS['_boost_relationships'][] = $relationship;
  }
}

/**
 * Genrate a list of views based off of defaults.
 *
 * Views that have a path, can be cached by boost & anonymous users can access
 *
 * @return array
 *    (view_name . ' - ' . display_name . ' - /' . path => Enabled/Disabled)
 */
function boost_views_generate_default_list() {
  $account = user_load(0);
  $views = views_get_all_views();
  $enabled = array();
  $disabled = array();
  foreach ($views as $view_name => $view) {
    // disabled views get nothing.
    if (!empty($view->disabled)) {
      unset($views[$view_name]);
      continue;
    }

    $view->init_display();
    foreach ($view->display as $display_id => $display) {
      // Anonymous users can access view
      if (!$view->access($display_id, $account)) {
        continue;
      }
      $hash = $view_name . ' - ' . $display_id;

      // Use the default view
      if (strcmp($display_id, 'default') == 0) {
        $enabled[$hash] = $hash;
      }
      // View with a path
      elseif (isset($display->display_options['path'])) {
        $path = $display->display_options['path'];
        $hash = $view_name . ' - ' . $display_id . ' - /' . $path;
        // Path is cacheable via boost & does not take arguments
        if (boost_is_cacheable($path) && !stristr($path, '%')) {
          $enabled[$hash] = $hash;
        }
        // Path is not cacheable via boost or view takes arguments
      // All other displays mark as disabled
      else {
        $disabled[$hash] = 0;
      }
  ksort($disabled);
  natcasesort($enabled);
  $checkboxes = array_merge(array('line-break' => 0), $disabled, $enabled);
  return $checkboxes;
}

/**
 * Genrate a list of valid views that boost will search for new content.
 *
 * @return array
 */
function boost_views_get_valid_list($fresh = FALSE) {
  // Load saved default values; if not saved, save it.
  $defaults = variable_get('boost_views_list_default', FALSE);
  if (!$defaults || $fresh) {
    $defaults = boost_views_generate_default_list();
    variable_set('boost_views_list_default', $defaults);
  }

  // Load custom settings
  $list = variable_get('boost_views_list_custom', array());
  // Load defaults. http://php.net/operators.array
  $list += $defaults;


  return $list;
}

/**
 * Genrate a list of valid views that boost will search for new content.
 *
 * @return array
 */
function boost_views_get_valid_array() {
  $list = boost_views_get_valid_list();
  $views = array();
  foreach ($list as $hash => $enabled) {
    if ($enabled) {
      $info = explode(' - ', $hash);
      $page_type = $info[0];
      $page_id = $info[1];
      $key = $page_type . ' - ' . $page_id;
      $views[$key]['page_type'] = $page_type;
      $views[$key]['page_id'] = $page_id;
    }
  }
  return $views;
/**
 * Implementation of hook_views_pre_view().
 *
 * Hack due to this issue: http://drupal.org/node/619852
 */
function boost_views_pre_view(&$view) {
}

/**
* Send out a fast 404 and exit.
*/
function boost_fast404() {
  global $base_path;
  if (!headers_sent()) {
    header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
  }
  print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
  print '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">';
  print '<head><title>' . t('404 Not Found') . '</title></head>';
  print '<body><h1>' . t('Not Found') . '</h1>';
  print '<p>' . t('The requested URL was not found on this server.') . '</p>';
  print '<p><a href="' . $base_path . '">' . t('Home') . '</a></p>';
  print '</body></html>';
  exit();
}

 * Implementation of hook_init(). Performs page setup tasks if page not cached.
  global $user, $base_path, $base_root, $base_url;

  // Force lowercase host name
  $base_root = strtolower($base_root);
  $parts = parse_url($base_url);
  $parts['host'] = strtolower($parts['host']);
  $base_url = boost_glue_url($parts);
  // Make sure this is the correct domain
  // Only works if $base_url is set in settings.php
  if (strcmp($parts['host'], strtolower($_SERVER['HTTP_HOST'])) != 0) {
    $GLOBALS['conf']['cache'] = CACHE_DISABLED;
    $GLOBALS['_boost_cache_this'] = FALSE;
    return;
  }

  // Check if Drupal is started from index.php - could cause problems with other
  // contrib modules like ad module.
  if (strpos($_SERVER['SCRIPT_FILENAME'], 'index.php') !== FALSE) {
    $uid = isset($user->uid) ? $user->uid : 0;
    // Remove Boost cookie at logout if it still exists
    if (BOOST_AGGRESSIVE_COOKIE && isset($_COOKIE[BOOST_COOKIE]) && $uid == 0) {
      boost_set_cookie($uid, BOOST_TIME - 86400);
    }
    // Remove Boost cookie if set to -1
    elseif (isset($_COOKIE[BOOST_COOKIE]) && $_COOKIE[BOOST_COOKIE] == '-1') {
      boost_set_cookie($uid, BOOST_TIME - 86400);
    }
    // Set Boost cookie if it doesn't exists and user is logged in
    elseif (BOOST_AGGRESSIVE_COOKIE && !isset($_COOKIE[BOOST_COOKIE]) && $uid != 0) {
      boost_set_cookie($uid);
    }
  // Disable all caches when nocache is set
  if (isset($_GET['nocache'])) {
    $GLOBALS['conf']['cache'] = CACHE_DISABLED;
    $GLOBALS['_boost_cache_this'] = FALSE;
    return;
  }

  // Make sure this is not a 404 redirect from the htaccesss file
  $path = explode($base_path, request_uri());
  array_shift($path);
  $path = implode($base_path, $path);
  $path = explode('?', $path);
  $path = array_shift($path);
  if ($path != '' && empty($_REQUEST['q']) && !stristr($path, '.php') && isset($_SERVER['REDIRECT_STATUS']) && $_SERVER['REDIRECT_STATUS'] == 404) {
    $GLOBALS['conf']['cache'] = CACHE_DISABLED;
    $GLOBALS['_boost_cache_this'] = FALSE;


    if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_init_404'])) {
      watchdog('boost', '404 received from server via redirect, going to send a 404. Info: !output', array('!output' => boost_print_r($_SERVER, TRUE, TRUE)));
  if (empty($_REQUEST['q'])) {
  else {
    $GLOBALS['_boost_path'] = $_REQUEST['q'];
  }

  // Remove anchor tags from url.
  if (stristr($GLOBALS['_boost_path'], '#')) {
    $GLOBALS['_boost_path'] = array_shift(explode('#', $GLOBALS['_boost_path']));
  }

  // Make the proper filename for our query
  $GLOBALS['_boost_query'] = BOOST_CHAR;
  $query = array();
    if (BOOST_PAGER_CLEAN && $key == 'page') {
      $GLOBALS['_boost_path'] .= '/page/' . $val;
      continue;
    }
    if ($key != 'q' && $key != 'destination') {
      $query[$key] = $val;
  $GLOBALS['_boost_query'] .= str_replace('&amp;', '&', urldecode(http_build_query($query)));
    boost_set_cookie($user->uid);
    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['REQUEST_METHOD'] != 'HEAD')
      || $_SERVER['SERVER_SOFTWARE'] === 'PHP CLI'
      || !variable_get('boost_enabled', CACHE_NORMAL)
      || !boost_is_cacheable($GLOBALS['_boost_path'])
  // We only generate cached pages for anonymous visitors.
    if (variable_get('boost_enabled', CACHE_NORMAL) != CACHE_AGGRESSIVE) {
      $GLOBALS['conf']['cache'] = CACHE_DISABLED;
    }
    register_shutdown_function('_boost_ob_handler');
    ob_start();
/**
 * Implementation of hook_domainupdate() - keeps domain whitelist variable
 * current
 */

function boost_domainupdate($op, $domain, $form_state = array()) {
  switch ($op) {
    case 'create':
      if (variable_get('boost_domain_whitelist_use_domain', FALSE)) {
        $whitelist = variable_get('boost_domain_whitelist', array());
        $whitelist[ $domain['subdomain'] ] = $domain['subdomain'];
        asort($whitelist);
        variable_set('boost_domain_whitelist', $whitelist);
      }
      break;

    case 'update':
      if (variable_get('boost_domain_whitelist_use_domain', FALSE)) {
        $whitelist = variable_get('boost_domain_whitelist', array());
        unset($whitelist[ $domain['subdomain'] ]);
        $new_name = $form_state['values']['subdomain'];
        $whitelist[ $new_name ] = $new_name;
        asort($whitelist);
        variable_set('boost_domain_whitelist', $whitelist);
      }
      break;

    case 'delete':
      if (variable_get('boost_domain_whitelist_use_domain', FALSE)) {
        $whitelist = variable_get('boost_domain_whitelist', array());
        unset($whitelist[ $domain['subdomain'] ]);
        variable_set('boost_domain_whitelist', $whitelist);
      }
      break;
  }
}

/**
 * Grabs drupal_goto requests via boost_exit and looks for redirects.
 *
 * Looks at the current page and the destination, seeing if the internal name
 * is the same; node/8 == node/8.
 *
 * @param $destination
 *   URL that user will be sent to soon.
 */
function boost_redirect_handler($destination) {
  global $base_path, $base_root;
  if (empty($destination)) {
    return;
  }
  $source = $base_root . request_uri();

  // Parse the URLs
  $new_parts = parse_url($destination);
  $current_parts = parse_url($source);

  // Get paths
  $current_path = ltrim($current_parts['path'], $base_path);
  $current_path_system = $_GET['q'];
  $new_path = ltrim($new_parts['path'], $base_path);
  $new_path_system = drupal_get_normal_path($new_path);
  if (empty($new_path)) {
    $new_path_system = variable_get('site_frontpage', 'node');
  }

  // Build alt source url
  $alt_parts = $current_parts;
  $alt_parts['path'] = $current_path_system;
  $alt_src = boost_glue_url($alt_parts);
  $urls = array($alt_src, $source);

  $debug = array(
    'destination' => $destination,
    'source' => $source,
    'alt_src' => $alt_src,
    'current_path' => $current_path,
    'current_path_system' => $current_path_system,
    'new_path' => $new_path,
    'new_path_system' => $new_path_system,
  );

  // Handle domain alias redirects
  if (   module_exists('domain_alias')
      && isset($_domain['redirect'])
      && $_domain['redirect'] == TRUE
    boost_cache_kill_url($urls);
    return;
  }
  // Redirect is not to the same domain
  elseif (strcmp($new_parts['host'], $current_parts['host']) != 0) {
    //watchdog('boost-redirect', 'redirect is not to the same domain' . str_replace('    ', '&nbsp;&nbsp;&nbsp;&nbsp;', nl2br(htmlentities(print_r(array($debug), TRUE)))));
    boost_cache_kill_url($urls);
    return;
  }

  // Check for globalredirect internal to alias redirect
  if (strcmp($current_path, $new_path_system) == 0) {
    boost_cache_kill_url($urls);
    return;
  }

  // Check for globalredirect alias to alias redirect (deslashing)
  // Also grabs not clean to clean redirects
  if (strcmp($current_path, $_REQUEST['q']) != 0) {
    if (strcmp($current_path_system, $new_path_system) == 0) {
      boost_cache_kill_url($urls);
      return;
    }
  }

  if (module_exists('path_redirect')) {
    // Check for normal path_redirect alias to alias redirect
    $path_redirects = boost_path_redirect_load(array('source' => $current_path));
    if (isset($path_redirects)) {
      foreach ($path_redirects as $path_redirect) {
        $current_path_system = $path_redirect['redirect'];
        $debug['new_current_path_system'] = $current_path_system;
        break;
      }
    }
    if (strcmp($current_path_system, $new_path_system) == 0) {
      boost_cache_kill_url($urls);
      return;
    }

    // Check for alt path_redirect alias to alias redirect
    $path_redirects = boost_path_redirect_load(array('source' => $current_path_system));
    if (isset($path_redirects)) {
      foreach ($path_redirects as $path_redirect) {
        $current_path_system = $path_redirect['redirect'];
        $debug['new_current_path_system'] = $current_path_system;
        break;
      }
    }
    if (strcmp($current_path_system, $new_path_system) == 0) {
      boost_cache_kill_url($urls);
      return;
    }
  }

  // Last attempt of getting a "match" for this redirect
  $result = db_query("SELECT page_callback, page_type, page_id FROM {boost_cache} WHERE expire = 0 AND (hash_url = '%s' OR hash_url = '%s')", md5($source),  md5($alt_src));
  while ($row = db_fetch_array($result)) {
    // Handle node redirects
    if ($row['page_callback'] == 'node') {
      $current_path_system = $row['page_callback'] . '/' . $row['page_id'];
      if (strcmp($current_path_system, $new_path_system) == 0) {
        boost_cache_kill_url($urls);
        return;
      }
    }
  }
  //watchdog('boost-redirect', 'Nothing Done' . str_replace('    ', '&nbsp;&nbsp;&nbsp;&nbsp;', nl2br(htmlentities(print_r($debug, TRUE)))));
/**
 * 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.
 *
 * @param $destination
 *   URL that user will be sent to soon.
 */
function boost_exit($destination = NULL) {
  global $_boost, $user;
  if (!empty($destination) && $_SERVER['REQUEST_METHOD'] != 'POST' && empty($_GET['destination'])) {
    // Make sure path functions are available.
    drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);
    boost_redirect_handler($destination);
  }
  // 404, 403 dection and removal from the boost cache.
  // Only run if user is anonymous.
  if (empty($user->uid)) {
    $status = boost_get_http_status();
    if ($status == 404 || $status == 403) {
      // Bail out of caching
      $GLOBALS['_boost_cache_this'] = FALSE;

      // Get content type
      $types = boost_get_content_type();
      $types = array_pop($types);

      // Match extension
      if (stristr($types, 'text/javascript')) {
        $extension = BOOST_JSON_EXTENSION;
      }
      elseif (   stristr($types, 'application/rss')
              || stristr($types, 'text/xml')
              || stristr($types, 'application/rss+xml')
                ) {
        $extension = BOOST_JSON_EXTENSION;
      }
      elseif (stristr($types, 'text/html') && BOOST_CACHE_HTML) {
        $extension = BOOST_FILE_EXTENSION;
      }

      // Get filename
      if (!empty($extension)) {
        $filename = boost_file_path($GLOBALS['_boost_path'], TRUE, $extension);
        // Remove dead item from the cache (file & db);
        if ($filename) {
          $files = array(array('filename' => $filename));
          boost_cache_kill($files, TRUE);
          boost_remove_db($files);
        }
      }
    }
  }

  // Check that hook_exit() was invoked by drupal_goto() for a POST request:
  // Check that we're dealing with an anonymous visitor. and that some
  // session messages have actually been set during this page request:
  if (   !empty($destination)
      && $_SERVER['REQUEST_METHOD'] == 'POST'
      && empty($user->uid)
      && $messages = drupal_set_message()
          ) {
    $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:
    if (BOOST_EXIT_IN_HOOK_EXIT) {
      // FIXME: call any remaining exit hooks since we're about to terminate?
      exit(header('Location: ' . $destination));
    }
    else {
      header('Location: ' . $destination);
  if (   BOOST_ASYNCHRONOUS_OUTPUT
      && isset($GLOBALS['_boost_cache_this'])
      && $GLOBALS['_boost_cache_this']
      && headers_sent($filename, $linenum)
      && !boost_headers_contain('Location: ')
      && BOOST_VERBOSE >= 7
      && isset($_boost['verbose_option_selected']['boost_exit_headers'])
    watchdog('boost', 'boost_exit() Debug: Headers already sent in @filename on line @linenum. Asynchronous Operation will not be used.', array('@filename' => $filename, '@linenum' => $linenum));
  }
/**
 * 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'),
  );
  $items['boost_views.php'] = array(
    'page callback' => 'boost_views_async',
    'type' => MENU_CALLBACK,
    '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
          && version_compare(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() {
  if (!variable_get('boost_enabled', CACHE_NORMAL)) {
  // Remove old domains if they are now a redirect
  if (module_exists('domain_alias')) {
    $domains = domain_domains();
    $old_domains = array();
    foreach ($domains as $key => $value) {
      foreach ($value['aliases'] as $alias) {
        if ($alias['redirect'] == 1) {
          $old_domains[] = boost_cache_directory($alias['pattern'], FALSE);
        }
      }
    }
    if (!empty($old_domains)) {
      $result = boost_db_multi_select_in('boost_cache', 'base_dir', "'%s'", $old_domains);
      $files = array();
      while ($row = db_fetch_array($result)) {
        $files[] = array('filename' => $row['filename'], 'hash' => $row['hash']);
      }
      if (!empty($files)) {
        boost_cache_kill($files, TRUE);
        boost_remove_db($files);
      }
    }
  }

  // Remove old domains if they are "inactive"
  if (module_exists('domain')) {
    $domains = domain_domains();
    $old_domains = array();
    foreach ($domains as $key => $value) {
      if ($value['valid'] == 0) {
        $old_domains[] = boost_cache_directory($value['subdomain'], FALSE);
      }
    }
    if (!empty($old_domains)) {
      $result = boost_db_multi_select_in('boost_cache', 'base_dir', "'%s'", $old_domains);
      $files = array();
      while ($row = db_fetch_array($result)) {
        $files[] = array('filename' => $row['filename'], 'hash' => $row['hash']);
      }
      if (!empty($files)) {
        boost_cache_kill($files, TRUE);
        boost_remove_db($files);
      }
    }
  }

  // Check for new views on cron
  if (module_exists('views') && BOOST_VIEWS_LIST_BEHAVIOR == 0) {
    $defaults = boost_views_generate_default_list();
    variable_set('boost_views_list_default', $defaults);
  }

  $expire = TRUE;
  if (BOOST_CHECK_BEFORE_CRON_EXPIRE) {
    $expire = boost_has_site_changed(TRUE);
  if (!BOOST_LOOPBACK_BYPASS && variable_get('boost_expire_cron', TRUE) && $expire && boost_cache_expire_all()) {
    if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_cron_expire'])) {
      watchdog('boost', 'Expired stale files from static page cache.', array(), WATCHDOG_NOTICE);
    }