You are here

boost.module in Boost 6

Same filename and directory in other branches
  1. 8 boost.module
  2. 5 boost.module
  3. 7 boost.module

Provides static file caching for Drupal text output. Pages, Feeds, ect...

File

boost.module
View source
<?php

/**
 * @file
 * Provides static file caching for Drupal text output. Pages, Feeds, ect...
 */

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

// Module settings
define('BOOST_TIME', time());
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);

// 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'));
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
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', ''));
define('BOOST_AGGRESSIVE_COOKIE', variable_get('boost_aggressive_cookie', TRUE));
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));
define('BOOST_FLUSH_CCK_REFERENCES', variable_get('boost_flush_cck_references', TRUE));
define('BOOST_FLUSH_FRONT', variable_get('boost_flush_front', TRUE));
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));
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));
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));
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'] = '';

//$_boost

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

// Core API hooks

/**
 * Implementation of hook_help(). Provides online user help.
 */
function boost_help($path, $arg) {
  switch ($path) {
    case 'admin/help#boost':
      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>';
      }
      break;
    case 'admin/settings/performance/boost':
      return '<p>' . '' . '</p>';
  }

  //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
 *  node->tids
 */
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
    $relationship = array(
      'child_page_callback' => 'node',
      'child_page_type' => $node->type,
      'child_page_id' => $item->nid,
    );
    if (BOOST_VERBOSE >= 9) {
      $relationship['debug'] = array(
        '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;
      }
      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;
        }
        else {
          $disabled[$hash] = 0;
        }
      }
      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.
 */
function boost_init() {
  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);
    }
    elseif (isset($_COOKIE[BOOST_COOKIE]) && $_COOKIE[BOOST_COOKIE] == '-1') {
      boost_set_cookie($uid, BOOST_TIME - 86400);
    }
    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),
      ));
    }
    boost_fast404();
  }

  //set variables
  if (empty($_REQUEST['q'])) {

    //front page
    $GLOBALS['_boost_path'] = '';
  }
  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();
  foreach ($_GET as $key => $val) {
    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)));
  if (!empty($user->uid)) {
    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'])) {
    $GLOBALS['_boost_cache_this'] = FALSE;
    return;
  }

  // We only generate cached pages for anonymous visitors.
  if (empty($user->uid)) {
    if (variable_get('boost_enabled', CACHE_NORMAL) != CACHE_AGGRESSIVE) {
      $GLOBALS['conf']['cache'] = CACHE_DISABLED;
    }
    $GLOBALS['_boost_cache_this'] = TRUE;
    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;
  }
  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;

  // Check for redirects.
  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') || stristr($types, 'application/json')) {
        $extension = BOOST_JSON_EXTENSION;
      }
      elseif (stristr($types, 'application/rss') || stristr($types, 'text/xml') || stristr($types, 'application/rss+xml') || stristr($types, 'application/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);
    }
  }

  // Set watchdog error if headers already sent
  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(
    'page callback' => 'boost_stats_ajax_callback',
    'type' => MENU_CALLBACK,
    'access callback' => 1,
    'access arguments' => array(
      'access content',
    ),
    'file path' => drupal_get_path('module', 'boost'),
    'file' => 'stats/boost_stats.ajax.inc',
  );
  $items['boost-crawler'] = array(
    'page callback' => 'boost_crawler_run',
    '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'),
  );
  return $items;
}

/**
 * Implementation of hook_form_alter(). Performs alterations before a form
 * is rendered.
 */
function boost_form_alter(&$form, $form_state, $form_id) {
  if (boost_is_cacheable($GLOBALS['_boost_path'])) {
    $form['#immutable'] = TRUE;
    drupal_add_js(drupal_get_path('module', 'boost') . '/boost.js');
  }
  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');
      }
      break;
  }
}

/**
 * Implementation of hook_cron(). Performs periodic actions.
 */
function boost_cron() {
  if (!variable_get('boost_enabled', CACHE_NORMAL)) {
    return;
  }
  global $_boost;

  // 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);
  }

  // Expire old content
  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);
    }
  }

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

  // Crawl Site
  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)) {
    boost_cache_clear_all();
  }
  return;
}

/**
 * Implementation of hook_comment(). Acts on comment modification.
 */
function boost_comment($comment, $op) {
  if (!variable_get('boost_enabled', CACHE_NORMAL)) {
    return;
  }
  if (is_array($comment)) {
    $comment = (object) $comment;
  }

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

/**
 * Sets the base_dir array key based on settings.
 *
 * @param array &$data
 *  array that might want to have the base_dir key added to it.
 */
function boost_set_base_dir_in_array(&$data) {
  if ($data) {
    foreach ($data as $key => $value) {
      if (!array_key_exists('base_dir', $data[$key]) && !BOOST_FLUSH_ALL_MULTISITE) {
        $data[$key]['base_dir'] = BOOST_FILE_PATH;
      }
      if (array_key_exists('base_dir', $data[$key]) && BOOST_FLUSH_ALL_MULTISITE) {
        unset($data[$key]['base_dir']);
      }
    }
  }
}

/**
 * Implementation of hook_nodeapi(). Acts on nodes defined by other modules.
 */
function boost_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  global $_boost;
  if (!variable_get('boost_enabled', CACHE_NORMAL) || !isset($node->nid)) {
    return;
  }
  $data[] = array(
    'page_callback' => 'node',
    'page_id' => $node->nid,
  );
  boost_set_base_dir_in_array($data);
  switch ($op) {
    case 'insert':
      boost_expire_node($node);

      // Run all cached views, looking for this new node
      if (BOOST_FLUSH_VIEWS_INSERT && module_exists('views')) {
        $GLOBALS['_boost_nid'] = $node->nid;
        $_boost['new_nodes'][$node->nid] = $node->nid;
        register_shutdown_function('_boost_view_insert');
      }

      // Insert new node into boost_cache table
      if (variable_get('boost_insert_node_on_creation', FALSE)) {
        $GLOBALS['_boost_nid'] = $node->nid;
        $_boost['new_nodes'][$node->nid] = $node->nid;
        register_shutdown_function('_boost_cache_insert');
      }
      break;
    case 'update':
      boost_expire_node($node);

      // Run all cached views, looking for this new node
      if (BOOST_FLUSH_VIEWS_UPDATE && module_exists('views')) {
        $GLOBALS['_boost_nid'] = $node->nid;
        $_boost['new_nodes'][$node->nid] = $node->nid;
        register_shutdown_function('_boost_view_insert');
      }

      // if node is not published, delete it.
      if (!$node->status) {
        boost_cache_expire_router($data, TRUE, TRUE);
      }
      break;
    case 'delete':
      boost_expire_node($node);
      boost_cache_expire_router($data, TRUE, TRUE);
      break;
    case 'presave':

      // If path changes remove old path entry from database
      // Logic taken from path_redirect_node_presave()
      if (!empty($node->path)) {
        $node_path = 'node/' . $node->nid;
        $old_alias = drupal_get_path_alias($node_path, $node->language ? $node->language : '');
        if ($old_alias != $node_path && $node->path != $old_alias) {

          // If the user is manually changing the path alias, nuke the old files
          boost_cache_expire_router($data, TRUE, TRUE);
        }
      }
      break;
  }
}

/**
 * Shutdown function, gets called at the very end of node creation.
 *
 * Node is now created, thus we can get the node path and set the boost_cache
 * table.
 */
function _boost_cache_insert() {
  static $processed = FALSE;
  if ($processed) {
    return;
  }
  global $_boost;
  if (empty($_boost['new_nodes'])) {
    return FALSE;
  }

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

  // Load node
  foreach ($_boost['new_nodes'] as $nid) {
    $node = boost_node_get_basics($nid);
    if (!$node) {
      continue;
    }
    $router_item = array();
    $router_item['page_callback'] = 'node';
    $router_item['page_type'] = $node->type;
    $router_item['page_id'] = $node->nid;

    // Set DB defaults
    $expire = 0;
    $lifetime = -1;
    $push = -1;
    $timer = 0;
    $timer_average = 0;
    $extension = BOOST_FILE_EXTENSION;

    // Get list of base urls for this node
    $base_urls = array();
    foreach (boost_get_base_urls($node) as $domain_id) {
      foreach ($domain_id as $base) {
        $base_urls[] = $base . $node->path;
      }
    }

    // Insert each url into the DB
    foreach ($base_urls as $url) {
      $parts = parse_url($url);
      $file_path = boost_cache_directory($parts['host'], FALSE);
      $filename = boost_file_path($node->path, FALSE, BOOST_FILE_EXTENSION, $file_path);
      boost_put_db($filename, $expire, $lifetime, $push, $router_item, $timer, $timer_average, $extension, $url, $file_path);
    }
  }
  $processed = TRUE;
}

/**
 * Get domains the node is currently published to
 *
 * @param $node
 *   node object
 * @return array
 *   array('$gid' => $gid)
 */
function boost_get_domains(&$node) {
  if (empty($node->nid)) {
    return array();
  }
  $domains = array();
  $result = db_query("SELECT gid FROM {domain_access} WHERE nid = %d", $node->nid);
  while ($row = db_fetch_array($result)) {
    $gid = $row['gid'];
    $domains[$gid] = $gid;
  }
  return $domains;
}

/**
 * Get all base url's where this node can appear
 *
 * @param $node
 *   node object
 * @param $reset
 *  A boolean flag to clear the static variable if necessary.
 * @return array
 *   array(0 => array(0 => $base_url . '/'))
 */
function boost_get_base_urls(&$node, $reset = FALSE) {
  global $base_url, $base_path;

  // Get list of URL's if using domain access
  $base_urls = array();
  $domains = array();
  if (module_exists('domain') && isset($node->domains)) {

    // Get domains from node object
    foreach ($node->domains as $key => $domain_id) {
      if ($key != $domain_id) {
        continue;
      }
      $domains[$domain_id] = $domain_id;
    }

    // Get domains from database
    foreach (boost_get_domains($node) as $domain_id) {
      $domains[$domain_id] = $domain_id;
    }

    // Get aliases and set base url
    foreach ($domains as $domain_id) {
      $domain = domain_lookup($domain_id, NULL, $reset);
      if ($domain['valid'] == 1) {
        if (isset($domain['path'])) {
          $base_urls[$domain_id][] = $domain['path'];
        }
        if (is_array($domain['aliases'])) {
          foreach ($domain['aliases'] as $alias) {
            $alias['pattern'] = trim($alias['pattern']);
            if ($alias['redirect'] != 1 && !empty($alias['pattern'])) {
              $temp_domain = array(
                'scheme' => $domain['scheme'],
                'subdomain' => $alias['pattern'],
              );
              $base_urls[$domain_id][] = domain_get_path($temp_domain);
            }
          }
        }
      }
    }
  }
  else {
    $base_urls[0][] = $base_url . '/';
  }
  return $base_urls;
}

/**
 * Run views looking for new nodes.
 */
function boost_views_async() {

  // Exit if no nodes
  if (empty($_GET['new_nodes']) || !is_array($_GET['new_nodes'])) {
    return;
  }

  // Exit if key does not match.
  if (!empty($_GET['key'])) {
    $key = variable_get('boost_crawler_key', FALSE);
    if ($key == $_GET['key']) {

      // Break connection so processing is async. Return key.
      boost_async_opp($_GET['key']);

      // Give us lots of ram
      $m_limit = ini_get('memory_limit');
      $m_limit_int = substr($m_limit, 0, -1);
      $m_limit_int = (int) $m_limit_int;
      $m_limit_scale = strtoupper(substr($m_limit, -1));
      if ($m_limit_scale == 'M' && $m_limit_int < 256) {
        @ini_set('memory_limit', $m_limit_int * 4 . 'M');
      }

      // Give us lots of time
      $t_limit = ini_get('max_execution_time');
      @ini_set('max_execution_time', $t_limit * 4);
    }
    else {
      return;
    }
  }
  else {
    return;
  }

  // Get list of nodes to process.
  global $_boost;
  $_boost['new_nodes'] = $_GET['new_nodes'];

  // Process list.
  $data = _boost_views_runit();

  // Set ini variables back
  if (isset($m_limit)) {
    @ini_set('memory_limit', $m_limit);
  }
  if (isset($t_limit)) {
    @ini_set('max_execution_time', $t_limit);
  }
  return $data;
}

/**
 * Shutdown function, gets called at the very end of node creation.
 *
 * Node is now created, thus views has access to the new node. Searches all
 * cached views for newly created node. Expires the outdated views from the cache.
 */
function _boost_view_insert() {
  global $_boost, $base_url, $base_path;
  static $processed = FALSE;

  // Only run once
  if ($processed) {
    return;
  }

  // Exit if no new nodes
  if (empty($_boost['new_nodes'])) {
    return FALSE;
  }

  // Make sure node is numeric
  foreach ($_boost['new_nodes'] as $key => $value) {
    if (!is_numeric($value) || !is_numeric($key)) {
      unset($_boost['new_nodes'][$key]);
    }
  }
  if (empty($_boost['new_nodes'])) {
    return;
  }

  // Prep for async
  // URL key.
  $key = variable_get('boost_crawler_key', FALSE);
  if ($key == FALSE) {
    variable_set('boost_crawler_key', mt_rand());
    $key = variable_get('boost_crawler_key', FALSE);
  }

  // Query string.
  $query = array(
    'new_nodes' => $_boost['new_nodes'],
    'rand' => mt_rand(),
    'key' => $key,
  );
  $query_string = str_replace('&amp;', '&', urldecode(http_build_query($query)));

  // Setup request URL and headers.
  $ip = variable_get('boost_server_addr', FALSE);
  if (empty($ip)) {
    $ip = $_SERVER['SERVER_ADDR'];
  }

  // Check for IPv6 addresses, must be un square brackets
  // http://www.sixxs.net/wiki/Detecting_IPv6_In_a_Web_Page
  if (substr_count($ip, ":") > 0 && substr_count($ip, ".") == 0) {
    $ip = "[" . $ip . "]";
  }
  $url = 'http://' . $ip . $base_path . 'boost_views.php?' . $query_string;
  $headers['Host'] = $_SERVER['HTTP_HOST'];

  // Send nodes to async processor
  $socket_timeout = ini_set('default_socket_timeout', 2);
  $results = drupal_http_request($url, $headers);
  ini_set('default_socket_timeout', $socket_timeout);

  // Check response.
  $key_back = trim($results->data);

  //   $key_back = (int)$key_back;
  // If async failed; block here and generate images and caches.
  if ($key_back != $key) {
    watchdog('boost', 'Asynchronous views failed. Using Synchronous mode.');
    _boost_views_runit();
  }

  // We have processed nodes
  $processed = TRUE;
  return;
}
function _boost_views_runit($debug = FALSE, $nid = FALSE) {
  global $_boost, $base_url, $base_path;

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

  // Get all views that are not expired in database
  if (BOOST_FLUSH_ALL_MULTISITE) {
    $result = db_query("SELECT page_id, page_type FROM {boost_cache} WHERE page_callback = 'view' AND expire > 0 AND expire <> 434966400 GROUP BY page_id, page_type");
  }
  else {
    $result = db_query("SELECT page_id, page_type FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = 'view' AND expire > 0 AND expire <> 434966400 GROUP BY page_id, page_type", BOOST_FILE_PATH);
  }

  // Setup data arrays and presets
  $data = array();
  $number_views = 0;
  $number_hits = 0;
  $views = array();
  while ($boost = db_fetch_array($result)) {
    $key = $boost['page_type'] . ' - ' . $boost['page_id'];
    $views[$key]['page_type'] = $boost['page_type'];
    $views[$key]['page_id'] = $boost['page_id'];
  }

  // Get list of views that might contain the new content.
  $views += boost_views_get_valid_array();
  $base_urls = array();
  if (module_exists('domain')) {

    // Get all domains into the base_urls array
    foreach (domain_domains() as $key => $value) {
      $domains[$key] = $key;
    }
    $fake_node = new stdClass();
    $fake_node->domains = $domains;
    $base_urls = boost_get_base_urls($fake_node);
    unset($fake_node);
  }
  else {
    $base_urls[0][] = $base_url . '/';
  }

  // get list of nodes
  $nodes = array();
  if (!empty($nid)) {
    $nodes[] = $nid;
  }
  elseif (!empty($_boost['new_nodes'])) {
    $nodes = $_boost['new_nodes'];
  }
  if (empty($nodes)) {
    return;
  }

  // Loop through each node
  foreach ($nodes as $nid) {
    $node = boost_node_get_basics($nid);
    if (!$node || !is_numeric($node->nid)) {
      continue;
    }

    // Get terms for future usage (/taxonomy/term/% view)
    $tids = boost_taxonomy_node_get_tids($node->nid);
    if (isset($_boost['nid-' . $node->nid]['tids'])) {
      $tids += $_boost['nid-' . $node->nid]['tids'];
    }
    $options = array(
      'operator' => '=',
      'value' => array(
        'value' => $node->nid,
      ),
      'group' => '0',
      'exposed' => FALSE,
      'expose' => array(
        'operator' => FALSE,
        'label' => '',
      ),
      'relationship' => 'none',
    );

    // Loop through each view in the boost cache
    foreach ($views as $boost) {
      unset($view);
      $view = views_get_view($boost['page_type'], TRUE);
      if (!is_object($view)) {
        continue;
      }

      // Only work with node views
      if ($view->base_table != 'node') {
        continue;
      }

      // Make sure the view is a valid display
      if (empty($view->display[$boost['page_id']])) {
        continue;
      }

      // Set view display
      $view
        ->set_display($boost['page_id']);
      $domains = array();
      $domain_filter = FALSE;
      if (module_exists('domain')) {

        // Check for domain access filters
        $filters = $view
          ->get_items('filter', $boost['page_id']);
        if (isset($filters['current_all']) || isset($filters['gid']) || isset($filters['domain_id'])) {

          // Only need to check the view for domains this node is published to
          $domains = $node->domains;
          foreach ($domains as $key => $value) {
            if ($key != $value) {
              unset($domains[$key]);
            }
          }
          $domain_filter = TRUE;
        }
      }
      if (!$domain_filter) {

        // boost_get_base_urls uses 0 if domain access is not installed
        $domains = array(
          0 => 0,
        );
      }
      $first = TRUE;
      $view_checked = FALSE;
      foreach ($domains as $key => $domain_id) {

        // View has to be reloaded inorder for hook_views_query_substitutions
        // to work correctly because this is in a loop
        if (!$first) {
          unset($view);
          $view = views_get_view($boost['page_type'], TRUE);
          if (!is_object($view)) {
            continue;
          }

          // Make sure the view is a valid display
          if (empty($view->display[$boost['page_id']])) {
            continue;
          }
        }
        $first = FALSE;
        $view
          ->set_display($boost['page_id']);

        // Make sure view is valid
        if (!$view_checked) {
          $h = $view->display[$boost['page_id']]->handler->default_display->options;
          $broken = FALSE;
          if (!empty($h)) {
            foreach ($h as $type => $rows) {
              if (empty($rows)) {
                continue;
              }
              if ($type == 'sorts' || $type == 'fields' || $type == 'arguments' || $type == 'filters' || $type == 'relationships') {
                $type = rtrim($type, 's');
                foreach ($rows as $id => $row) {
                  $table = $row['table'];
                  $field = $row['field'];
                  $views_data = views_fetch_data($table);
                  if (empty($views_data)) {
                    $broken = TRUE;
                    break 2;
                  }
                  if (!isset($views_data[$field][$type])) {
                    $broken = TRUE;
                    break 2;
                  }
                }
              }
            }
          }
          unset($h);
          if ($broken) {
            break;
          }
          $view_checked = TRUE;
        }

        // Filter to just this nid
        $view
          ->add_item($boost['page_id'], 'filter', 'node', 'nid', $options);

        // Set ***CURRENT_DOMAIN*** variable
        // See domain_views_query_substitutions()
        if ($domain_filter) {
          domain_set_domain($domain_id, TRUE);
        }
        $view
          ->pre_execute();
        $view
          ->execute();

        // Increment Counter
        $number_views++;
        foreach ($view->result as $item) {
          if ($item->nid == $nid) {
            foreach ($base_urls as $key => $value) {
              $save = TRUE;
              if ($domain_filter && $key != $domain_id) {
                $save = FALSE;
              }
              if ($save) {
                foreach ($value as $url) {
                  $parts = parse_url($url);
                  $file_path = boost_cache_directory($parts['host'], FALSE);
                  $hash = $file_path . 'view' . $boost['page_type'] . $boost['page_id'];
                  $data[$hash] = array(
                    'base_dir' => $file_path,
                    'page_callback' => 'view',
                    'page_type' => $boost['page_type'],
                    'page_id' => $boost['page_id'],
                  );
                  $number_hits++;
                }
              }
            }
          }
        }

        // Free memory
        if (isset($view) && is_object($view) && method_exists($view, 'destroy')) {
          $view
            ->destroy();

          // Fix views bug http://drupal.org/node/988680
          unset($view->old_view);
        }
        unset($view);
      }

      // Free memory
      if (isset($view) && is_object($view) && method_exists($view, 'destroy')) {
        $view
          ->destroy();

        // Fix views bug http://drupal.org/node/988680
        unset($view->old_view);
      }
      unset($view);
      if ($domain_filter) {
        domain_reset_domain(TRUE);
      }
    }
  }
  if (!$debug) {
    if ($data) {
      $flushed = boost_cache_expire_router($data);
    }
    if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_view_insert'])) {
      watchdog('boost', 'Debug: _boost_view_insert() <br />%count Views Searched (%viewnames) %times times; %hits of them contain the new nodes (%nids) and where thus flushed. As a result of this %flushed pages where expired from the boost cache.<br /> !domains', array(
        '%nids' => implode(', ', $nodes),
        '%count' => count($views),
        '%times' => $number_views,
        '%hits' => $number_hits,
        '%flushed' => $flushed,
        '!domains' => str_replace('    ', '&nbsp;&nbsp;&nbsp;&nbsp;', nl2br(htmlentities(print_r($base_urls, TRUE)))),
        '%viewnames' => implode(', ', array_keys($views)),
      ));
    }
    $processed = TRUE;
  }
  else {
    return array(
      'in-view' => $data,
      'in-cache' => boost_cache_expire_router($data, FALSE, FALSE, TRUE),
    );
  }
}

/**
 * Implementation of hook_votingapi_insert().
 *
 * @param $votes
 *  array of votes
 */
function boost_votingapi_insert($votes) {
  if (!variable_get('boost_enabled', CACHE_NORMAL)) {
    return;
  }
  foreach ($votes as $vote) {
    $node = node_load($vote['content_id'], NULL, TRUE);
    boost_expire_node($node, $vote['content_id']);
  }
}

/**
 * Implementation of hook_votingapi_delete().
 *
 * @param $votes
 *  array of votes
 */
function boost_votingapi_delete($votes) {
  if (!variable_get('boost_enabled', CACHE_NORMAL)) {
    return;
  }
  foreach ($votes as $vote) {
    $node = node_load($vote['content_id'], NULL, TRUE);
    boost_expire_node($node, $vote['content_id']);
  }
}

/**
 * Expires a node from the cache; including related pages.
 *
 * Expires front page if promoted, taxonomy terms,
 *
 * @param $node
 *  node object
 * @param $nid
 *  node id
 * @param $debug
 *  TRUE to return values without expiring anything.
 * @return
 *  TRUE if no debug, array($data, $paths) if debug turned on.
 */
function boost_expire_node($node, $nid = 0, $debug = FALSE) {
  global $_boost;
  $data = array();
  $paths = array();

  // Check node object
  if (empty($node->nid)) {
    if (is_numeric($nid)) {
      $node->nid = $nid;
    }
    else {
      return FALSE;
    }
  }

  // Expire this node
  if (BOOST_NO_DATABASE) {
    $paths['node'] = 'node/' . $node->nid;
  }
  else {
    $data['node'] = array(
      'page_callback' => 'node',
      'page_id' => $node->nid,
    );
  }

  // If promoted to front page, expire front page
  if (BOOST_FLUSH_FRONT && $node->promote == 1) {
    $paths['front'] = '<front>';
  }

  // Get taxonomy terms and flush
  if (module_exists('taxonomy') && BOOST_FLUSH_NODE_TERMS) {

    // Get old terms from DB
    $tids = boost_taxonomy_node_get_tids($node->nid);

    // Get new terms from node object
    if (!empty($node->taxonomy)) {
      foreach ($node->taxonomy as $vocab) {
        if (is_array($vocab)) {
          foreach ($vocab as $term) {
            $tids[$term] = $term;
          }
        }
      }
    }

    // See if the taxonony/term/% path is a view containing domain access restrictions
    if (module_exists('domain') && module_exists('views')) {
      $list = boost_views_get_valid_list();
      foreach ($list as $hash => $enabled) {
        $info = explode(' - ', $hash);
        if ($info[2] == '/taxonomy/term/%') {

          // Load View
          $view = views_get_view($info[0], TRUE);
          if (!is_object($view)) {
            continue;
          }

          // Check for domain access filter "Available on current domain"
          $filters = $view
            ->get_items('filter', $info[1]);

          // Free memory
          $view
            ->destroy();
          unset($view);
          if (isset($filters['current_all']) || isset($filters['gid']) || isset($filters['domain_id'])) {
            $fake_node = $node;
          }
          else {
            foreach (domain_domains() as $key => $value) {
              $domains[$key] = $key;
            }
            $fake_node = new stdClass();
            $fake_node->domains = $domains;
          }
          break;
        }
      }
    }
    if (!isset($fake_node)) {
      $fake_node = $node;
    }

    // Get list of base urls for this node
    $base_dirs = array();
    foreach (boost_get_base_urls($fake_node) as $domain_id) {
      foreach ($domain_id as $base) {
        $parts = parse_url($base);
        $base_dirs[] = boost_cache_directory($parts['host'], FALSE);
      }
    }
    unset($fake_node);

    // Set each tid in the data array
    foreach ($tids as $tid) {
      if (is_numeric($tid)) {
        if (BOOST_NO_DATABASE) {
          $term = taxonomy_get_term($tid);
          $paths['term' . $tid] = taxonomy_term_path($term);
        }
        else {
          foreach ($base_dirs as $base_dir) {
            $data['term:' . $tid . ' base:' . $base_dir] = array(
              'page_callback' => 'taxonomy',
              'page_id' => $tid,
              'base_dir' => $base_dir,
            );
          }
        }
      }
    }

    // Save all term IDs into the global scope
    $_boost['nid-' . $node->nid]['tids'] = $tids;
  }

  // Get menu and flush related items in the menu.
  if (BOOST_FLUSH_MENU_ITEMS != 0) {
    if (!isset($node->menu['menu_name'])) {
      menu_nodeapi($node, 'prepare');
    }
    $menu = menu_tree_all_data($node->menu['menu_name']);
    $tempa = NULL;
    $tempb = NULL;
    if (BOOST_FLUSH_MENU_ITEMS == 1) {
      $links = boost_get_menu_structure($menu, FALSE, 'node/' . $node->nid, NULL, $tempa, $tempb);
    }
    elseif (BOOST_FLUSH_MENU_ITEMS == 2) {
      $links = boost_get_menu_structure($menu, NULL, NULL, NULL, $tempa, $tempb);
    }
    unset($tempa);
    unset($tempb);
    $paths = array_merge($links, $paths);
  }

  // Get CCK References and flush.
  if (BOOST_FLUSH_CCK_REFERENCES && module_exists('nodereference')) {
    $nids = array();
    $type = content_types($node->type);
    if ($type) {
      foreach ($type['fields'] as $field) {

        // Add referenced nodes to nids. This will clean up nodereferrer fields
        // when the referencing node is updated.
        if ($field['type'] == 'nodereference') {
          $node_field = isset($node->{$field}['field_name']) ? $node->{$field}['field_name'] : array();
          foreach ($node_field as $delta => $item) {
            $nids[$item['nid']] = $item['nid'];
          }
        }
      }
      foreach ($nids as $nid) {
        if (is_numeric($nid)) {
          if (BOOST_NO_DATABASE) {
            $paths['reference' . $nid] = 'node/' . $nid;
          }
          else {
            $data['reference' . $nid] = array(
              'page_callback' => 'node',
              'page_id' => $nid,
            );
          }
        }
      }
    }

    // Get CCK references pointing to this node and flush.
    if (module_exists('nodereferrer')) {
      $nids = nodereferrer_referrers($node->nid);
      foreach ($nids as $nid) {
        if (is_numeric($nid['nid'])) {
          if (BOOST_NO_DATABASE) {
            $paths['referrer' . $nid['nid']] = 'node/' . $nid['nid'];
          }
          else {
            $data['referrer' . $nid['nid']] = array(
              'page_callback' => 'node',
              'page_id' => $nid['nid'],
            );
          }
        }
      }
    }
  }

  // Get views containing this node and flush.
  if (BOOST_FLUSH_VIEWS && module_exists('views')) {
    $router_item = _boost_get_menu_router();
    $relationship = array();
    $relationship[] = array(
      'page_callback' => 'node',
      'page_type' => $node->type,
      'page_id' => $node->nid,
    );
    $relationships = boost_cache_get_node_relationships($relationship);
    $data = array_merge($data, $relationships);
  }

  // Flush the cache
  $flushed = 0;
  if (!$debug) {
    if (!empty($data)) {
      $flushed += boost_cache_expire_router($data);
    }
    if (!empty($paths)) {
      $flushed += boost_cache_expire_derivative($paths, TRUE);
    }
    if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_expire_node'])) {
      watchdog('boost', 'Debug: boost_expire_node() <br />Node !nid was flushed resulting in !flushed pages being expired from the cache <br />data: !data <br />paths: !paths', array(
        '!nid' => $node->nid,
        '!flushed' => $flushed,
        '!data' => boost_print_r($data, TRUE, TRUE),
        '!paths' => boost_print_r($paths, TRUE, TRUE),
      ));
    }
    return $flushed;
  }
  else {
    return array(
      'router-in' => $data,
      'router-results' => boost_cache_expire_router($data, FALSE, FALSE, TRUE),
      'paths-in' => $paths,
      'path-results' => boost_cache_expire_derivative($paths, FALSE, FALSE, TRUE),
    );
  }
}

/**
 * Finds parent, siblings and children of the menu item. UGLY CODE...
 *
 * @param array $menu
 *  Output from menu_tree_all_data()
 * @param bool $found
 *  Signal for when the needle was found in the menu array.
 *  Set TRUE to get entire menu
 * @param string $needle
 *  Name of menu link. Example 'node/21'
 * @param bool $first
 *  Keep track of the first call; this is a recursive function.
 * @param bool &$found_global
 *  Used to signal the parent item was found in one of it's children
 * @param bool &$menu_out
 *  Output array of parent, siblings and children menu links
 *
 * TODO: Use page_callback and page_arguments instead of link_path.
 *  Can use boost_cache_expire_router() then.
 */
function boost_get_menu_structure($menu, $found, $needle, $first, &$found_global, &$menu_out) {

  // Set Defaults
  $found = !is_null($found) ? $found : TRUE;
  $needle = !is_null($needle) ? $needle : '';
  $first = !is_null($first) ? $first : TRUE;
  $found_global = FALSE;
  $menu_out = !is_null($menu_out) ? $menu_out : array();

  // Get Siblings
  foreach ($menu as $item) {
    if ($item['link']['hidden'] == 0 && $item['link']['page_callback'] != '' && ($item['link']['link_path'] == $needle || $found)) {
      $menu_out[] = $item['link']['link_path'];
      $found = TRUE;
    }
  }

  // Get Children
  foreach ($menu as $item) {
    if ($item['link']['hidden'] != 0) {
      continue;
    }
    if ($item['link']['page_callback'] != '' && ($item['link']['link_path'] == $needle || $found)) {
      $menu_out[] = $item['link']['link_path'];
      $found = TRUE;
    }

    // Get Grandkids
    if (!empty($item['below'])) {
      $sub_menu = array();
      foreach ($item['below'] as $below) {
        if ($below['link']['hidden'] == 0) {
          $sub_menu[] = $below;
        }
      }
      boost_get_menu_structure($sub_menu, $needle, $found, FALSE, $found_global, $menu_out);
      $structure[$item['link']['link_path']][] = $sub;
      if ($item['link']['page_callback'] != '' && $found_global) {

        // Get Parent of kid
        $menu_out[] = $item['link']['link_path'];
      }
    }
    else {
      $structure[$item['link']['link_path']] = '';
    }
  }

  // Clean up
  if (isset($structure) && is_array($structure)) {
    $structure = array_unique($structure);
  }
  $found_global = $found;
  if ($first) {
    if (isset($menu_out) && is_array($menu_out)) {
      $menu_out = array_unique($menu_out);
      sort($menu_out);
      return $menu_out;
    }
    else {
      return array();
    }
  }
  else {
    return $structure;
  }
}

/**
 * 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] = $term;
  }
  return $tids;
}

/**
 * Implementation of hook_taxonomy(). Acts on taxonomy changes.
 */
function boost_taxonomy($op, $type, $term = NULL) {
  if (!variable_get('boost_enabled', CACHE_NORMAL)) {
    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) {
  global $user, $_boost;
  if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_user_op'])) {
    watchdog('boost', 'Debug: boost_user() <br />The %op operation was sent for user %user', array(
      '%op' => $op,
      '%user' => $user->uid,
    ));
  }
  switch ($op) {
    case 'login':

      // Set a special cookie to prevent authenticated users getting served
      // pages from the static page cache.
      boost_set_cookie($user->uid);
      break;
    case 'logout':

      // set the cookie to 0, then remove at the following request, this way browsers won't show a cached logged in page
      boost_set_cookie(-1);
      break;
    case 'delete':
      if (!variable_get('boost_enabled', CACHE_NORMAL)) {
        return;
      }

      // Expire the relevant user page from the static page cache to prevent serving stale content:
      if (!empty($account->uid)) {
        if (BOOST_NO_DATABASE) {
          $paths[] = 'user/' . $account->uid;
          $flushed = boost_cache_expire_derivative($paths, TRUE, TRUE);
        }
        else {
          $data[] = array(
            'page_callback' => 'user',
            'page_id' => $account->uid,
          );
          boost_set_base_dir_in_array($data);
          $flushed = boost_cache_expire_router($data, TRUE, TRUE);
        }
        if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_user_delete'])) {
          watchdog('boost', 'Debug: boost_user() <br />User !uid was deleted resulting in !flushed pages being expired from the cache', array(
            '!uid' => $account->uid,
            '!flushed' => $flushed,
          ));
        }
      }
      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'),
          'weight' => 10,
          'cache' => BLOCK_NO_CACHE,
        ),
        'config' => array(
          'info' => t('Boost: Pages cache configuration'),
          'weight' => 10,
          'cache' => BLOCK_NO_CACHE,
        ),
        'stats' => array(
          'info' => t('Boost: AJAX core statistics'),
          'weight' => 10,
          'cache' => BLOCK_NO_CACHE,
        ),
        'relationship' => array(
          'info' => t('Boost: Expiration Relationship'),
          'weight' => 10,
          'cache' => BLOCK_NO_CACHE,
        ),
      );
      break;
    case 'configure':
      if ($delta == 'stats') {
        $form['boost_block_show_stats'] = array(
          '#type' => 'checkbox',
          '#title' => t('Display Statistics.'),
          '#default_value' => variable_get('boost_block_show_stats', FALSE),
          '#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),
        );
        return $form;
      }
      break;
    case 'save':
      if ($delta == 'stats') {
        variable_set('boost_block_show_stats', $edit['boost_block_show_stats']);
        variable_set('boost_block_cache_stats_block', $edit['boost_block_cache_stats_block']);
      }
      break;
    case 'view':
      $block = array();
      switch ($delta) {
        case 'relationship':

          // 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 = '';
            $router_item = _boost_get_menu_router();
            if ($router_item['page_callback'] == 'node') {
              $node = node_load(arg(1));
              $data = boost_expire_node($node, $node->nid, TRUE);
              if (BOOST_FLUSH_VIEWS_INSERT && module_exists('views')) {
                $data['view'] = _boost_views_runit(TRUE, $node->nid);
              }
              $output .= str_replace('    ', '&nbsp;&nbsp;&nbsp;&nbsp;', nl2br(htmlentities(print_r($data, TRUE))));
            }
            $block['subject'] = '';
            $block['content'] = $output;
          }
          break;
        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 = '';

            // Has anything on the site changed?
            if (BOOST_CHECK_BEFORE_CRON_EXPIRE) {
              $output .= t('Site Has Changed: %old<br />', array(
                '%old' => boost_has_site_changed() ? 'True' : 'False',
              ));
            }

            // Display Cached File Info
            $filename = boost_file_path($GLOBALS['_boost_path']);
            $ttl = boost_db_get_ttl($filename);
            $generate = boost_get_generation_time($filename);
            if ($generate !== FALSE) {
              if (boost_is_cached($GLOBALS['_boost_path'])) {
                $output .= $filename . ' <br />';
              }
              else {
                $output .= $filename . '<br />Does not exist. <br />';
              }
              if ($ttl < 0) {
                $output .= t('<strong>Expired: %interval ago</strong><br />', array(
                  '%interval' => format_interval(abs($ttl)),
                ));
              }
              else {
                $output .= t('Expire In: %interval<br />', array(
                  '%interval' => format_interval(abs($ttl)),
                ));
              }
              $tim = boost_db_get_cache_age($filename);
              $output .= t('Cache Age: %time<br />', array(
                '%time' => format_interval($tim),
              ));
              $output .= t('Cache Generated: %time seconds<br />', array(
                '%time' => round($generate, 2),
              )) . ' ';
              if (user_access('administer site configuration')) {
                $output .= drupal_get_form('boost_block_flush_form');
              }
              $output .= '<br />';
            }
            else {
              $output .= t('This page is being served <strong>live</strong> to anonymous visitors, as it is not currently in the static page cache.');
            }

            // Display any info about errors on this page
            $error = _boost_page_have_error();
            $msg_count = empty($GLOBALS['_boost_message_count']) ? 0 : $GLOBALS['_boost_message_count'];
            $drupal_msg = max(count(drupal_get_messages(NULL, FALSE)), $msg_count);
            if ($error || BOOST_HALT_ON_MESSAGES && $drupal_msg != 0) {
              $output .= t('There are <strong>php errors</strong> or <strong>Drupal messages</strong> on this page, preventing boost from caching.') . ' ';
              if ($error) {
                $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/boost'),
                ));
              }
              if (BOOST_HALT_ON_MESSAGES && $drupal_msg != 0) {
                $output .= t('MESSAGES: %msg <br /> !performance', array(
                  '%msg' => $drupal_msg,
                  '!performance' => l(t('Turn Off Error Checking'), 'admin/settings/performance/boost'),
                ));
              }
            }
            $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 (user_access('administer site configuration') && boost_is_cacheable($GLOBALS['_boost_path']) && !BOOST_NO_DATABASE) {
            $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['REQUEST_METHOD'] != 'HEAD' || $_SERVER['SERVER_SOFTWARE'] === 'PHP CLI' || !variable_get('boost_enabled', CACHE_NORMAL) || isset($_GET['nocache']) || !boost_is_cacheable($GLOBALS['_boost_path']) || !empty($user->uid) || !module_exists('statistics'))) {
            $block = array();
            $block['subject'] = t('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');
          }
          break;
      }
      return $block;
  }
}
function boost_block_flush_form() {
  $router_item = _boost_get_menu_router();
  $form['boost_clear']['page_callback'] = array(
    '#type' => 'hidden',
    '#value' => $router_item['page_callback'],
  );
  $form['boost_clear']['page_type'] = array(
    '#type' => 'hidden',
    '#value' => $router_item['page_type'],
  );
  $form['boost_clear']['page_id'] = array(
    '#type' => 'hidden',
    '#value' => $router_item['page_id'],
  );
  $form['boost_clear']['path'] = array(
    '#type' => 'hidden',
    '#value' => $GLOBALS['_boost_path'],
  );
  $filename = boost_file_path($GLOBALS['_boost_path']);
  $ttl = boost_db_get_ttl($filename);
  if ($ttl < 0) {
    $form['boost_clear']['kill'] = array(
      '#type' => 'hidden',
      '#value' => TRUE,
    );
  }
  $form['boost_cache']['clear'] = array(
    '#type' => 'submit',
    '#value' => BOOST_EXPIRE_NO_FLUSH && $ttl >= 0 ? t('Expire Page') : t('Flush Page'),
    '#submit' => array(
      'boost_block_form_flush_submit',
    ),
  );
  return $form;
}
function boost_block_form_flush_submit(&$form_state, $form) {
  global $_boost;
  $data = array();

  // Special front page handling
  if (!empty($form['values']['page_callback']) && $form['values']['page_callback'] == 'node_page_default' && BOOST_CACHE_XML) {
    $data[] = array(
      'page_callback' => 'node_feed',
    );
  }
  $data[] = array(
    'page_callback' => $form['values']['page_callback'],
    'page_type' => $form['values']['page_type'],
    'page_id' => $form['values']['page_id'],
  );
  boost_set_base_dir_in_array($data);
  $flushed = 0;
  if (array_key_exists('kill', $form['values'])) {
    if ($data) {
      $flushed += boost_cache_expire_router($data, TRUE);
    }
    if (array_key_exists('path', $form['values'])) {
      $flushed += boost_cache_expire_derivative(array(
        $form['values']['path'],
      ), TRUE, TRUE);
    }
    if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_block_flush_submit'])) {
      $form_path = empty($form['values']['path']) ? '' : $form['values']['path'];
      watchdog('boost', 'Debug: boost_block_form_flush_submit() <br />Page !path was deleted resulting in !flushed pages being flushed from the cache', array(
        '!path' => $form_path,
        '!flushed' => $flushed,
      ));
    }
  }
  else {
    if ($data) {
      $flushed += boost_cache_expire_router($data);
    }
    if (array_key_exists('path', $form['values'])) {
      $flushed += boost_cache_expire_derivative(array(
        $form['values']['path'],
      ), TRUE);
    }
    if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_block_flush_submit'])) {
      $form_path = empty($form['values']['path']) ? '' : $form['values']['path'];
      watchdog('boost', 'Debug: boost_block_form_flush_submit() <br />Page !path was expired resulting in !flushed pages being expired from the cache', array(
        '!path' => $form_path,
        '!flushed' => $flushed,
      ));
    }
  }
}
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('Maximum 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(
    '#type' => 'select',
    '#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_type'],
      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',
    ),
  );
  $lifetime = isset($settings[0]['lifetime']) ? $settings[0]['lifetime'] : -1;
  $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' => t('%lifetime - %id', array(
      '%lifetime' => $period[$lifetime],
      '%id' => $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,
  );
  $lifetime = isset($settings[1]['lifetime']) ? $settings[1]['lifetime'] : -1;
  $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' => t('%lifetime - %type', array(
      '%lifetime' => $period[$lifetime],
      '%type' => $router_item['page_type'],
    )),
  );
  $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,
  );
  $lifetime = isset($settings[2]['lifetime']) ? $settings[1]['lifetime'] : -1;
  $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' => t('%lifetime - %callback', array(
      '%lifetime' => $period[$lifetime],
      '%callback' => $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) {
  global $_boost;
  $flushed = boost_set_db_page_settings($form['values']['lifetime'], $form['values']['push'], $form['values']['selection']);
  if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_block_settings_submit'])) {
    watchdog('boost', 'Debug: boost_block_db_settings_form_submit() <br />!flushed pages being expired from the cache in order for the new settings to take effect.', array(
      '!flushed' => $flushed,
    ));
  }
}

/**
 * Removes page specific settings in the boost cache database.
 */
function boost_block_db_rm_settings_form_submit(&$form_state, $form) {
  global $_boost;
  $router_item = _boost_get_menu_router();
  $data = array();
  if (is_numeric($form['values']['id'])) {
    boost_remove_settings_db($form['values']['id_value']);
    $data[] = array(
      'base_dir' => BOOST_FILE_PATH,
      'page_callback' => $router_item['page_callback'],
      'page_type' => $router_item['page_type'],
      'page_id' => $router_item['page_id'],
    );
  }
  if (is_numeric($form['values']['type'])) {
    boost_remove_settings_db($form['values']['type_value']);
    $data[] = array(
      'base_dir' => BOOST_FILE_PATH,
      'page_callback' => $router_item['page_callback'],
      'page_type' => $router_item['page_type'],
    );
  }
  if (is_numeric($form['values']['container'])) {
    boost_remove_settings_db($form['values']['container_value']);
    $data[] = array(
      'base_dir' => BOOST_FILE_PATH,
      'page_callback' => $router_item['page_callback'],
    );
  }
  if ($data) {
    $flushed = boost_cache_expire_router($data);
  }
  if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_block_rm_settings_submit'])) {
    watchdog('boost', 'Debug: boost_block_db_rm_settings_form_submit() <br />!flushed pages being expired from the cache in order for the new settings to take effect.', array(
      '!flushed' => $flushed,
    ));
  }
}

/**
 * Generate js/html for boost stat counter.
 *
 * NOTE HTML code could be added to the $buffer directly. Would prevent 2x
 * counts on first view. Would be hard to do though.
 *
 * @param $filename
 *   Name of boost's statistics php file.
 */
function boost_stats_generate($filename) {
  global $base_path;

  // is node & node count enabled.
  if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == '' && variable_get('statistics_count_content_views', 0)) {
    $nid = arg(1);
  }
  else {
    $nid = 'NULL';
  }

  // 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'];
  }
  else {
    $title = 'NULL';
    $q = 'NULL';
  }
  $page_js = array(
    'boost' => array(
      'nid' => $nid,
      'q' => $q,
      'title' => $title,
    ),
  );
  $site_js = <<<ETO
\$.getJSON(Drupal.settings.basePath + "{<span class="php-variable">$filename</span>}", {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) {
    if (contents == 'NULL') {
      \$(id).parent().parent().hide();
    }
    else {
      \$(id).html(contents);
    }
  });
});
ETO;

  // 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;qq=' . $q . '" alt="" /></div></noscript>';
  return $page_ns;
}

/**
 * Implementation of hook_theme().
 */
function boost_theme() {
  return array(
    'boost_cache_status' => array(
      'arguments' => array(
        'ttl' => NULL,
        'text' => NULL,
      ),
    ),
  );
}
function theme_boost_cache_status($ttl, $text) {
  return '<div style="font-size: x-small;">' . $text . '</div>';
}

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

// Output buffering callback

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

  // If Compressed data was given to us decompress it
  if (boost_headers_contain('gzip')) {
    $decompressed_buffer = gzinflate(substr(substr($buffer, 10), 0, -8));
  }
  else {
    $decompressed_buffer = $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']));

  // Very late cache canceling
  $router_item = _boost_get_menu_router();
  if ($router_item['page_callback'] == 'search404_page' || $router_item['page_callback'] == 'fivestar_vote') {
    $GLOBALS['_boost_cache_this'] = FALSE;
  }

  // Check for PHP errors
  if ($error = _boost_page_have_error()) {
    $GLOBALS['_boost_cache_this'] = FALSE;
    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/boost'),
      ), WATCHDOG_WARNING);
    }
  }

  // Check for drupal messages
  if (BOOST_HALT_ON_MESSAGES && isset($GLOBALS['_boost_message_count']) && $GLOBALS['_boost_message_count'] != 0) {
    $GLOBALS['_boost_cache_this'] = FALSE;
    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/boost'),
      ), WATCHDOG_WARNING);
    }
  }

  // Check the currently set content type and the HTTP response code. only cache
  // 'text/*' pages that were output with a 200 status number.
  if (!empty($decompressed_buffer)) {
    $status = boost_get_http_status();
    $types = boost_get_content_type();
    if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_ob_handler_info'])) {
      watchdog('boost', 'Debug: _boost_ob_handler() <br />HTTP Info: !status - !types <br />Path: !path <br />Content Container: !callback <br />Content Type: !type <br />ID: !id <br />Cache This: !cache.', array(
        '!status' => $status,
        '!types' => implode(', ', $types),
        '!path' => boost_file_path($GLOBALS['_boost_path']),
        '!callback' => $router_item['page_callback'],
        '!type' => $router_item['page_type'],
        '!id' => $router_item['page_id'],
        '!cache' => $GLOBALS['_boost_cache_this'] ? 'TRUE' : 'FALSE',
      ));
    }

    // Bail out if we can not cache
    if ($status != 200 || $GLOBALS['_boost_cache_this'] == FALSE) {
      return;
    }

    // Check for corret types and cache accordingly
    $types = array_pop($types);
    if (stristr($types, 'text/javascript') && BOOST_CACHE_JSON) {
      if (BOOST_ASYNCHRONOUS_OUTPUT && !headers_sent()) {
        boost_async_opp($buffer, FALSE, 'text/javascript; charset=utf-8');
      }
      boost_cache_set($GLOBALS['_boost_path'], $decompressed_buffer, BOOST_JSON_EXTENSION);
    }
    elseif ((stristr($types, 'application/rss') || stristr($types, 'text/xml') || stristr($types, 'application/rss+xml')) && BOOST_CACHE_XML) {
      if (BOOST_ASYNCHRONOUS_OUTPUT && !headers_sent()) {
        boost_async_opp($buffer, FALSE, 'text/xml; charset=utf-8');
      }
      boost_cache_set($GLOBALS['_boost_path'], $decompressed_buffer, BOOST_XML_EXTENSION);
    }
    elseif (stristr($types, 'text/html') && BOOST_CACHE_HTML) {
      if (BOOST_ASYNCHRONOUS_OUTPUT && !headers_sent()) {
        boost_async_opp($buffer, FALSE, 'text/html; charset=utf-8');
      }
      boost_cache_set($GLOBALS['_boost_path'], $decompressed_buffer, BOOST_FILE_EXTENSION);

      // html output requires special handling of the aggregated js/css files.
      boost_cache_css_js_files($decompressed_buffer);
    }
  }
}

/**
 * Determines the MIME content type of the current page response based on
 * the currently set Content-Type HTTP header.
 *
 * This should normally return the string 'text/html' unless another module
 * has overridden the content type.
 */
function boost_get_content_type() {
  $headers = explode("content-type: ", strtolower(drupal_get_headers()));
  $types = array();
  foreach ($headers as $header) {
    if (!empty($header)) {
      list($header_first_line) = explode('\\n', $header);
      list($header_first_part) = explode('; charset=', $header_first_line);
      $types[] = $header_first_part;
    }
  }
  return array_filter($types);
}

/**
 * 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() {
  if (function_exists('drupal_get_headers')) {
    foreach (explode("\n", drupal_get_headers()) as $header) {
      preg_match('!^HTTP\\/.*?\\s+(\\d+)!', $header, $matches);
      if (isset($matches[1])) {
        return (int) $matches[1];
      }
    }
  }
  return 200;
}
function boost_headers_contain($text) {
  if (function_exists('headers_list')) {
    $list = headers_list();
    if (empty($list)) {
      return FALSE;
    }
    foreach ($list as $header) {
      $info = stristr($header, $text);
      if ($info !== FALSE) {
        return $info;
      }
    }
  }
  return FALSE;
}

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

// Boost API implementation

/**
 * Determines whether a given url can be cached or not by boost.
 *
 * To avoid potentially troublesome situations, the user login page is never
 * cached, nor are any admin pages.
 *
 * @param $path
 *   Current URL
 *
 * $path = $GLOBALS['_boost_path'] most of the time
 * uses $GLOBALS['_boost_query'] as well
 */
function boost_is_cacheable($path) {
  global $base_root;
  $is_front = FALSE;
  if (empty($path)) {
    $is_front = TRUE;
    $path = variable_get('site_frontpage', 'node');
  }
  $normal_path = drupal_get_normal_path($path);

  // normalize path
  $full = $normal_path . '-' . $GLOBALS['_boost_query'];
  $decoded1 = urldecode($full);
  $decoded2 = urldecode($decoded1);
  while ($decoded1 != $decoded2) {
    $decoded1 = urldecode($decoded2);
    $decoded2 = urldecode($decoded1);
  }
  $decoded = $decoded2;
  unset($decoded2);
  unset($decoded1);
  $url = $base_root . request_uri();

  // Never cache
  //  the user login/registration/password/reset pages
  //  any admin pages
  //  comment reply pages
  //  boost_stats.php
  //  any shopping cart pages
  //  node add page
  //  openid login page
  //  filefield upload progress page
  //  URL variables that contain / or \
  //  if incoming URL contains '..' or null bytes or ://
  //  if url contains #
  // Limit the maximum directory nesting depth of the path
  // Do not cache if destination is set
  if ($normal_path == 'user' || preg_match('!^user/(login|register|password|reset)!', $normal_path) || preg_match('!^admin!', $normal_path) || preg_match('!^comment/reply!', $normal_path) || preg_match('!boost_stats.php$!', $normal_path) || preg_match('!^cart!', $normal_path) || preg_match('!^node/add!', $normal_path) || preg_match('!^openid!', $normal_path) || preg_match('!^filefield/progress/!', $normal_path) || strpos($GLOBALS['_boost_query'], '/') || strpos($GLOBALS['_boost_query'], "\\") || strpos($full, '..') !== FALSE || strpos($full, "\0") !== FALSE || strpos($decoded, '://') !== FALSE || strpos($decoded, '#') !== FALSE || strpos($decoded, '..') !== FALSE || strpos($decoded, "\0") !== FALSE || count(explode('/', $path)) > BOOST_MAX_PATH_DEPTH || !empty($_GET['destination'])) {
    return FALSE;
  }
  if (!BOOST_CACHE_XML && (preg_match('!/feed$!', $normal_path) || preg_match('!\\.xml$!', $normal_path))) {
    return FALSE;
  }
  if (!BOOST_CACHE_QUERY && ($GLOBALS['_boost_query'] != BOOST_CHAR || strstr($url, '?') !== FALSE)) {
    return FALSE;
  }

  // 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 if this domain has been whitelisted for caching.
  $use_lists = variable_get('boost_domain_use_lists', BOOST_DOMAIN_NO_LISTS);
  if ($use_lists == BOOST_DOMAIN_WHITELIST_ONLY || $use_lists == BOOST_DOMAIN_BOTH_LISTS) {
    $is_whitelisted = FALSE;
    $whitelist = variable_get('boost_domain_whitelist', array());
    $current_domain = $_SERVER['HTTP_HOST'];

    /* It'd be possible (and involve less code overall) to use domain_lookup()
     * here instead of stuffing everything into the "boost_domain_whitelist"
     * variable, but this method will avoid the extra db query and hooks that
     * calling domain_lookup can cause during page load.
     */
    $is_whitelisted = isset($whitelist[$current_domain]);
    if (!$is_whitelisted) {

      // Loop through the list of wildcards, trying to match the current domain.
      $whitelist_wild = variable_get('boost_domain_whitelist_wild', array());
      $current_domain_array = explode('.', $current_domain);

      // www.bar.com -> array('www','bar','com')
      foreach ($whitelist_wild as $wildcard) {
        $wildcard_array = explode('.', $wildcard);

        // *.quux.qz -> array('*','quux','qz')
        // If the arrays aren't the same size, don't bother matching their contents.
        $wc_count = count($wildcard_array);
        if (count($current_domain_array) != $wc_count) {
          continue;
        }
        for ($i = 0; $i < $wc_count; $i++) {

          /* If the current element isn't * and doesn't match the pattern, skip
           * to the next wildcard. */
          if ($wildcard_array[$i] != '*' && $wildcard_array[$i] != $current_domain_array[$i]) {
            break;
          }

          /* If the loop hasn't terminated by now, all elements of the current
           * wildcard array were checked.  Mark this domain as whitelisted and
           * don't check any other whitelist elements.
           */
          if ($i == $wc_count - 1) {
            $is_whitelisted = TRUE;
            break 2;
          }
        }
      }
    }
    if (!$is_whitelisted && variable_get('boost_domain_whitelist_use_domain', FALSE) && function_exists('domain_alias_lookup')) {

      /* Either I call domain_alias_lookup (and deal with the cost), I ignore
       * its wildcard capabilities and stuff all aliases into
       * boost_domain_whitelist, or I implement something subtly incompabile. */
      $is_whitelisted = -1 != domain_alias_lookup($current_domain);
    }
    if (!$is_whitelisted) {
      return FALSE;
    }
  }

  // Check if this domain has been blacklisted for caching.
  if ($use_lists == BOOST_DOMAIN_BLACKLIST_ONLY || $use_lists == BOOST_DOMAIN_BOTH_LISTS) {
    $blacklist = variable_get('boost_domain_blacklist', array());
    if (isset($blacklist[$current_domain])) {
      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 . "]/", $full)) {
    return FALSE;
  }

  // Don't cache if path is in the source; but allow the front page to be cached
  if (variable_get('boost_cache_url_alias_src', FALSE) && !$is_front && (int) db_result(db_query("SELECT count(*) FROM {url_alias} WHERE src = '%s'", $path)) > 0) {
    return FALSE;
  }

  // Invoke hook_boost_is_cacheable($path)
  foreach (module_implements('boost_is_cacheable') as $module) {
    if (($result = module_invoke($module, 'boost_is_cacheable', $path)) !== NULL) {
      if (!$result) {
        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);
    }
    else {
      $page_match = drupal_eval(BOOST_CACHEABILITY_PAGES);
    }
  }
  else {
    $page_match = TRUE;
  }
  return $page_match;
}

/**
 * This hook is run inorder to determine if a page should be cached.
 *  Runs in boost_init().
 *
 * @return FALSE to not cache the page, TRUE to cache the page.
 *  Returning a FALSE is absolute, Returning TRUE is more like an ignore,
 *  doesn't guarantee it will be cached; some other setting could make the
 *  is_boost_cacheable() call return FALSE to boost_init()
 *
 */
function hook_boost_is_cacheable($path) {
  return TRUE;
}

/**
 * Determines whether a given Drupal page is currently cached or not.
 *
 * @param $path
 *   Current URL
 */
function boost_is_cached($path) {

  // no more need to check if path is empty cause it is done on the input of this function before calling it
  // no more need to use drupal_get_normal_path - we do not need the internal path (node/56) - we are fine with aliases
  return file_exists(boost_file_path($path));
}

/**
 * Deletes all files currently in the cache.
 */
function boost_cache_clear_all() {
  global $_boost;
  if (variable_get('boost_ignore_flush', 0) == 0) {
    boost_cache_clear_all_db();
    boost_cache_delete(TRUE);
    if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_cache_clear_all'])) {
      watchdog('boost', 'Flushed ALL files from static page cache.', array(), WATCHDOG_NOTICE);
    }
    return TRUE;
  }
  return FALSE;
}

/**
 * Deletes all expired static files currently in the cache via filesystem.
 */
function boost_cache_expire_all_filesystem() {
  boost_cache_delete(FALSE);
  return TRUE;
}

/**
 * Resets all entries in database.
 */
function boost_cache_clear_all_db() {
  if (BOOST_FLUSH_ALL_MULTISITE) {
    db_query("UPDATE {boost_cache} SET expire = %d", 0);
  }
  else {
    db_query("UPDATE {boost_cache} SET expire = %d WHERE base_dir = '%s'", 0, BOOST_FILE_PATH);
  }
}

/**
 * Deletes files in the cache.
 *
 * @param $flush
 *   If true clear the entire cache directory.
 */
function boost_cache_delete($flush = FALSE) {
  clearstatcache();

  //recreate dirs
  _boost_mkdir_p(BOOST_FILE_PATH);
  _boost_mkdir_p(BOOST_GZIP_FILE_PATH);

  //add in .boost root id file
  _boost_write_file_chmod(BOOST_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
  _boost_write_file_chmod(BOOST_GZIP_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
  foreach (_boost_copy_file_get_domains(BOOST_PERM_FILE_PATH) as $dir) {
    _boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
  }
  foreach (_boost_copy_file_get_domains(BOOST_PERM_GZIP_FILE_PATH) as $dir) {
    _boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
  }

  //Flush Cache
  if (file_exists(BOOST_FILE_PATH)) {
    _boost_rmdir_rf(BOOST_FILE_PATH, $flush, TRUE);
  }
  if (file_exists(BOOST_GZIP_FILE_PATH)) {
    _boost_rmdir_rf(BOOST_GZIP_FILE_PATH, $flush, TRUE);
  }

  //recreate dirs
  _boost_mkdir_p(BOOST_FILE_PATH);
  _boost_mkdir_p(BOOST_GZIP_FILE_PATH);

  //add in .boost root id file
  _boost_write_file_chmod(BOOST_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
  _boost_write_file_chmod(BOOST_GZIP_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH);
  foreach (_boost_copy_file_get_domains(BOOST_PERM_FILE_PATH) as $dir) {
    _boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
  }
  foreach (_boost_copy_file_get_domains(BOOST_PERM_GZIP_FILE_PATH) as $dir) {
    _boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
  }

  // Make sure cache dir has htaccess rules
  boost_htaccess_cache_dir_put();
}
function boost_htaccess_cache_dir_put() {

  // Server is not apache; do nothing
  if (stristr($_SERVER["SERVER_SOFTWARE"], 'apache') == FALSE) {
    return TRUE;
  }

  // Get some info
  $cache_dir = BOOST_ROOT_CACHE_DIR;
  $filename = $cache_dir . '/.htaccess';
  $generated = boost_htaccess_cache_dir_generate();
  $htaccess = file_exists($filename) ? file_get_contents($filename) : FALSE;

  // htaccess exists and has the correct contents
  if ($htaccess && strcmp($htaccess, $generated) === 0) {
    return TRUE;
  }

  // cache dir doesn't exist, try to create it; if no go, bail out.
  if (!is_dir($cache_dir) && !_boost_mkdir_p($cache_dir)) {
    return FALSE;
  }

  // cache dir htaccess is not there, create it.
  $result = file_put_contents($filename, $generated);
  if ($result) {
    return $result;
  }
  else {
    return FALSE;
  }
}
function boost_htaccess_cache_dir_generate() {
  global $base_path;

  // no dot
  $_html = str_replace('.', '', BOOST_FILE_EXTENSION);
  $_xml = str_replace('.', '', BOOST_XML_EXTENSION);
  $_css = str_replace('.', '', BOOST_CSS_EXTENSION);
  $_js = str_replace('.', '', BOOST_JS_EXTENSION);
  $_json = str_replace('.', '', BOOST_JSON_EXTENSION);
  $_gz = str_replace('.', '', BOOST_GZIP_EXTENSION);

  // with a \ slash
  $gz = str_replace('.', '\\.', BOOST_GZIP_EXTENSION);
  $string = '';
  if (BOOST_CACHE_HTML) {
    if (variable_get('boost_force_utf8', TRUE)) {
      $string .= "AddDefaultCharset utf-8\n";
    }
  }
  switch (variable_get('boost_apache_etag', 0)) {
    case 0:
      break;
    case 1:
      $string .= "FileETag None\n";
      break;
    case 2:
      $string .= "FileETag All\n";
      break;
    case 3:
      $string .= "FileETag MTime Size\n";
      break;
  }
  if (!BOOST_DISABLE_CLEAN_URL && (BOOST_CACHE_HTML || BOOST_CACHE_XML || BOOST_CACHE_JSON)) {
    $files = array();
    if (BOOST_CACHE_HTML) {
      $files[] = $_html;
    }
    if (BOOST_CACHE_XML) {
      $files[] = $_xml;
    }
    if (BOOST_CACHE_JSON) {
      $files[] = $_json;
    }
    $files = '(' . implode('|', $files) . ')';
    if (BOOST_GZIP) {
      $files = "{$files}|({$files}{$gz})";
    }
    $string .= "<FilesMatch \"\\.({$files})\$\">\n";
    $string .= "  <IfModule mod_expires.c>\n";
    $string .= "    ExpiresDefault A1\n";
    $string .= "  </IfModule>\n";
    $string .= "  <IfModule mod_headers.c>\n";
    $string .= "    Header set Expires \"Sun, 19 Nov 1978 05:00:00 GMT\"\n";
    $string .= "    Header set Cache-Control \"no-store, no-cache, must-revalidate, post-check=0, pre-check=0\"\n";
    if (variable_get('boost_apache_xheader', 0) > 0) {
      $string .= "    Header set X-Header \"Boost Citrus 1.8\"\n";
    }
    $string .= "  </IfModule>\n";
    $string .= "</FilesMatch>\n";
  }
  if (BOOST_CACHE_HTML || BOOST_CACHE_XML || BOOST_CACHE_CSS || BOOST_CACHE_JS || BOOST_CACHE_JSON) {
    $string .= "<IfModule mod_mime.c>\n";
    $string .= BOOST_CACHE_HTML ? "  AddCharset utf-8 .{$_html}\n" : '';
    $string .= BOOST_CACHE_XML ? "  AddCharset utf-8 .{$_xml}\n" : '';
    $string .= BOOST_CACHE_CSS ? "  AddCharset utf-8 .{$_css}\n" : '';
    $string .= BOOST_CACHE_JS ? "  AddCharset utf-8 .{$_js}\n" : '';
    $string .= BOOST_CACHE_JSON ? "  AddCharset utf-8 .{$_json}\n" : '';
    $string .= BOOST_GZIP ? "  AddEncoding gzip .{$_gz}\n" : '';
    $string .= "</IfModule>\n";
  }

  // Fix for versions of apache that do not respect the T='' RewriteRule
  $files = '';
  if (BOOST_CACHE_HTML) {
    $files .= "{$_html}|";
    if (BOOST_GZIP) {
      $files .= "{$_html}{$gz}|";
    }
    $files = trim($files, '|');
    $string .= "<FilesMatch \"\\.({$files})\$\">\n";
    $string .= "  ForceType text/html\n";
    $string .= "</FilesMatch>\n";
  }
  $files = '';
  if (BOOST_CACHE_XML) {
    $files .= "{$_xml}|";
    if (BOOST_GZIP) {
      $files .= "{$_xml}{$gz}|";
    }
    $files = trim($files, '|');
    $string .= "<FilesMatch \"\\.({$files})\$\">\n";
    $string .= "  ForceType text/xml\n";
    $string .= "</FilesMatch>\n";
  }
  $files = '';
  if (BOOST_CACHE_JSON) {
    $files .= "{$_json}|";
    if (BOOST_GZIP) {
      $files .= "{$_json}{$gz}|";
    }
  }
  if (BOOST_CACHE_JS) {
    $files .= "{$_js}|";
    if (BOOST_GZIP) {
      $files .= "{$_js}{$gz}|";
    }
  }
  if ($files != '') {
    $files = trim($files, '|');
    $string .= "<FilesMatch \"\\.({$files})\$\">\n";
    $string .= "  ForceType text/javascript\n";
    $string .= "</FilesMatch>\n";
  }
  $files = '';
  if (BOOST_CACHE_CSS) {
    $files .= "{$_css}|";
    if (BOOST_GZIP) {
      $files .= "{$_css}{$gz}|";
    }
    $files = trim($files, '|');
    $string .= "<FilesMatch \"\\.({$files})\$\">\n";
    $string .= "  ForceType text/css\n";
    $string .= "</FilesMatch>\n";
  }
  $string .= "\n";
  $string .= "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\n";
  $string .= "Options None\n";
  $string .= "Options +FollowSymLinks\n";
  $string .= "\n";
  return $string;
}

/**
 * Finds all possible paths/redirects/aliases given the root path.
 *
 * @param $paths
 *   Array of current URLs
 * @param $both
 *   Expire database & file
 * @param $force_flush
 *   Override the settings and kill the file
 */
function boost_cache_expire_derivative($paths, $both = FALSE, $force_flush = FALSE, $debug = FALSE) {
  global $base_path;
  $expire = array();
  if (empty($paths)) {
    return FALSE;
  }
  foreach ($paths as $path) {

    // Given path
    $expire[] = $path;

    // Add the empty front page path if this is the alias
    if ($path == variable_get('site_frontpage', 'node')) {
      $expire[] = '';
      $expire[] = 'rss.xml';
    }

    // Special front page feed handling
    if (BOOST_CACHE_XML && ($path == '' || $path == '<front>')) {
      $expire[] = 'rss.xml';
    }

    // Path alias
    $path_alias = url($path, array(
      'absolute' => FALSE,
    ));
    if ($base_path != '/') {
      $path_alias = implode('/', array_diff_assoc(array_filter(explode('/', $path_alias)), array_filter(explode('/', $base_path))));
    }
    $expire[] = $path_alias;

    // Path redirects
    if (module_exists('path_redirect')) {
      $path_redirects = boost_path_redirect_load(array(
        'redirect' => $path,
      ));
      if (isset($path_redirects)) {
        foreach ($path_redirects as $path_redirect) {
          $expire[] = $path_redirect['path'];
        }
      }
    }
  }

  // Expire cached files
  $counter = 0;
  if (empty($expire)) {
    return FALSE;
  }
  $expire = array_unique($expire);
  if (!$debug) {
    if (BOOST_NO_DATABASE) {
      $counter += boost_cache_expire_by_filename($expire, TRUE, $force_flush);
    }
    elseif ($both) {
      $counter += boost_cache_expire_by_db($expire);
      $counter += boost_cache_expire_by_filename($expire, TRUE, $force_flush);
    }
    else {
      $counter += boost_cache_expire_by_db($expire);
      if ($counter == 0) {

        // Database was a negative. Fallback: Look into flushing by filename
        $counter += boost_cache_expire_by_filename($expire, TRUE, $force_flush);
      }
    }
    return $counter;
  }
  else {
    return array(
      'in' => $expire,
      'db' => boost_cache_expire_by_db($expire, TRUE),
      'filename' => boost_cache_expire_by_filename($expire, TRUE, FALSE, TRUE),
    );
  }
}

/**
 * Expires the static file cache for the given paths via database.
 *
 * @param $paths
 *   Array of URL's
 * @param $debug
 *   TRUE to display what would have happened
 */
function boost_cache_expire_by_db($paths, $debug = FALSE) {
  $hashes = array();
  if (empty($paths)) {
    return FALSE;
  }

  // Get all cache files directly associated with this path
  foreach ($paths as $path) {

    // With URL Variables
    $html = boost_file_path($path, TRUE, BOOST_FILE_EXTENSION);
    if ($html !== FALSE) {
      $xml = boost_file_path($path, TRUE, BOOST_XML_EXTENSION);
      $json = boost_file_path($path, TRUE, BOOST_JSON_EXTENSION);

      // Hash the paths
      $hashes[] = md5($html);
      $hashes[] = md5($xml);
      $hashes[] = md5($json);
    }

    // Without URL Variables
    $html = boost_file_path($path, FALSE, BOOST_FILE_EXTENSION);
    if ($html !== FALSE) {
      $xml = boost_file_path($path, FALSE, BOOST_XML_EXTENSION);
      $json = boost_file_path($path, FALSE, BOOST_JSON_EXTENSION);

      // Hash the paths
      $hashes[] = md5($html);
      $hashes[] = md5($xml);
      $hashes[] = md5($json);
    }
  }
  $result = boost_db_multi_select_in('boost_cache', 'hash', "'%s'", $hashes);

  // Eliminate duplicates with the key hash
  $data = array();
  $counter = 0;
  $filenames = array();
  if ($result) {
    while ($info = db_fetch_array($result)) {
      if (($info['page_callback'] == 'node' || $info['page_callback'] == 'taxonomy') && $info['page_id'] == 0) {

        // If we can't get a 'lock' just expire the file
        $filenames[] = $info['filename'];
      }
      elseif ($info['page_id'] != '' && $info['page_type'] != '' && $info['page_callback'] != '') {

        // Use boost_cache_expire_router() if we can get a 'lock' on this item in the database
        $hash = BOOST_FILE_PATH . $info['page_callback'] . $info['page_type'] . $info['page_id'];
        $data[$hash] = $info;
      }
      else {

        // If we can't get a 'lock' just expire the file
        $filenames[] = $info['filename'];
      }
    }

    // Expire all files that match up
    if (!$debug) {
      if ($data) {
        boost_set_base_dir_in_array($data);
        $counter += boost_cache_expire_router($data);
      }
      if ($filenames) {
        $counter += boost_cache_flush_by_filename($filenames);
      }
    }
    else {
      boost_set_base_dir_in_array($data);
      return array(
        $data,
        $filenames,
      );
    }
  }
  return $counter;
}

/**
 * Expires the static file cache for paths matching a wildcard via filesystem.
 *
 * @param $path
 *   Array of URLs
 * @param $wildcard
 *   If true get all chached files that start with this path.
 * @param $force_flush
 *   If true kill file no matter what.
 */
function boost_cache_expire_by_filename($paths, $wildcard = TRUE, $force_flush, $debug = FALSE) {
  $filenames = array();
  if (empty($paths)) {
    return FALSE;
  }
  foreach ($paths as $path) {

    // Sanity check
    if (boost_file_path($path, FALSE) === FALSE) {
      continue;
    }

    // Get list of related files
    $html = glob(boost_file_path($path, FALSE, NULL) . ($wildcard ? '*' : '') . BOOST_FILE_EXTENSION, GLOB_NOSORT);
    $xml = glob(boost_file_path($path, FALSE, NULL) . ($wildcard ? '*' : '') . BOOST_XML_EXTENSION, GLOB_NOSORT);
    $json = glob(boost_file_path($path, FALSE, NULL) . ($wildcard ? '*' : '') . BOOST_JSON_EXTENSION, GLOB_NOSORT);

    // Make sure something is in the arrays
    $html[] = '';
    $xml[] = '';
    $json[] = '';

    // Merge arrays
    $filenames = array_filter(array_merge($filenames, $html, $xml, $json));
  }

  // Remove double slash from filename if it exists.
  foreach ($filenames as $key => $filename) {
    $filenames[$key] = implode('/', array_filter(explode('/', $filename)));
  }

  // Flush expired files
  if (!$debug) {
    boost_cache_flush_by_filename($filenames, $force_flush);
  }
  else {
    return $filenames;
  }
}

/**
 * Expires the static file cache for files given.
 *
 * @param array $filenames
 *   filenames
 * @param $force_flush
 *   If true get all chached files that start with this path.
 */
function boost_cache_flush_by_filename($filenames, $force_flush = FALSE) {
  global $_boost;
  $files = array();
  if ($filenames) {
    $filenames = array_unique($filenames);
    foreach ($filenames as $filename) {
      $files[] = array(
        'filename' => $filename,
      );
    }
    $counter = boost_cache_kill($files, $force_flush);
    if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_flush_filename'])) {
      watchdog('boost', 'Debug: boost_cache_flush_by_filename() <br />Following files where flushed: <br />!list', array(
        '!list' => implode('<br />', $filenames),
      ));
    }
    return $counter;
  }
  else {
    return FALSE;
  }
}

/**
 * Expires the static file cache for the given router items.
 *
 * @param $router_items
 *   Array of $router_item array "objects"
 *     ['page_callback']
 *     ['page_type']
 *     ['page_id']
 *     ['base_dir']
 * @param $force_flush
 *   Override BOOST_EXPIRE_NO_FLUSH setting
 * @param $remove_from_db
 *   Remove this entry from the boost-cache table.
 * @param $debug
 *   Do nothing but return what would have been expired.
 */
function boost_cache_expire_router($router_items, $force_flush = FALSE, $remove_from_db = FALSE, $debug = FALSE) {

  // Get filenames & hash from db
  if (!is_array($router_items)) {
    return FALSE;
  }
  global $_boost;
  $count = 0;
  $files = $list = array();
  foreach ($router_items as $dblookup) {
    if (isset($dblookup['base_dir'])) {
      if (isset($dblookup['page_id']) && isset($dblookup['page_type']) && isset($dblookup['page_callback'])) {
        $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_type'], $dblookup['page_id']);
        if (!$debug) {
          db_query("DELETE FROM {boost_cache_relationships} WHERE base_dir = '%s' AND page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_type'], $dblookup['page_id']);
        }
      }
      elseif (isset($dblookup['page_id']) && isset($dblookup['page_callback'])) {
        $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = '%s' AND page_id = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_id']);
        if (!$debug) {
          db_query("DELETE FROM {boost_cache_relationships} WHERE base_dir = '%s' AND page_callback = '%s' AND page_id = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_id']);
        }
      }
      elseif (isset($dblookup['page_type']) && isset($dblookup['page_callback'])) {
        $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = '%s' AND page_type = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_type']);
        if (!$debug) {
          db_query("DELETE FROM {boost_cache_relationships} WHERE base_dir = '%s' AND page_callback = '%s' AND page_type = '%s'", $dblookup['base_dir'], $dblookup['page_callback'], $dblookup['page_type']);
        }
      }
      elseif (isset($dblookup['page_callback'])) {
        $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND page_callback = '%s'", $dblookup['base_dir'], $dblookup['page_callback']);
        if (!$debug) {
          db_query("DELETE FROM {boost_cache_relationships} WHERE base_dir = '%s' AND page_callback = '%s'", $dblookup['base_dir'], $dblookup['page_callback']);
        }
      }
      else {
        continue;
      }
    }
    else {
      if (isset($dblookup['page_id']) && isset($dblookup['page_type']) && isset($dblookup['page_callback'])) {
        $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $dblookup['page_callback'], $dblookup['page_type'], $dblookup['page_id']);
        if (!$debug) {
          db_query("DELETE FROM {boost_cache_relationships} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $dblookup['page_callback'], $dblookup['page_type'], $dblookup['page_id']);
        }
      }
      elseif (isset($dblookup['page_id']) && isset($dblookup['page_callback'])) {
        $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE page_callback = '%s' AND page_id = '%s'", $dblookup['page_callback'], $dblookup['page_id']);
        if (!$debug) {
          db_query("DELETE FROM {boost_cache_relationships} WHERE page_callback = '%s' AND page_id = '%s'", $dblookup['page_callback'], $dblookup['page_id']);
        }
      }
      elseif (isset($dblookup['page_type']) && isset($dblookup['page_callback'])) {
        $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE page_callback = '%s' AND page_type = '%s'", $dblookup['page_callback'], $dblookup['page_type']);
        if (!$debug) {
          db_query("DELETE FROM {boost_cache_relationships} WHERE page_callback = '%s' AND page_type = '%s'", $dblookup['page_callback'], $dblookup['page_type']);
        }
      }
      elseif (isset($dblookup['page_callback'])) {
        $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE page_callback = '%s'", $dblookup['page_callback']);
        if (!$debug) {
          db_query("DELETE FROM {boost_cache_relationships} WHERE page_callback = '%s'", $dblookup['page_callback']);
        }
      }
      else {
        continue;
      }
    }
    while ($info = db_fetch_array($result)) {
      if (stristr($info['filename'], '#')) {
        continue;
      }
      $files[$info['hash']] = $info;
      if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_expire_router'])) {
        $list[] = $info['filename'];
      }
    }
  }
  if (!$debug) {
    if (count($files)) {
      $count = boost_cache_kill($files, $force_flush);
      if ($remove_from_db) {
        boost_remove_db($files);
      }
    }
    if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_expire_router'])) {
      watchdog('boost', 'Debug: boost_cache_expire_router() <br />Following files where flushed: <br />!list<br /><br />Input: <br /> !input <br />Files: !files', array(
        '!list' => implode('<br />', $list),
        '!input' => boost_print_r($router_items, TRUE, TRUE),
        '!files' => boost_print_r($files, TRUE, TRUE),
      ));
    }
    return $count;
  }
  else {
    return $files;
  }
}

/**
 * Deletes cached page from file system & database.
 *
 * @param array $urls
 *   list of urls to remove from the boost cache
 * @param boolean $force_flush = TRUE
 *   Override BOOST_EXPIRE_NO_FLUSH setting.
 */
function boost_cache_kill_url($urls, $force_flush = TRUE) {
  global $base_path;
  $files = array();
  foreach ($urls as $value) {
    $decoded = urldecode($value);
    if ($decoded != $value) {
      $urls[] = $decoded;
    }
    $raw = rawurldecode($value);
    if ($raw != $decoded) {
      $urls[] = $decoded;
    }
  }
  $urls = array_unique($urls);
  $hashes = array_map('md5', $urls);
  $parts = array_map('parse_url', $urls);
  foreach ($parts as $part) {
    $files[]['filename'] = boost_file_path(ltrim($part['path'], $base_path), TRUE, BOOST_FILE_EXTENSION);
  }
  $result = boost_db_multi_select_in('boost_cache', 'hash_url', "'%s'", $hashes);
  while ($row = db_fetch_array($result)) {
    $files[] = array(
      'filename' => $row['filename'],
      'hash' => $row['hash'],
    );
  }
  if (!empty($files)) {
    boost_cache_kill($files, $force_flush);
    boost_remove_db($files);
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Deletes cached page from file system.
 *
 * @param array $files
 *  An array of files. Each file is a secondary array must have a key for 'filename'
 *  Optional keys for 'hash' and 'base_dir' that will be recalculated if necessary.
 *  The hash is the primary key in the database. If omitted it will be recalculated from the filename.
 * @param boolean $force_flush = FALSE
 *  Override BOOST_EXPIRE_NO_FLUSH setting.
 */
function boost_cache_kill($files, $force_flush = FALSE) {
  $hashes = array();
  $count = 0;
  if (!$files) {
    return FALSE;
  }

  // If not ignoring file removal
  // AND site is multisite and cache path matches filename
  //  OR full base url matches filename
  if (variable_get('boost_ignore_flush', 0) < 3) {

    // Calc md5 hash and set base dir
    foreach ($files as $key => $file) {
      if (!is_string($file['filename'])) {
        if (BOOST_VERBOSE >= 5) {
          watchdog('boost', 'Error in boost_cache_kill() <br />String was not given for filename: !output', array(
            '!output' => boost_print_r($file, TRUE, TRUE),
          ));
        }
        continue;
      }
      if (empty($file['hash'])) {
        $files[$key]['hash'] = md5($file['filename']);
      }
      if (empty($file['base_dir'])) {
        $files[$key]['base_dir'] = BOOST_FILE_PATH;
      }
      $hashes[] = $files[$key]['hash'];
      if (stristr($files[$key]['filename'], BOOST_ROOT_CACHE_DIR) == FALSE) {
        unset($files[$key]);
      }
    }

    // Expire entries from Database
    if (count($hashes)) {
      if ($force_flush || !BOOST_EXPIRE_NO_FLUSH) {
        boost_db_multi_update_set('boost_cache', 'expire', '%d', 0, 'hash', "'%s'", $hashes);
        boost_db_multi_update_set('boost_cache', 'timer', '%d', 0, 'hash', "'%s'", $hashes);
      }
      else {
        boost_db_multi_update_set('boost_cache', 'expire', '%d', 434966400, 'hash', "'%s'", $hashes);
        $count = db_affected_rows();
      }
    }

    // Kill Files from filesystem
    if ($force_flush || !BOOST_EXPIRE_NO_FLUSH) {
      foreach ($files as $file) {
        $filenames = boost_get_all_filenames($file['filename'], $file['base_dir']);
        foreach ($filenames as $key => $values) {
          foreach ($values as $num => $filename) {
            if (file_exists($filename)) {
              @unlink($filename);
              if ($key == 'normal' && $num == 0) {
                $count++;
              }
            }
          }
        }
      }
    }
  }
  return $count;
}

/**
 * Flushes all expired pages from cache.
 *
 * TODO del empty dirs if enabled
 */
function boost_cache_expire_all() {
  if (variable_get('boost_ignore_flush', 0) < 2) {
    if (BOOST_NO_DATABASE) {
      boost_cache_expire_all_filesystem();
    }
    else {
      boost_cache_expire_all_db();
    }
    return TRUE;
  }
  return FALSE;
}

/**
 * Flushes all expired pages via database lookup.
 *
 * TODO del empty dirs if enabled
 */
function boost_cache_expire_all_db() {
  global $_boost;
  $list = $files = array();
  if (BOOST_FLUSH_ALL_MULTISITE) {
    $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE expire BETWEEN 1 AND %d", BOOST_TIME);
  }
  else {
    $result = db_query("SELECT filename, hash, base_dir FROM {boost_cache} WHERE base_dir = '%s' AND expire BETWEEN 1 AND %d", BOOST_FILE_PATH, BOOST_TIME);
  }
  while ($boost = db_fetch_array($result)) {
    $files[] = $boost;
    if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_expire_all_db_list'])) {
      $list[] = $boost['filename'];
    }
  }
  if (count($files)) {
    $count = boost_cache_kill($files, TRUE);
  }
  if (BOOST_FLUSH_DIR) {

    // TO-DO: del empty dirs.
  }
  if (BOOST_VERBOSE >= 9 && isset($_boost['verbose_option_selected']['boost_cache_expire_all_db_list'])) {
    watchdog('boost', 'Debug: boost_cache_expire_all_db() <br />Following files where flushed: <br />!list', array(
      '!list' => implode('<br />', $list),
    ));
  }
  elseif (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_verbose_refined'])) {
    watchdog('boost', 'Debug: boost_cache_expire_all_db() <br />!num files where flushed', array(
      '!num' => $count,
    ));
  }
  return TRUE;
}

/**
 * Returns the cached contents of the specified page, if available.
 *
 * @param $path
 *   Current URL
 */
function boost_cache_get($path) {
  if ($filename = boost_file_path($path)) {
    if (file_exists($filename) && is_readable($filename)) {
      return file_get_contents($filename);
    }
  }
  return NULL;
}

/**
 * Returns all possible filenames given the input and current settings
 *
 * @param $filename
 *   Name of file
 * @param $base_dir
 *   Value from base_dir column in database
 * @return
 *   returns a 2 dimensional array
 *    1st dimension key is either gzip or normal
 *    2nd dimension contains all the filenames
 */
function boost_get_all_filenames($filename, $base_dir = NULL) {
  $namesA = array();
  $namesB = array();
  $filenames = array();
  $base_dir = is_null($base_dir) ? BOOST_FILE_PATH : $base_dir;
  if (stristr($filename, '[')) {
    $paths = explode('/', $filename);
    $end = array_pop($paths);
    $end = preg_replace("([[0-9]])", ']', $end);
    $paths[] = $end;
    $namesA[] = implode('/', $paths);
  }
  $namesA[] = $filename;
  foreach ($namesA as $filename) {
    $namesB[] = $filename;

    // Generate urlencoded filename, if name contains decoded characters
    $paths = explode('/', $filename);
    $end = array_pop($paths);
    $end = str_replace('[', '%5B', $end);
    $end = str_replace(']', '%5D', $end);
    $end = str_replace(',', '%2C', $end);
    $end = str_replace(' ', '%20', $end);
    $paths[] = $end;
    $namesB[] = implode('/', $paths);
  }
  $namesB = array_unique($namesB);

  // Generate gzip filenames
  foreach ($namesB as $name) {
    if (BOOST_SET_FILE_ENCODING != '') {
      $name = iconv("UTF-8", BOOST_SET_FILE_ENCODING, $name);
    }
    $filenames['normal'][] = $name;
    if (BOOST_GZIP) {

      // Replace the correct dir with the gzip version for the given base dir.
      $gzip_base_path = implode('/', array_filter(explode('/', str_replace(BOOST_ROOT_CACHE_DIR . '/' . BOOST_NORMAL_DIR, BOOST_ROOT_CACHE_DIR . '/' . BOOST_GZIP_DIR . '/', $base_dir))));
      $filenames['gzip'][] = str_replace($base_dir, $gzip_base_path, $name) . BOOST_GZIP_EXTENSION;
    }
  }
  return $filenames;
}

/**
 * Edit document before it is put into the boost cache.
 *
 * This hook is run at right before the page is cached by boost.
 *
 * $GLOBALS['_boost_cache_this'] and $GLOBALS['_boost_router_item'] are useful.
 * set $GLOBALS['_boost_cache_this'] = FALSE if you wish to not cache this page.
 *
 * @param $path
 *   URL path of the document
 * @param $data
 *   String containing the data
 * @param $extension
 *   file extension type. Use to detect what type of document your operating on.
 * @return
 *   $data string containing the document
 */
function hook_boost_preprocess($path, $data, $extension) {
  return $data;
}

/**
 * Replaces/Sets the cached contents of the specified page, if stale.
 *
 * @param $path
 *   Current URL
 * @param $data
 *   URL's contents
 * @param $extension
 *   File extension for this mime type
 */
function boost_cache_set($path, $data, $extension = BOOST_FILE_EXTENSION) {

  // Exit if nothing is here to cache
  if (empty($data)) {
    return FALSE;
  }

  // Get custom expiration time if set
  $router_item = _boost_get_menu_router();
  $settings = boost_get_settings_db($router_item);
  $expire = -2;
  foreach ($settings as $value) {
    if ($value != NULL) {
      $expire = $value['lifetime'];
      break;
    }
  }
  $cached_at = date('Y-m-d H:i:s', BOOST_TIME);

  // Code commenting style based on what is being cached.
  // Append the Boost footer with the relevant timestamps
  switch ($extension) {
    case BOOST_FILE_EXTENSION:
      $expire = $expire == -2 ? BOOST_CACHE_LIFETIME : $expire;
      if (variable_get('boost_apache_xheader', 0) < 2) {
        $comment_start = '<!-- ';
        $comment_end = " -->\n";
        $expires_at = date('Y-m-d H:i:s', BOOST_TIME + $expire);
        $comment = $comment_start . str_replace(array(
          '%cached_at',
          '%expires_at',
        ), array(
          $cached_at,
          $expires_at,
        ), BOOST_BANNER) . $comment_end;

        //$data = _boost_inject_code(rtrim($data), "\n" . $comment);
        $data = rtrim($data) . "\n" . $comment;
      }
      break;
    case BOOST_XML_EXTENSION:
      $expire = $expire == -2 ? BOOST_CACHE_XML_LIFETIME : $expire;
      if (variable_get('boost_apache_xheader', 0) < 2) {
        $comment_start = '<!-- ';
        $comment_end = " -->\n";
        $expires_at = date('Y-m-d H:i:s', BOOST_TIME + $expire);
        $comment = $comment_start . str_replace(array(
          '%cached_at',
          '%expires_at',
        ), array(
          $cached_at,
          $expires_at,
        ), BOOST_BANNER) . $comment_end;
        $data = rtrim($data) . "\n" . $comment;
      }
      break;
    case BOOST_JSON_EXTENSION:
      $expire = $expire == -2 ? BOOST_CACHE_JSON_LIFETIME : $expire;
      if (variable_get('boost_apache_xheader', 0) < 2) {
        $comment_start = '/* ';
        $comment_end = " */\n";
        $expires_at = date('Y-m-d H:i:s', BOOST_TIME + $expire);
        $comment = $comment_start . str_replace(array(
          '%cached_at',
          '%expires_at',
        ), array(
          $cached_at,
          $expires_at,
        ), BOOST_BANNER) . $comment_end;
        $data = rtrim($data) . "\n" . $comment;
      }
      break;
  }

  // Invoke hook_boost_preprocess($path, $data, $extension)
  foreach (module_implements('boost_preprocess') as $module) {
    if (($result = module_invoke($module, 'boost_preprocess', $path, $data, $extension)) != NULL) {
      $data = $result;
    }
  }

  // Execute the pre-process function if one has been defined
  if (function_exists(BOOST_PRE_PROCESS_FUNCTION)) {
    $data = call_user_func(BOOST_PRE_PROCESS_FUNCTION, $path, $data, $extension);
  }
  db_set_active();

  // Final check, make sure this page should be cached. Allow for the preprocess
  // function to have a final say in if this page should be cached.
  if (!$GLOBALS['_boost_cache_this'] || empty($data)) {
    return FALSE;
  }

  // Create or update the static files as needed
  if (($filename = boost_file_path($path, TRUE, $extension)) && (BOOST_OVERWRITE_FILE || !file_exists($filename) || boost_db_is_expired($filename))) {

    // Special handling of the front page for aggressive gzip test
    if ($path == '' && BOOST_AGGRESSIVE_GZIP && $extension == BOOST_FILE_EXTENSION) {
      _boost_generate_gzip_test_file();
      boost_cache_write($filename, _boost_inject_code($data, '<script type="text/javascript">
<!--//--><![CDATA[//><!--
function boost_gzip_test_ready() {
  if(boost_xhr.readyState != 4) {
    setTimeout(boost_gzip_test_ready, 1000);
  }
  else if (boost_xhr.getResponseHeader("Content-Encoding") == "gzip" && boost_xhr.responseText.indexOf("</html>") != -1) {
    var date = new Date();
    var days = 14;
    date.setTime(date.getTime() + (days * 24*60*60*1000));
    expires = "; expires=" + date.toUTCString();
    document.cookie = "boost-gzip=true" + expires + "; path=/"
  }
}

var boost_xhr = $.ajax({url: Drupal.settings.basePath + "boost-gzip-cookie-test.html"});
boost_gzip_test_ready();
//--><!]]>
</script>' . "\n"));
    }
    else {
      boost_cache_write($filename, $data);
    }
    if (!BOOST_NO_DATABASE) {
      boost_db_prep($filename, $extension, BOOST_TIME + $expire);
      boost_cache_set_node_relationships(isset($GLOBALS['_boost_relationships']) ? $GLOBALS['_boost_relationships'] : array());
    }
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Creates a parent child relationship for pages like views.
 *
 * @param $relationships
 *   Array of $router_item array "objects"
 *    Required
 *     ['child_page_callback']
 *     ['child_page_type']
 *     ['child_page_id']
 *    Optional
 *     ['base_dir']
 *     ['page_callback']
 *     ['page_type']
 *     ['page_id']
 *
 * If Optional is not set it will use values in $GLOBALS['_boost_router_item']
 */
function boost_cache_set_node_relationships($relationships) {
  global $base_root, $_boost;
  $url = $base_root . request_uri();
  if (variable_get('boost_store_url_percent_enc', FALSE)) {
    $url = urldecode(rawurlencode($url));
  }
  $hash_url = md5($url);
  $router_item = _boost_get_menu_router();
  if (!is_array($relationships) || empty($relationships)) {
    return FALSE;
  }

  // Grab all entries related to this url
  $old_data = array();
  $results = db_query("SELECT hash, timestamp FROM {boost_cache_relationships} WHERE hash_url = '%s'", $hash_url);
  while ($row = db_fetch_array($results)) {
    $old_data[$row['hash']] = $row['timestamp'];
  }
  $counter = 0;
  $new_data = array();
  foreach ($relationships as $data) {

    // If one of the required items is not set, skip this entry
    if (!isset($data['child_page_callback']) || !isset($data['child_page_type']) || !isset($data['child_page_id'])) {
      if (BOOST_VERBOSE >= 5) {
        watchdog('boost', 'boost_cache_set_node_relationships() <br /> child_page_* was not set.<br /> !data<br /> !backtrace', array(
          '!data' => boost_print_r($data, TRUE, TRUE),
          '!backtrace' => boost_backtrace(FALSE),
        ));
      }
      continue;
    }

    // Set the optional parameters
    $data['base_dir'] = isset($data['base_dir']) ? $data['base_dir'] : BOOST_FILE_PATH;
    $data['page_callback'] = isset($data['page_callback']) ? $data['page_callback'] : $router_item['page_callback'];
    $data['page_type'] = isset($data['page_type']) ? $data['page_type'] : $router_item['page_type'];
    $data['page_id'] = isset($data['page_id']) ? $data['page_id'] : $router_item['page_id'];

    // Sanity Checks
    foreach ($data as $key => $value) {
      $loop = 0;
      while (is_array($data[$key]) && $loop < 25) {
        $data[$key] = array_pop($value);
        $loop++;
      }
    }
    foreach ($data as $key => $value) {
      if (is_object($value) || is_array($value)) {
        if (BOOST_VERBOSE >= 5) {
          watchdog('boost', 'boost_cache_set_node_relationships() <br />"!key" has a value of "!value"; should be a string or int.', array(
            '!key' => $key,
            '!value' => $value,
          ));
        }
        continue;
      }
    }

    // Skip if this is referencing its self.
    if ($data['page_callback'] == $data['child_page_callback'] && $data['page_type'] == $data['child_page_type'] && $data['page_id'] == $data['child_page_id']) {
      continue;
    }

    // Create the primary key
    $data['hash'] = md5($data['base_dir'] . $data['page_callback'] . $data['page_type'] . $data['page_id'] . $data['child_page_callback'] . $data['child_page_type'] . $data['child_page_id']);
    $new_data[] = $data;
  }

  // Count number of entries
  $update_required = FALSE;
  if (count($new_data) !== count($old_data)) {
    $update_required = TRUE;
  }

  // See if new and old data is not the same
  if (!$update_required) {
    foreach ($new_data as $data) {
      if (empty($old_data[$data['hash']])) {
        $update_required = TRUE;
        break;
      }
    }
  }
  if ($update_required) {
    foreach ($new_data as $data) {

      // Insert data into database
      db_query("UPDATE {boost_cache_relationships}\n      SET base_dir = '%s',\n      page_callback = '%s',\n      page_type = '%s',\n      page_id = '%s',\n      child_page_callback = '%s',\n      child_page_type = '%s',\n      child_page_id = '%s',\n      hash_url = '%s',\n      timestamp = '%d'\n      WHERE hash = '%s'", $data['base_dir'], $data['page_callback'], $data['page_type'], $data['page_id'], $data['child_page_callback'], $data['child_page_type'], $data['child_page_id'], $hash_url, BOOST_TIME, $data['hash']);
      if (!db_affected_rows()) {
        @db_query("INSERT INTO {boost_cache_relationships} (hash, base_dir, page_callback, page_type, page_id, child_page_callback, child_page_type, child_page_id, hash_url, timestamp) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)", $data['hash'], $data['base_dir'], $data['page_callback'], $data['page_type'], $data['page_id'], $data['child_page_callback'], $data['child_page_type'], $data['child_page_id'], $hash_url, BOOST_TIME);
      }
      $counter++;
    }
    $removed = boost_cache_prune_node_relationship($hash_url);
    if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_cache_set_node_relationships'])) {
      watchdog('boost', 'Debug: boost_cache_set_node_relationships() <br />!num of !total given entries to the boost_cache_relationships table added or updated; !removed entries removed due to them being outdated. !dump', array(
        '!num' => $counter,
        '!total' => count($GLOBALS['_boost_relationships']),
        '!removed' => $removed,
        '!dump' => str_replace('    ', '&nbsp;&nbsp;&nbsp;&nbsp;', nl2br(htmlentities(print_r($relationships, TRUE)))),
      ));
    }
  }
  unset($GLOBALS['_boost_relationships']);
  return TRUE;
}

/**
 * Creates a parent child relationship for pages like views.
 *
 * @param $relationships
 *   Array of $router_item array "objects"
 *    Required
 *     ['page_callback']
 *     ['page_type']
 *     ['page_id']
 *    Optional
 *     ['base_dir']
 */
function boost_cache_get_node_relationships($relationships) {
  if (!is_array($relationships)) {
    return FALSE;
  }
  $results = array();
  foreach ($relationships as $data) {

    // If one of the required items is not set, skip this entry
    if (!isset($data['page_callback']) || !isset($data['page_type']) || !isset($data['page_id'])) {
      continue;
    }
    if (BOOST_FLUSH_ALL_MULTISITE) {
      $result = db_query("SELECT page_callback, page_type, page_id, base_dir FROM {boost_cache_relationships} WHERE child_page_id = '%s' AND child_page_type = '%s' AND child_page_callback = '%s'", $data['page_id'], $data['page_type'], $data['page_callback']);
    }
    else {
      $data['base_dir'] = isset($data['base_dir']) ? $data['base_dir'] : BOOST_FILE_PATH;
      $result = db_query("SELECT page_callback, page_type, page_id, base_dir FROM {boost_cache_relationships} WHERE child_page_id = '%s' AND child_page_type = '%s' AND child_page_callback = '%s' AND base_dir = '%s'", $data['page_id'], $data['page_type'], $data['page_callback'], $data['base_dir']);
    }
    while ($info = db_fetch_array($result)) {
      $hash = 'relationship base:' . $info['base_dir'] . ' ' . $info['page_callback'] . ' ' . $info['page_type'] . ' ' . $info['page_id'];
      $results[$hash] = $info;
    }
  }
  return $results;
}

/**
 * Given hash of url delete any old relationships.
 *
 * @param $hash_url
 */
function boost_cache_prune_node_relationship($hash_url) {

  // Grab all entires related to this URL; find ones that don't match the latest
  // timestamp and remove them.
  $records = 0;
  $result = db_query("SELECT hash, timestamp FROM {boost_cache_relationships} WHERE hash_url = '%s' ORDER BY timestamp DESC", $hash_url);
  while ($info = db_fetch_array($result)) {
    if ($info['timestamp'] < BOOST_TIME) {
      db_query("DELETE FROM {boost_cache_relationships} WHERE hash = '%s'", $info['hash']);
      $records++;
    }
  }
  return $records;
}

/**
 * Figure out what is going in the database & put it in
 *
 * @param $filename
 *   Name of cached file; primary key in database
 * @param $extension
 *  Filename extension: Used for content types.
 * @param $expire
 *  Cache expiration time in seconds (UNIX time).
 */
function boost_db_prep($filename, $extension, $expire) {
  $router_item = _boost_get_menu_router();
  $timer = timer_read('page');
  $timer_average = $timer;
  $lifetime = -1;
  $push = -1;
  $settings = boost_get_settings_db($router_item);
  foreach ($settings as $value) {
    if ($value != NULL) {
      $boost_settings_db = $value;
      break;
    }
  }
  $boost_db = boost_get_db($filename);

  //get time data from actual entry, if this page has been cached before.
  if ($boost_db) {

    //  $expire = $boost_db['lifetime'] != -1 ? $boost_db['lifetime'] + BOOST_TIME : $expire;
    //  $lifetime = $boost_db['lifetime'];
    //  $push = $boost_db['push'];
    $timer_average = ($boost_db['timer_average'] + $timer) / 2;
  }

  //get data from settings table, if this page has not been put into the cache.
  if (isset($boost_settings_db)) {
    $expire = $boost_settings_db['lifetime'] != -1 ? $boost_settings_db['lifetime'] + BOOST_TIME : $expire;
    $lifetime = $boost_settings_db['lifetime'];
    $push = $boost_settings_db['push'];
  }
  boost_put_db($filename, $expire, $lifetime, $push, $router_item, $timer, $timer_average, $extension);
}

/**
 * Puts boost info into database.
 *
 * @param $filename
 *   Name of cached file; hash of this is primary key in database
 * @param $expire
 *   Expiration time
 * @param $lifetime
 *   Default lifetime
 * @param $push
 *   Pre-cache this file
 * @param $router_item
 *   Array containing page_callback, page_type & page_id.
 * @param $timer
 *   Time it took drupal to build this page.
 * @param $timer_average
 *   Average time Drupal has spent building this page.
 * @param $extension
 *   Filename extension: Used for content types.
 * @param $url
 *   Optional: Full URL of cached page
 * @param $file_path
 *   Optional: BOOST_FILE_PATH
 */
function boost_put_db($filename, $expire, $lifetime, $push, $router_item, $timer, $timer_average, $extension, $url = NULL, $file_path = NULL) {
  global $base_root;
  $url = is_null($url) ? $base_root . request_uri() : $url;
  $file_path = is_null($file_path) ? BOOST_FILE_PATH : $file_path;
  $hash = md5($filename);
  if (variable_get('boost_store_url_percent_enc', FALSE)) {
    $url = urldecode(rawurlencode($url));
  }
  $hash_url = md5($url);
  db_query("UPDATE {boost_cache} SET expire = %d, lifetime = %d, push = %d, page_callback = '%s', page_type = '%s', timer = %d, timer_average = %d, base_dir = '%s', page_id = '%s', extension = '%s', url = '%s', filename = '%s', hash_url = '%s' WHERE hash = '%s'", $expire, $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], $timer, $timer_average, $file_path, $router_item['page_id'], $extension, $url, $filename, $hash_url, $hash);
  if (!db_affected_rows()) {
    @db_query("INSERT INTO {boost_cache} (hash, hash_url, filename, expire, lifetime, push, page_callback, page_type, timer, timer_average, base_dir, page_id, extension, url) VALUES ('%s', '%s', '%s', %d, %d, %d, '%s', '%s', %d, %d, '%s', '%s', '%s', '%s')", $hash, $hash_url, $filename, $expire, $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], $timer, $timer_average, $file_path, $router_item['page_id'], $extension, $url);
  }
}

/**
 * Removes info from database. Use on 404 or 403.
 *
 * @param array $files
 *  An array of files. Each file is a secondary array with keys for 'filename' and 'hash'.
 *  The hash is the primary key in the database. If omitted it will be recalculated from the filename.
 */
function boost_remove_db($files) {
  $hashes = array();
  foreach ($files as $file) {
    if (empty($file['hash'])) {
      $file['hash'] = md5($file['filename']);
    }
    $hashes[] = $file['hash'];
  }
  if ($hashes) {
    boost_db_multi_delete_in('boost_cache', 'hash', "'%s'", $hashes);
  }
}

/**
 * Puts boost info into database.
 *
 * @param $expire
 *   Expiration time
 * @param $lifetime
 *   Default lifetime
 * @param $push
 *   Pre-cache this file
 * @param $router_item
 *   Array containing page_callback, page_type & page_id.
 */
function boost_put_settings_db($lifetime, $push, $router_item, $scope) {
  switch ($scope) {
    case 0:
      db_query("UPDATE {boost_cache_settings} SET lifetime = %d, push = %d WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '%s'", $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, $router_item['page_id']);
      if (!db_affected_rows()) {
        @db_query("INSERT INTO {boost_cache_settings} (lifetime, push, page_callback, page_type, base_dir, page_id) VALUES (%d, %d, '%s', '%s', '%s', '%s')", $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, $router_item['page_id']);
      }
      break;
    case 1:
      db_query("UPDATE {boost_cache_settings} SET lifetime = %d, push = %d WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '0'", $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH);
      if (!db_affected_rows()) {
        @db_query("INSERT INTO {boost_cache_settings} (lifetime, push, page_callback, page_type, base_dir, page_id) VALUES (%d, %d, '%s', '%s', '%s', '%s')", $lifetime, $push, $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, 0);
      }
      break;
    case 2:
      db_query("UPDATE {boost_cache_settings} SET lifetime = %d, push = %d WHERE page_callback = '%s' AND page_type = '0' AND base_dir = '%s' AND page_id = '0'", $lifetime, $push, $router_item['page_callback'], BOOST_FILE_PATH);
      if (!db_affected_rows()) {
        @db_query("INSERT INTO {boost_cache_settings} (lifetime, push, page_callback, page_type, base_dir, page_id) VALUES (%d, %d, '%s', '%s', '%s', '%s')", $lifetime, $push, $router_item['page_callback'], '0', BOOST_FILE_PATH, 0);
      }
      break;
  }
}

/**
 * Removes info from boost database.
 *
 * @param $csid
 *   Cache Settings primary ID
 */
function boost_remove_settings_db($csid) {
  db_query("DELETE FROM {boost_cache_settings} WHERE csid = %d", $csid);
}

/**
 * Sets per page configuration.
 *
 * @param $lifetime
 *   Default lifetime
 * @param $push
 *   Pre-cache this file
 * @param $scope
 *   At what level does this effect cache expiration
 */
function boost_set_db_page_settings($lifetime, $push, $scope) {
  $router_item = _boost_get_menu_router();
  $filename = boost_file_path($GLOBALS['_boost_path']);
  $info = boost_get_db($filename);
  if (!$info) {
    $info['expire'] = 0;
  }
  elseif ($lifetime == -1) {
    $info['expire'] = $info['expire'] - $info['lifetime'] + BOOST_CACHE_LIFETIME;
  }
  elseif ($info['lifetime'] == -1) {
    $info['expire'] = $info['expire'] - BOOST_CACHE_LIFETIME + $lifetime;
  }
  elseif ($info['lifetime'] != $lifetime) {
    $info['expire'] = $info['expire'] - $info['lifetime'] + $lifetime;
  }

  // Clear old files so they acquire the new settings.
  $data = array();
  switch ($scope) {
    case 0:
      $data[] = array(
        'base_dir' => BOOST_FILE_PATH,
        'page_callback' => $router_item['page_callback'],
        'page_type' => $router_item['page_type'],
        'page_id' => $router_item['page_id'],
      );
      break;
    case 1:
      $data[] = array(
        'base_dir' => BOOST_FILE_PATH,
        'page_callback' => $router_item['page_callback'],
        'page_type' => $router_item['page_type'],
      );
      break;
    case 2:
      $data[] = array(
        'base_dir' => BOOST_FILE_PATH,
        'page_callback' => $router_item['page_callback'],
      );
      break;
  }
  boost_put_settings_db($lifetime, $push, $router_item, $scope);
  $count = 0;
  if ($data) {
    $count += boost_cache_expire_router($data);
  }
  return $count;
}

/**
 * Gets boost info from cache database.
 *
 * @param $filename
 *   Filename to be looked up in the database
 */
function boost_get_db($filename) {
  $hash = md5($filename);
  return db_fetch_array(db_query("SELECT * FROM {boost_cache} WHERE hash = '%s'", $hash));
}

/**
 * Gets boost settings from cache settings database.
 *
 * @param $router_item
 *   Array containing page_callback, page_type & page_id.
 */
function boost_get_settings_db($router_item) {
  $settings = array();

  // Get a more exact match first
  if (BOOST_FLUSH_ALL_MULTISITE) {
    $settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $router_item['page_callback'], $router_item['page_type'], $router_item['page_id'], 0, 1));

    // Get for the content type
    $settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $router_item['page_callback'], $router_item['page_type'], 0, 0, 1));

    // Finally get the content container (node, view, term, ect...)
    $settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND page_id = '%s'", $router_item['page_callback'], 0, 0, 0, 1));
  }
  else {
    $settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '%s'", $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, $router_item['page_id'], 0, 1));

    // Get for the content type
    $settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '%s'", $router_item['page_callback'], $router_item['page_type'], BOOST_FILE_PATH, 0, 0, 1));

    // Finally get the content container (node, view, term, ect...)
    $settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_type = '%s' AND base_dir = '%s' AND page_id = '%s'", $router_item['page_callback'], 0, BOOST_FILE_PATH, 0, 0, 1));
  }
  return $settings;
}

/**
 * Checks various timestamps in the database.
 *
 * @param $set_max
 *  bool Allow one to read and not set the max_timestamp.
 *
 * @return bool
 *  Returns TRUE if the site has changed since the last time this function was called.
 */
function boost_has_site_changed($set_max = FALSE) {

  // Make sure database has been indexed.
  if (boost_drupal_get_installed_schema_version('boost') <= 6120) {
    return FALSE;
  }

  // Index any new tables
  $ret = array();
  _boost_index_exists($ret, 'node_revisions', 'timestamp');
  _boost_index_exists($ret, 'files', 'timestamp');
  _boost_index_exists($ret, 'comments', 'timestamp');
  _boost_index_exists($ret, 'node', 'changed');
  _boost_index_exists($ret, 'node_comment_statistics', 'last_comment_timestamp');
  _boost_index_exists($ret, 'votingapi_vote', 'timestamp');

  // Get timestamps from the database
  $node_revisions = boost_get_time('node_revisions', 'timestamp');

  //$history = boost_get_time('history', 'timestamp');
  $files = boost_get_time('files', 'timestamp');
  $comments = boost_get_time('comments', 'timestamp');
  $voteapi_vote = boost_get_time('votingapi_vote', 'timestamp');
  $node = boost_get_time('node', 'changed');
  $last_comment_timestamp = boost_get_time('node_comment_statistics', 'last_comment_timestamp');
  $max = max($node_revisions, $files, $comments, $node, $last_comment_timestamp, $voteapi_vote);
  if ($max != variable_get('boost_max_timestamp', BOOST_TIME)) {
    if ($set_max) {
      variable_set('boost_max_timestamp', (int) $max);
    }
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Returns the currently installed schema version for a module.
 *
 * @see drupal_get_installed_schema_version()
 *
 * @param $module
 *   A module name.
 * @param $reset
 *   Set to TRUE after modifying the system table.
 * @param $array
 *   Set to TRUE if you want to get information about all modules in the
 *   system.
 * @return
 *   The currently installed schema version.
 */
function boost_drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
  static $versions = array();
  if ($reset) {
    $versions = array();
  }
  if (!$versions) {
    $versions = array();
    $result = db_query("SELECT name, schema_version FROM {system} WHERE type = '%s'", 'module');
    while ($row = db_fetch_object($result)) {
      $versions[$row->name] = $row->schema_version;
    }
  }
  return $array ? $versions : $versions[$module];
}

/**
 * Checks various timestamps in the database.
 *
 * @param $table
 *  Database table name
 * @param $column
 *  Column containing the time stamp
 * @return int
 *  Returns largest time in the table.
 */
function boost_get_time($table, $column) {
  if (db_table_exists($table)) {
    return (int) db_result(db_query_range("SELECT %s FROM {%s} ORDER BY %s DESC", $column, $table, $column, 0, 1));
  }
  else {
    return 0;
  }
}

/**
 * Writes data to filename in an atomic operation thats compatible with older
 * versions of php (php < 5.2.4 file_put_contents() doesn't lock correctly).
 *
 * @param $filename
 *   Name of file to be written
 * @param $buffer
 *   Contents of file
 */
function boost_cache_write($filename, $buffer) {
  $filenames = boost_get_all_filenames($filename);
  foreach ($filenames as $key => $values) {
    if ($key == 'gzip') {
      $data = gzencode($buffer, 9);
    }
    else {
      $data = $buffer;
    }
    foreach ($values as $filename) {
      if (!_boost_mkdir_p(dirname($filename))) {
        if (BOOST_VERBOSE >= 3) {
          watchdog('boost', 'Unable to create directory: %dir<br /> Group ID: %gid<br /> User ID: %uid<br /> Current script owner: %user<br />', array(
            '%dir' => dirname($filename),
            '%gid' => getmygid(),
            '%uid' => getmyuid(),
            '%user' => get_current_user(),
          ), WATCHDOG_WARNING);
        }
      }
      $tempfile = $filename . getmypid();
      $oldfile = $tempfile . 'old';
      if (@file_put_contents($tempfile, $data) === FALSE) {
        if (BOOST_VERBOSE >= 3) {
          watchdog('boost', 'Unable to write temp file: %file<br /> Group ID: %gid<br /> User ID: %uid<br /> Current script owner: %user<br />', array(
            '%file' => $tempfile,
            '%gid' => getmygid(),
            '%uid' => getmyuid(),
            '%user' => get_current_user(),
          ), WATCHDOG_WARNING);
        }
        return FALSE;
      }
      else {
        if (is_numeric(BOOST_PERMISSIONS_FILE)) {
          @chmod($tempfile, octdec(BOOST_PERMISSIONS_FILE));
        }

        // Erase old file
        if (BOOST_OVERWRITE_FILE) {

          // Keep old file around just in case rename fails
          @rename($filename, $oldfile);
        }

        // Put temp file in its final location
        if (@rename($tempfile, $filename) === FALSE) {

          // If rename failed then remove new file and put old file back
          @unlink($tempfile);
          @rename($oldfile, $filename);
          if (BOOST_VERBOSE >= 5) {
            watchdog('boost', 'Unable to rename file: %temp  to  %file<br /> Group ID: %gid<br /> User ID: %uid<br /> Current script owner: %user<br />', array(
              '%temp' => $tempfile,
              '%file' => $filename,
              '%gid' => getmygid(),
              '%uid' => getmyuid(),
              '%user' => get_current_user(),
            ), WATCHDOG_WARNING);
          }
          return FALSE;
        }
        elseif (BOOST_OVERWRITE_FILE) {

          // Rename is sucessful; remove old file
          @unlink($oldfile);
        }
      }
    }
  }
  return TRUE;
}

/**
 * Returns the full directory path to the static file cache directory.
 *
 * @param $host
 *   Host name. Example: example.com
 * @param $absolute
 *   Give path from system root if true. If false give path from web root.
 * @param $root_dir
 *   Cache directory
 * @param $normal_dir
 *   Normal directory
 */
function boost_cache_directory($host = NULL, $absolute = TRUE, $root_dir = NULL, $normal_dir = NULL) {
  global $base_url;
  $temp_base_url = $base_url;
  $root_dir = is_null($root_dir) ? BOOST_ROOT_CACHE_DIR : $root_dir;
  $normal_dir = is_null($normal_dir) ? BOOST_NORMAL_DIR : $normal_dir;
  if ($temp_base_url == "http://") {
    if (!BOOST_MULTISITE_SINGLE_DB) {
      $temp_base_url = $temp_base_url . str_replace($root_dir . '/', '', variable_get('boost_file_path', boost_cache_directory(NULL, FALSE)));
    }
    elseif (BOOST_NORMAL_DIR != '' && db_result(db_query("SELECT count(DISTINCT base_dir) FROM {boost_cache}")) == 1) {
      $temp_base_url = db_result(db_query("SELECT DISTINCT base_dir FROM {boost_cache}"));
      $temp_base_url = $temp_base_url . str_replace($root_dir . '/', '', $base_dir);
      $temp_base_url = $temp_base_url . str_replace($normal_dir . '/', '', $base_dir);
    }
  }
  if (@parse_url($temp_base_url) === FALSE) {

    //Error has been caught here
    if (BOOST_VERBOSE >= 1) {
      watchdog('boost', 'base_url is not set in your settings.php file. Please read Important Notes in boosts README.txt file.', array(), WATCHDOG_NOTICE);
    }
    return FALSE;
  }
  $parts = parse_url($temp_base_url);
  $host = !empty($host) ? $host : $parts['host'];
  $host = strtolower($host);
  $parts['path'] = isset($parts['path']) ? $parts['path'] : '/';
  $subdir = implode('/', array_filter(explode('/', !empty($base_path) ? $base_path : $parts['path'])));
  return implode('/', !$absolute ? array_filter(array(
    $root_dir,
    $normal_dir,
    $host,
    $subdir,
  )) : array_filter(array(
    getcwd(),
    $root_dir,
    $normal_dir,
    $host,
    $subdir,
  )));
}

/**
 * Returns the static file path for a Drupal page.
 *
 * @param $path
 *   path to convert to boost's file naming convention
 * @param $query
 *   add query to path
 * @param $extension
 *   add extension to end of filename
 * @param $file_path
 *   Optional: BOOST_FILE_PATH
 *
 * $path = $GLOBALS['_boost_path'] most of the time
 */
function boost_file_path($path, $query = TRUE, $extension = BOOST_FILE_EXTENSION, $file_path = NULL) {

  //handling of url variables
  if ($GLOBALS['_boost_query'] != BOOST_CHAR) {
    if ($query) {
      $path .= $GLOBALS['_boost_query'];
    }
    else {
      $path .= BOOST_CHAR;
    }
  }
  else {
    $path .= $GLOBALS['_boost_query'];
  }

  // Under no circumstances should the incoming path contain '..' or null
  // bytes; we also limit the maximum directory nesting depth of the path
  if (strpos($path, '..') !== FALSE || strpos($path, "\0") !== FALSE || count(explode('/', $path)) > BOOST_MAX_PATH_DEPTH) {
    return FALSE;
  }
  $file_path = is_null($file_path) ? BOOST_FILE_PATH : $file_path;
  return implode('/', array(
    $file_path,
    $path . (is_null($extension) ? '' : $extension),
  ));
}

/**
 * Returns the time it took to generate this cached page.
 * @param $filename
 *   Name of cached file
 */
function boost_get_generation_time($filename) {
  $boost_db = boost_get_db($filename);
  return $boost_db['timer'] != 0 ? $boost_db['timer'] / 1000.0 : FALSE;
}

/**
 * Returns the age of a cached file, measured in seconds since it was last
 * updated.
 * @param $filename
 *   Name of cached file
 */
function boost_file_get_age($filename) {
  return BOOST_TIME - filemtime($filename);
}
function boost_db_get_age($filename) {
  $boost_db = boost_get_db($filename);
  return $boost_db['expire'] != 0 ? $boost_db['expire'] : FALSE;
}

/**
 * Returns the remaining time-to-live for a cached file, measured in
 * seconds.
 * @param $filename
 *   Name of cached file
 */
function boost_file_get_ttl($filename) {
  return BOOST_CACHE_LIFETIME - boost_file_get_age($filename);
}
function boost_db_get_ttl($filename) {
  return boost_db_get_age($filename) - BOOST_TIME;
}
function boost_db_get_cache_age($filename) {
  $boost_db = boost_get_db($filename);
  $lifetime = BOOST_CACHE_LIFETIME;
  if ($boost_db['lifetime'] != -1) {
    $lifetime = $boost_db['lifetime'];
  }
  $time = BOOST_TIME - ($boost_db['expire'] - $lifetime);
  return $time;
}

/**
 * Determines whether a cached file has expired, i.e. whether its age
 * exceeds the maximum cache lifetime as defined by Drupal's system
 * settings.
 * @param $filename
 *   Name of cached file
 */
function boost_file_is_expired($filename) {
  return boost_file_get_age($filename) > BOOST_CACHE_LIFETIME;
}
function boost_db_is_expired($filename) {
  return boost_db_get_age($filename) < BOOST_TIME;
}

/**
 * Sets a special cookie preventing authenticated users getting served pages
 * from the static page cache.
 *
 * @param $uid
 *   User ID Number
 * @param $expires
 *   Expiration time
 */
function boost_set_cookie($uid, $expires = NULL) {
  if (!$expires) {

    // Let the old way still work, in case user object was passed
    $uid = is_object($uid) ? $uid->uid : $uid;
    $expires = ini_get('session.cookie_lifetime');
    $expires = !empty($expires) && is_numeric($expires) ? BOOST_TIME + (int) $expires : 0;
    setcookie(BOOST_COOKIE, strval($uid), $expires, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure') == '1');
  }
  else {
    setcookie(BOOST_COOKIE, '0', $expires, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure') == '1');
  }
  $GLOBALS['_boost_cache_this'] = FALSE;
}

/**
 * Retrieve a specific URL redirect from the database.
 * http://drupal.org/node/451790
 *
 * @param $where
 *   Array containing 'redirect' => $path
 */
function boost_path_redirect_load($where = array(), $args = array(), $sort = array()) {
  $redirects = array();
  if (is_numeric($where)) {
    $where = array(
      'rid' => $where,
    );
  }
  foreach ($where as $key => $value) {
    if (is_string($key)) {
      $args[] = $value;
      $where[$key] = $key . ' = ' . (is_numeric($value) ? '%d' : "'%s'");
    }
  }
  if ($where && $args) {
    $sql = "SELECT * FROM {path_redirect} WHERE " . implode(' AND ', $where);
    if ($sort) {
      $sql .= ' ORDER BY ' . implode(' ,', $sort);
    }
    $result = db_query($sql, $args);
    while ($redirect = db_fetch_array($result)) {
      $redirects[] = $redirect;
    }
    return $redirects;
  }
}

/**
 * Cache css and or js files.
 *
 * Parse the html file so we get all css/js files. drupal_get_js/css isn't 100%.
 *
 * @param $buffer
 *   String containing documents html.
 */
function boost_cache_css_js_files($buffer) {
  if (BOOST_CACHE_CSS) {

    // Extract external css files from html document
    $css_files = explode('<link ', $buffer);
    array_shift($css_files);
    foreach ($css_files as $key => $value) {

      // Extract css filename
      list($temp) = explode('" />', $value);
      $temp = explode('href="', $temp);
      $temp = explode('//', array_pop($temp));
      $temp = explode(base_path(), array_pop($temp));
      array_shift($temp);
      list($temp) = explode('?', implode('/', $temp));
      list($css_files[$key]) = explode('"', $temp);
    }
    _boost_copy_css_files($css_files);
  }
  if (BOOST_CACHE_JS) {
    $js_files = explode('<script ', $buffer);
    array_shift($js_files);
    foreach ($js_files as $key => $value) {

      // Extract javascript src filename; kill scripts with no src tag
      list($temp) = explode('">', $value);
      $temp = explode('src="', $temp);
      $temp = explode('//', array_pop($temp));
      $temp = explode(base_path(), array_pop($temp));
      array_shift($temp);
      list($temp) = explode('?', implode('/', $temp));
      list($temp) = explode('"', $temp);
      list($js_files[$key]) = explode('type=', $temp);
    }
    _boost_copy_js_files(array_filter($js_files));
  }
  if (BOOST_CACHE_CSS || BOOST_CACHE_JS) {
    foreach (_boost_copy_file_get_domains(BOOST_PERM_FILE_PATH) as $dir) {
      _boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
    }
    foreach (_boost_copy_file_get_domains(BOOST_PERM_GZIP_FILE_PATH) as $dir) {
      _boost_write_file_chmod($dir . '/' . BOOST_ROOT_FILE, $dir);
    }
  }
}

/**
 * An alternative to print_r that unlike the original does not use output buffering with
 * the return parameter set to true. Thus, Fatal errors that would be the result of print_r
 * in return-mode within ob handlers can be avoided.
 * http://php.net/print-r#75872
 *
 * Comes with an extra parameter to be able to generate html code. If you need a
 * human readable DHTML-based print_r alternative, see http://krumo.sourceforge.net/
 *
 * Support for printing of objects as well as the $return parameter functionality
 * added by Fredrik (fredrik dot motin at gmail), to make it work as a drop-in
 * replacement for print_r (Except for that this function does not output
 * parenthesis around element groups... ;) )
 *
 * Based on return_array() By Matthew Ruivo (mruivo at gmail)
 * (http://www.php.net/manual/en/function.print-r.php#73436)
 */
function boost_print_r($var, $return = FALSE, $html = FALSE, $level = 0) {
  $spaces = "";
  $space = $html ? "&nbsp;" : " ";
  $newline = $html ? "<br />" : "\n";
  for ($i = 1; $i <= 6; $i++) {
    $spaces .= $space;
  }
  $tabs = $spaces;
  for ($i = 1; $i <= $level; $i++) {
    $tabs .= $spaces;
  }
  if (is_array($var)) {
    $title = "Array";
  }
  elseif (is_object($var)) {
    $title = get_class($var) . " Object";
  }
  $output = $title . $newline . $newline;

  // Recursive boost_print_r
  if ($var) {
    foreach ($var as $key => $value) {
      if (is_array($value) || is_object($value)) {
        $level++;
        $value = boost_print_r($value, TRUE, $html, $level);
        $level--;
      }
      $output .= $tabs . "[" . $key . "] => " . $value . $newline;
    }
  }
  if ($return) {
    return $output;
  }
  else {
    echo $output;
  }
}

/**
 * Alt to Drupal's url() function.
 * @see http://drupal.org/node/513860
 *
 * http://php.net/parse-url#85963
 */
function boost_glue_url($parsed) {
  if (!is_array($parsed)) {
    return FALSE;
  }
  $uri = isset($parsed['scheme']) ? $parsed['scheme'] . ':' . (strtolower($parsed['scheme']) == 'mailto' ? '' : '//') : '';
  $uri .= isset($parsed['user']) ? $parsed['user'] . (isset($parsed['pass']) ? ': ' . $parsed['pass'] : '') . '@' : '';
  $uri .= isset($parsed['host']) ? $parsed['host'] : '';
  $uri .= isset($parsed['port']) ? ':' . $parsed['port'] : '';
  if (isset($parsed['path'])) {
    $uri .= substr($parsed['path'], 0, 1) == '/' ? $parsed['path'] : (!empty($uri) ? '/' : '') . $parsed['path'];
  }
  $uri .= isset($parsed['query']) ? '?' . $parsed['query'] : '';
  $uri .= isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
  return $uri;
}

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

// Boost API internals

/**
 * Add an index if it doesn't exist
 *
 * @param $ret
 *   Array to which query results will be added.
 * @param $table
 *   The table to be altered.
 * @param $index
 *   The name of the index.
 */
function _boost_index_exists(&$ret, $table, $index) {
  global $db_type;
  if (db_table_exists($table)) {
    if (stristr($db_type, 'pgsql')) {

      // Selecting a db schema table, don't put pg_indexes inside {}
      $result = db_query("SELECT * FROM pg_indexes WHERE tablename = '{%s}'", $table);
      while ($name = db_fetch_array($result)) {
        if (stristr($name['indexname'], $index)) {
          return TRUE;
        }
      }
    }
    else {
      $result = db_query('SHOW INDEX FROM {%s}', $table);
      while ($name = db_fetch_array($result)) {
        if ($name['Column_name'] == $index) {
          return TRUE;
        }
      }
    }

    // Index doesn't exists create it
    db_add_index($ret, $table, $index, array(
      $index,
    ));
  }
  return FALSE;
}

/**
 * Change a file extension in the database.
 *
 * @param $old
 *   string of the old extension: .js
 * @param $new
 *   string of the new extension: .json
 */
function _boost_change_extension($old, $new) {

  // make sure we are in the webroot
  chdir(dirname($_SERVER['SCRIPT_FILENAME']));
  $result = db_query("SELECT base_dir, filename, hash FROM {boost_cache} WHERE extension = '%s'", $old);
  while ($filename = db_fetch_array($result)) {

    // change extension
    $new_filename = _boost_kill_file_extension($filename['filename']) . $new;
    $hash = md5($new_filename);

    // update database
    db_query("UPDATE {boost_cache} SET filename = '%s', extension = '%s', hash = '%s' WHERE hash = '%s'", $new_filename, $new, $hash, $filename['hash']);

    // update files, normal & gzip
    $filenames = boost_get_all_filenames($filename['filename'], $filename['base_dir']);
    $new_filenames = boost_get_all_filenames($new_filename, $filename['base_dir']);
    foreach ($filenames as $key => $values) {
      foreach ($values as $num => $filename) {
        if (file_exists($filename)) {
          rename($filename, $new_filenames[$key][$num]);
        }
      }
    }
  }
}

/**
 * Removes file extension from filename.
 *
 * @param $filename
 *   filename
 */
function _boost_kill_file_extension($filename) {
  $basename = basename($filename);
  $basename = substr($basename, 0, strrpos($basename, '.'));
  if (count(explode('/', $filename)) > 1) {
    return dirname($filename) . '/' . $basename;
  }
  else {
    return $basename;
  }
}

/**
 * Extract css filenames from html and copy them & their children.
 *
 * @param $css_files
 *   array containing all css filenames.
 */
function _boost_copy_css_files($css_files) {

  //copy files
  foreach ($css_files as $css_file) {

    // Strip extension from filename
    $css_file = _boost_kill_file_extension($css_file);
    if (file_exists($css_file . '.css')) {
      $src = $css_file . '.css';
      $dest = BOOST_PERM_FILE_PATH . '/' . $src . BOOST_PERM_CHAR . BOOST_CSS_EXTENSION;
      _boost_copy_file($src, $dest);
    }
    if (file_exists($css_file . '.css.gz')) {
      $src = $css_file . '.css.gz';
      $dest = BOOST_PERM_GZIP_FILE_PATH . '/' . $css_file . '.css' . BOOST_PERM_CHAR . BOOST_CSS_EXTENSION . BOOST_GZIP_EXTENSION;
      _boost_copy_file($src, $dest);
    }
    elseif (BOOST_GZIP && file_exists($css_file . '.css')) {
      $src = $css_file . '.css';
      $dest = BOOST_PERM_GZIP_FILE_PATH . '/' . $src . BOOST_PERM_CHAR . BOOST_CSS_EXTENSION . BOOST_GZIP_EXTENSION;
      _boost_gz_copy_file($src, $dest);
    }
  }
}

/**
 * Extract javascript filenames from html and copy them & their children.
 *
 * @param $js_files
 *   Array containing all javascript filenames.
 */
function _boost_copy_js_files($js_files) {

  //copy files
  foreach ($js_files as $js_file) {

    // Strip extension from filename
    $js_file = _boost_kill_file_extension($js_file);
    if (file_exists($js_file . '.js')) {
      $src = $js_file . '.js';
      $dest = BOOST_PERM_FILE_PATH . '/' . $src . BOOST_PERM_CHAR . BOOST_JS_EXTENSION;
      _boost_copy_file($src, $dest);
    }
    if (file_exists($js_file . '.js.gz')) {
      $src = $js_file . '.js.gz';
      $dest = BOOST_PERM_GZIP_FILE_PATH . '/' . $js_file . '.js' . BOOST_PERM_CHAR . BOOST_JS_EXTENSION . BOOST_GZIP_EXTENSION;
      _boost_copy_file($src, $dest);
    }
    elseif (BOOST_GZIP && file_exists($js_file . '.js')) {
      $src = $js_file . '.js';
      $dest = BOOST_PERM_GZIP_FILE_PATH . '/' . $src . BOOST_PERM_CHAR . BOOST_JS_EXTENSION . BOOST_GZIP_EXTENSION;
      _boost_gz_copy_file($src, $dest);
    }
    if (file_exists($js_file . '.jsmin.js')) {
      $src = $js_file . '.jsmin.js';
      $dest = BOOST_PERM_FILE_PATH . '/' . $src . BOOST_PERM_CHAR . BOOST_JS_EXTENSION;
      _boost_copy_file($src, $dest);
    }
    if (file_exists($js_file . '.jsmin.js.gz')) {
      $src = $js_file . '.jsmin.js.gz';
      $dest = BOOST_PERM_GZIP_FILE_PATH . '/' . $js_file . '.jsmin.js' . BOOST_PERM_CHAR . BOOST_JS_EXTENSION . BOOST_GZIP_EXTENSION;
      _boost_copy_file($src, $dest);
    }
    elseif (BOOST_GZIP && file_exists($js_file . '.jsmin.js')) {
      $src = $js_file . '.jsmin.js';
      $dest = BOOST_PERM_GZIP_FILE_PATH . '/' . $src . BOOST_PERM_CHAR . BOOST_JS_EXTENSION . BOOST_GZIP_EXTENSION;
      _boost_gz_copy_file($src, $dest);
    }
  }
}

/**
 * Copy a file.
 *
 * @param $src
 *   Source File.
 * @param $dest
 *   Destination.
 */
function _boost_copy_file($src, $dest) {
  $destinations = _boost_copy_file_get_domains($dest);
  foreach ($destinations as $destination) {
    if (_boost_mkdir_p(dirname($destination))) {
      @copy($src, $destination);
      if (is_numeric(BOOST_PERMISSIONS_FILE)) {
        @chmod($destination, octdec(BOOST_PERMISSIONS_FILE));
      }
    }
  }
}

/**
 * Copy a file and gzip its contents
 *
 * @param $src
 *   Source File.
 * @param $dest
 *   Destination.
 */
function _boost_gz_copy_file($src, $dest) {
  $destinations = _boost_copy_file_get_domains($dest);
  foreach ($destinations as $destination) {
    _boost_write_file_chmod($destination, gzencode(file_get_contents($src), 9));
  }
}

/**
 * Get the various subdomains from parallel module.
 *
 * @param $dest
 *   Destination.
 */
function _boost_copy_file_get_domains($dest) {
  global $base_url;
  $parts = parse_url($base_url);
  $destinations = array();
  $destinations[] = $dest;
  $domains = array();
  $domains[] = str_replace('//', '', variable_get('parallel_domain_css', ''));
  $domains[] = str_replace('//', '', variable_get('parallel_domain_img', ''));
  $domains[] = str_replace('//', '', variable_get('parallel_domain_js', ''));
  if (module_exists('cdn') && function_exists('cdn_get_domains')) {
    $domains = array_merge($domains, cdn_get_domains());
  }
  $domains = array_unique($domains);
  foreach ($domains as $domain) {
    $destinations[] = str_replace($parts['host'], $domain, $dest);
  }
  return $destinations;
}

/**
 * Get PHP error if it exists.
 */
function _boost_page_have_error() {
  if (function_exists('error_get_last')) {
    $error_ignore = variable_get('boost_halt_on_errors_list', array(
      E_NOTICE,
      E_USER_NOTICE,
    ));
    if (BOOST_HALT_ON_ERRORS && ($error = error_get_last())) {
      if (in_array($error['type'], $error_ignore)) {
        return FALSE;
      }
      else {

        // Do not cache page on all other errors
        $GLOBALS['_boost_cache_this'] = FALSE;
        return $error;
      }
    }
  }
  return FALSE;
}

/**
 * Implementation of hook_boost_menu_router().
 *
 * TODO Better support for arguments.
 *
 * @param $router_item
 *   info about this request
 *
 * @return array
 *   return the $router_item array with 'page_callback', 'page_type', 'page_id' set
 *   return NULL if you didn't get a match
 */
function boost_boost_menu_router($router_item) {

  // Handle nodes
  if ($router_item['args'][0] == 'node' && is_numeric($router_item['args'][1])) {
    $node = boost_node_get_basics($router_item['args'][1]);
    $router_item['page_callback'] = 'node';
    $router_item['page_id'] = $router_item['args'][1];
    if ($node) {
      $router_item['page_type'] = $node->type;
    }
    return $router_item;
  }

  // Handle taxonomy
  if ($router_item['args'][0] == 'taxonomy' && is_numeric($router_item['args'][2])) {
    $term = taxonomy_get_term($router_item['args'][2]);
    $router_item['page_callback'] = 'taxonomy';
    $router_item['page_id'] = $router_item['args'][2];
    if ($term) {
      $vocab = taxonomy_vocabulary_load($term->vid);
      $router_item['page_type'] = $vocab->name;
    }
    return $router_item;
  }

  // Handle users
  if ($router_item['args'][0] == 'user' && is_numeric($router_item['args'][1])) {
    $user = user_load($router_item['args'][1]);
    $router_item['page_callback'] = 'user';
    $router_item['page_id'] = $router_item['args'][1];
    if ($user !== FALSE) {
      $router_item['page_type'] = implode(', ', $user->roles);
    }
    return $router_item;
  }

  // Handle views
  if ($router_item['page_callback'] == 'views_page') {
    $router_item['page_callback'] = 'view';
    $router_item['page_type'] = array_shift($router_item['page_arguments']);
    $router_item['page_id'] = array_shift($router_item['page_arguments']);

    // See http://drupal.org/node/651798 for the reason why this if is needed
    if (is_array($router_item['page_id'])) {
      $router_item['page_id'] = array_shift($router_item['page_id']);
    }
    return $router_item;
  }

  // Handle panels
  if ($router_item['page_callback'] == 'page_manager_page_execute') {
    $subtask_id = array_shift($router_item['page_arguments']);
    $page = page_manager_page_load($subtask_id);
    $task = page_manager_get_task($page->task);
    if ($function = ctools_plugin_get_function($task, 'page callback')) {
      $router_item['page_callback'] = $function;
    }
    $router_item['page_type'] = $page->task;
    $router_item['page_id'] = $page->name;
    return $router_item;
  }
  elseif (is_array($router_item['page_arguments'])) {
    foreach ($router_item['page_arguments'] as $string) {
      if (is_string($string) && empty($router_item['page_type'])) {
        $router_item['page_type'] = $string;
      }
      elseif (is_string($string)) {
        $router_item['page_id'] .= $string;
      }
    }
  }

  // If router doesn't hold the the arguments, get them from the URL.
  if (empty($router_item['page_type'])) {
    $router_item['page_type'] = $router_item['extra_arguments'];
  }
  elseif (empty($router_item['page_id'])) {
    $router_item['page_id'] = $router_item['extra_arguments'];
  }

  // Try populating with the query string
  if (empty($router_item['page_type'])) {
    $router_item['page_type'] = $router_item['query'];
  }
  elseif (empty($router_item['page_id'])) {
    $router_item['page_id'] = $router_item['query'];
  }
  return $router_item;
}

/**
 * Gets menu router contex
 *
 * Allows for any content type to have its own cache expiration among other things.
 *
 * $GLOBALS['_boost_router_item'] gets set in this function
 *
 * @param $path
 *   Internal path
 * @param $get_array
 *   $_GET
 * @param $reset
 *   Reset static variable
 * @return array
 *   Important keys to return
 *    'page_callback'
 *    'page_type'
 *    'page_id'
 *    'page_number'
 */
function _boost_get_menu_router($path = NULL, $get_array = NULL, $reset = FALSE) {

  // Store result in static
  static $menu_router = array();
  if ($reset) {
    $menu_router = array();
  }
  if ($get_array === NULL) {
    $get_array = $_GET;
  }

  // Get query string & page number
  $query = array();
  $page = 0;
  foreach ($get_array as $key => $val) {
    if ($key != 'q' && $key != 'destination' && $key != 'page' && !empty($val)) {
      $query[$key] = $val;
    }
    if ($key == 'page' && is_numeric($val)) {
      $page = $val;
    }
  }
  $query = str_replace('&amp;', '&', urldecode(http_build_query($query)));

  // Static Index
  $index = $path ? $path . $query : 0 . $query;
  if (!empty($menu_router[$index])) {
    return $menu_router[$index];
  }
  $router_item = array();

  // Load the menu item
  $item = menu_get_item($path);
  if (is_array($item)) {
    $router_item = $item;
  }

  // Declare default array keys if not done.
  $router_item['page_callback'] = empty($router_item['page_callback']) ? '' : $router_item['page_callback'];
  $router_item['page_type'] = empty($router_item['page_type']) ? '' : $router_item['page_type'];
  $router_item['page_id'] = empty($router_item['page_id']) ? '' : $router_item['page_id'];

  // Get all args
  $args = arg($path);

  // Prevent array warnings
  $args[0] = empty($args[0]) ? '' : $args[0];
  $args[1] = empty($args[1]) ? '' : $args[1];
  $args[2] = empty($args[2]) ? '' : $args[2];
  $router_item['args'] = $args;

  // Get extra arguments
  $menu_args = arg(NULL, $router_item['path']);
  $diff = array();
  foreach ($args as $key => $value) {
    if (!empty($value) && !empty($menu_args[$key]) && $value !== $menu_args[$key] && $menu_args[$key] !== '%') {
      $diff[] = $value;
    }
  }
  if (!empty($diff)) {
    $router_item['extra_arguments'] = implode('/', $diff);
  }
  else {
    $router_item['extra_arguments'] = '';
  }
  $router_item['query'] = $query;
  $router_item['page_number'] = $page;

  // Make sure page arguments is set and is an array
  // Needed if view throws a 403
  if (!is_array($router_item['page_arguments'])) {
    $router_item['page_arguments'] = array(
      $router_item['page_arguments'],
    );
  }

  // Make sure function for menu callback is loaded.
  // See menu_execute_active_handler()
  if ($router_item['file']) {
    require_once $router_item['file'];
  }

  // Invoke hook_boost_menu_router($router_item)
  $modules = module_implements('boost_menu_router');

  // Make boosts built in hook the last one.
  $pos = array_search('boost', $modules);
  if ($pos !== FALSE) {
    $temp = $modules[$pos];
    unset($modules[$pos]);
    $modules[] = $temp;
  }
  foreach ($modules as $module) {
    if (($result = module_invoke($module, 'boost_menu_router', $router_item)) !== NULL) {
      $menu_router[$index] = $result;
      if ($path === NULL) {
        $GLOBALS['_boost_router_item'] = $menu_router[$index];
      }
      return $menu_router[$index];
    }
  }
}

/**
 * Recursive version of mkdir(), compatible with PHP4.
 *
 * @param $pathname
 *   The top-level directory that will be recursively created.
 * @param $recursive
 *   Operate in a recursive manner.
 * @param $depth
 *   How deep the recursion is.
 */
function _boost_mkdir_p($pathname, $recursive = TRUE, $depth = 0) {
  $depth++;
  $mode = is_numeric(BOOST_PERMISSIONS_DIR) ? octdec(BOOST_PERMISSIONS_DIR) : 0775;
  if (is_dir($pathname)) {
    return TRUE;
  }
  if ($depth > BOOST_MAX_PATH_DEPTH) {
    return FALSE;
  }
  if ($recursive && !_boost_mkdir_p(dirname($pathname), TRUE, $depth)) {
    return FALSE;
  }
  if ($result = @mkdir($pathname, $mode)) {
    @chmod($pathname, $mode);
  }
  return $result;
}

/**
 * Recursive version of rmdir(); use with extreme caution.
 *
 * @param $dirname
 *   the top-level directory that will be recursively removed
 * @param $flush
 *   optional nuke it all if true, otherwise kill only expired files
 * @param $first
 *   id first call to this function
 * @param $nuke
 *   clear it all, everything, ignore multisite restrictions
 */
function _boost_rmdir_rf($dirname, $flush = TRUE, $first = TRUE, $nuke = FALSE, &$timer = NULL) {
  if (empty($dirname)) {
    return FALSE;
  }
  $empty = TRUE;

  // Start with an optimistic mindset
  if (is_null($timer)) {
    $timer = _boost_microtime_float();
  }
  $now = _boost_microtime_float();
  $total_time = $now - $timer;
  if ($total_time > 59) {
    $timer = $now;

    //Ping Database to keep connection alive
    db_query("SHOW PROCESSLIST");
  }
  if ($first || !$first && ($nuke || !file_exists($dirname . '/' . BOOST_ROOT_FILE))) {
    $files = glob($dirname . '/*', GLOB_NOSORT);
    if ($files) {
      foreach ($files as $file) {
        if (is_dir($file)) {
          if (!_boost_rmdir_rf($file, $flush, FALSE, $nuke, $timer)) {
            $empty = FALSE;
          }
        }
        elseif (is_file($file) || is_link($file)) {
          if (!$flush && !boost_db_is_expired($file)) {
            $empty = FALSE;
            continue;
          }
          @unlink($file);
        }
        else {
          $empty = FALSE;

          // it's something else, don't del to be safe.
        }
      }
    }
  }

  //do not delete the root dir
  if (file_exists($dirname . '/' . BOOST_ROOT_FILE)) {
    return $empty;
  }

  // The reason for this elaborate safeguard is that Drupal will log even
  // warnings that should have been suppressed with the @ sign. Otherwise,
  // we'd just rely on the FALSE return value from rmdir().
  return $empty && BOOST_FLUSH_DIR && @rmdir($dirname);
}

/**
 * Internal variable get; don't rely on cache table.
 *
 * @param $name
 *   variable name
 */
function _boost_variable_get($name) {
  $value = unserialize(db_result(db_query("SELECT value FROM {variable} WHERE name = '%s'", $name)));
  return isset($value) ? $value : FALSE;
}

/**
 * Internal variable set; don't rely on cache table.
 *
 * @param $name
 *   variable name
 * @param $value
 *   variable value
 */
function _boost_variable_set($name, $value) {
  db_query("UPDATE {variable} SET value = '%s' WHERE name = '%s'", serialize($value), $name);
}

/**
 * Generate iframe gzip cookie test html file.
 */
function _boost_generate_gzip_test_file() {
  $filename = BOOST_ROOT_CACHE_DIR . '/' . BOOST_PERM_GZ_DIR . '/boost-gzip-cookie-test.html.gz';
  if (!file_exists($filename)) {
    $string = <<<ETO
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html><head><meta name="robots" content="noindex, nofollow" /><title> </title></head><body> </body></html>
ETO;
    _boost_write_file_chmod($filename, gzencode($string, 9));
  }
}

/**
 * Inject code into document.
 *
 * @param $document
 *  string containing html document.
 * @param $html_code
 *  string containing html snippet.
 * @param $point
 *  string of the insertion point
 */
function _boost_inject_code($document, $html_code, $point = '</body>') {
  return str_replace($point, $html_code . $point, $document);
}

/**
 * Create the dir, write the file, chmod the file.
 *
 * @param $filename
 *  Path and filename to be created
 * @param $data
 *  data inside the file
 */
function _boost_write_file_chmod($filename, $data) {
  if (_boost_mkdir_p(dirname($filename))) {
    $bytes = @file_put_contents($filename, $data);
    if ($bytes && is_numeric(BOOST_PERMISSIONS_FILE)) {
      @chmod($filename, octdec(BOOST_PERMISSIONS_FILE));
    }
    return $bytes;
  }
  else {
    return FALSE;
  }
}

/**
 * Attempts to set the PHP maximum execution time.
 * See http://api.drupal.org/api/function/drupal_set_time_limit/7
 *
 * This function is a wrapper around the PHP function set_time_limit().
 * When called, set_time_limit() restarts the timeout counter from zero.
 * In other words, if the timeout is the default 30 seconds, and 25 seconds
 * into script execution a call such as set_time_limit(20) is made, the
 * script will run for a total of 45 seconds before timing out.
 *
 * It also means that it is possible to decrease the total time limit if
 * the sum of the new time limit and the current time spent running the
 * script is inferior to the original time limit. It is inherent to the way
 * set_time_limit() works, it should rather be called with an appropriate
 * value every time you need to allocate a certain amount of time
 * to execute a task than only once at the beginning of the script.
 *
 * Before calling set_time_limit(), we check if this function is available
 * because it could be disabled by the server administrator. We also hide all
 * the errors that could occur when calling set_time_limit(), because it is
 * not possible to reliably ensure that PHP or a security extension will
 * not issue a warning/error if they prevent the use of this function.
 *
 * @param $time_limit
 *   An integer specifying the new time limit, in seconds. A value of 0
 *   indicates unlimited execution time.
 */
function _boost_set_time_limit($time_limit) {
  if (function_exists('set_time_limit')) {
    @set_time_limit($time_limit);
  }
}

/**
 * Insert many records into the database.
 *
 * NOTE Be aware of the MySQL's max_packet_size variable.
 *
 * @param $table
 *   The name of the table.
 * @param $fields array
 *     key: field name
 *   value: db_query placeholders; like %d or '%s'
 * @param $values
 *   array of values you wish to be inserted. If you have 3 fields then the
 *   array should be structured like
 *    array($field_1_value_A, $field_2_value_A, $field_3_value_A,
 *           $field_1_value_B, $field_2_value_B, $field_3_value_B);
 * @param $suppress
 *   bool. TRUE to suppress db_query errors
 * @return
 *   returns db_query() result.
 */
function boost_db_multi_insert($table, $fields, $data, $suppress = FALSE) {
  if (BOOST_VERBOSE >= 1 && count($data) % count($fields) != 0) {
    watchdog('boost_db_multi_insert', 'Number of fields in the fields array do not match the number of fields in the data array', array(), WATCHDOG_ERROR);
    return FALSE;
  }

  // Build the fields part of this query
  $field_names = implode(', ', array_keys($fields));

  // Get the number of rows that will be inserted
  $rows = count($data) / count($fields);

  // Build the values placeholders string.
  $values = '(' . implode(', ', $fields) . ')';
  $placeholders = $values;

  // Add the rest of the placeholders
  for ($i = 1; $i < $rows; $i++) {
    $placeholders .= ', ' . $values;
  }

  // Glue query together
  $query = "INSERT INTO {" . $table . "} ({$field_names}) VALUES {$placeholders}";

  // Run the query
  if ($suppress) {
    return @db_query($query, $data);
  }
  else {
    return db_query($query, $data);
  }
}

/**
 * Delete records from the database where IN(...).
 *
 * NOTE Be aware of the servers max_packet_size variable.
 *
 * @param $table
 *   The name of the table.
 * @param $field
 *  field name to be compared to
 * @param $placeholder
 *   db_query placeholders; like %d or '%s'
 * @param $data
 *   array of values you wish to compare to
 * @return
 *   returns db_query() result.
 */
function boost_db_multi_delete_in($table, $field, $placeholder, $data) {

  // Get the number of rows that will be inserted
  $rows = count($data);

  // Create what goes in the IN ()
  $in = $placeholder;

  // Add the rest of the place holders
  for ($i = 1; $i < $rows; $i++) {
    $in .= ', ' . $placeholder;
  }

  // Build the query
  $query = "DELETE FROM {" . $table . "} WHERE {$field} IN ({$in})";

  // Run the query
  return db_query($query, $data);
}

/**
 * Update records in the database where IN(...).
 *
 * NOTE Be aware of the servers max_packet_size variable.
 *
 * @param $table
 *   The name of the table.
 * @param $set_field
 *  field names to be compared to
 * @param $set_value
 *  field names to be compared to
 * @param $set_placeholders
 *   db_query placeholders; like %d or '%s'
 * @param $where_field
 *  field names to be compared to
 * @param $where_placeholder
 *   db_query placeholders; like %d or '%s'
 * @param $data
 *   array of values you wish to be inserted. If you have 3 fields then the
 *   array should be structured like
 *    array($field_1_value_A, $field_2_value_A, $field_3_value_A,
 *           $field_1_value_B, $field_2_value_B, $field_3_value_B);
 * @return
 *   returns db_query() result.
 */
function boost_db_multi_update_set($table, $set_field, $set_placeholders, $set_value, $where_field, $where_placeholder, $data) {

  // Get the number of rows that will be inserted
  $rows = count($data);

  // Create what goes in the IN ()
  $in = $where_placeholder;

  // Add the rest of the place holders
  for ($i = 1; $i < $rows; $i++) {
    $in .= ', ' . $where_placeholder;
  }

  // Build the query
  $query = "UPDATE {" . $table . "} SET {$set_field} = {$set_placeholders} WHERE {$where_field} IN ({$in})";

  // Add the set value to the top of the array
  array_unshift($data, $set_value);

  // Run the query
  return db_query($query, $data);
}

/**
 * Select records in the database matching where IN(...).
 *
 * NOTE Be aware of the servers max_packet_size variable.
 *
 * @param $table
 *   The name of the table.
 * @param $field
 *  field name to be compared to
 * @param $placeholder
 *   db_query placeholders; like %d or '%s'
 * @param $data
 *   array of values you wish to compare to
 * @return
 *   returns db_query() result.
 */
function boost_db_multi_select_in($table, $field, $placeholder, $data) {

  // Get the number of rows that will be inserted
  $rows = count($data);

  // Create what goes in the IN ()
  $in = $placeholder;

  // Add the rest of the place holders
  for ($i = 1; $i < $rows; $i++) {
    $in .= ', ' . $placeholder;
  }

  // Build the query
  $query = "SELECT * FROM {" . $table . "} WHERE {$field} IN ({$in})";

  // Run the query
  return db_query($query, $data);
}

/**
 * Runs debug_backtrace() and returns string containing that info.
 *
 * @param $args bool
 *  output function arguments?
 *
 * @return $output string
 *  parsed backtrace output
 */
function boost_backtrace($args = TRUE) {
  $output = '';
  foreach (debug_backtrace() as $key => $entry) {
    $output .= "\n\nLevel {$key} \n";
    if (array_key_exists('file', $entry)) {
      $output .= "File: " . $entry['file'];
      if (array_key_exists('line', $entry)) {
        $output .= " (Line: " . $entry['line'] . ")";
      }
      $output .= "\n";
    }
    if (array_key_exists('function', $entry)) {
      $output .= "Function: " . $entry['function'] . "\n";
    }
    if ($args && array_key_exists('args', $entry)) {
      $output .= "Args: " . implode(", ", $entry['args']) . "\n";
    }
    $output .= '<br />';
  }
  return $output;
}

/**
 * Get the length of a string in bytes
 *
 * @param $string
 *   get string length
 */
function boost_strlen($string) {
  if (function_exists('mb_strlen')) {
    return mb_strlen($string, '8bit');
  }
  else {
    return strlen($string);
  }
}

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

// PHP 4.x compatibility

/**
 * microtime(TRUE) emulation
 */
function _boost_microtime_float() {
  list($usec, $sec) = explode(" ", microtime());
  return (double) $usec + (double) $sec;
}
if (!function_exists('file_put_contents')) {
  function file_put_contents($filename, $data) {
    if ($fp = fopen($filename, 'wb')) {
      fwrite($fp, $data);
      fclose($fp);
      return filesize($filename);
    }
    return FALSE;
  }
}

// php.net/http-build-query#90438
if (!function_exists('http_build_query')) {
  function http_build_query($data, $prefix = '', $sep = '', $key = '') {
    $ret = array();
    foreach ((array) $data as $k => $v) {
      if (is_numeric($k) && $prefix != NULL) {
        $k = urlencode($prefix . $k);
      }
      if (!empty($key) || $key === 0) {
        $k = $key . '[' . urlencode($k) . ']';
      }
      if (is_array($v) || is_object($v)) {
        array_push($ret, http_build_query($v, '', $sep, $k));
      }
      else {
        array_push($ret, $k . '=' . urlencode($v));
      }
    }
    if (empty($sep)) {
      $sep = ini_get('arg_separator.output');
    }
    return implode($sep, $ret);
  }
}

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

// Crawler Code

/**
 * The brains of the crawler.
 *
 * @param $expire
 *  Has the site changed, if so get expire column
 */
function boost_crawler_run($expire = -1) {
  global $base_url, $_boost;
  $this_thread = isset($_GET['thread']) && is_numeric($_GET['thread']) ? $_GET['thread'] : NULL;
  $total_threads = isset($_GET['total']) && is_numeric($_GET['total']) ? $_GET['total'] : NULL;
  $expire = $expire == -1 && isset($_GET['expire']) && is_numeric($_GET['expire']) ? $_GET['expire'] : $expire;
  $self = BOOST_CRAWLER_SELF;
  $GLOBALS['_boost_max_execution_time'] = ini_get('max_execution_time');
  $GLOBALS['_boost_output_buffering'] = ini_get('output_buffering');
  if ($_GET['q'] == 'boost-crawler') {

    // if not called via cron, require key to be present in url
    if ($_GET['key'] != variable_get('boost_crawler_key', FALSE)) {
      drupal_access_denied();
      exit;
    }

    // Test for access on status page
    if ($_GET['test']) {
      echo '<h1>OK</h1>';
      exit;
    }

    // Stop button code
    if (_boost_variable_get('boost_crawler_stopped')) {

      // Wait 0 to 0.1 seconds before grabbing number of threads.
      usleep(mt_rand(0, 100000));
      db_lock_table('variable');
      $threads = _boost_variable_get('boost_crawler_number_of_threads');
      _boost_variable_set('boost_crawler_number_of_threads', (int) $threads - 1);

      // Clock out
      _boost_variable_set('boost_crawler_thread_num_' . $this_thread, 0);
      db_unlock_tables();
      if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_stop'])) {
        watchdog('boost', 'Crawler - Thread %num stopped.', array(
          '%num' => $this_thread,
        ));
      }
      ini_set('max_execution_time', $GLOBALS['_boost_max_execution_time']);
      ini_set('output_buffering', $GLOBALS['_boost_output_buffering']);
      exit;
    }

    // Kill this thread if it doesn't have a thread number assigned to it.
    if (!isset($this_thread)) {
      if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_rogue'])) {
        watchdog('boost', 'Crawler - Rogue thread killed.');
      }
      exit;
    }

    // Try to prevent crawler from stalling.
    ini_set('max_execution_time', 600);

    // Return html so connection closes
    boost_async_opp('async');

    // Turn off output buffer.
    ini_set('output_buffering', 'off');

    // Fetch the cron semaphore
    $semaphore = variable_get('cron_semaphore', FALSE);

    // Wait 15 seconds if cron still running and try again (let cron finish); if longer then 5 minutes stop stalling and start crawling.
    if ($semaphore == TRUE && BOOST_TIME - $semaphore < 300) {
      if (_boost_variable_get('boost_crawler_sleeping')) {

        // Kill this thread; multiple crawlers sleeping.
        ini_set('max_execution_time', $GLOBALS['_boost_max_execution_time']);
        ini_set('output_buffering', $GLOBALS['_boost_output_buffering']);
        exit;
      }
      _boost_variable_set('boost_crawler_sleeping', TRUE);
      if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_sleep'])) {
        watchdog('boost', 'Crawler Sleep for 15 seconds');
      }
      sleep(15);
      _boost_variable_set('boost_crawler_sleeping', FALSE);
      boost_async_call_crawler($self, 1, NULL, $expire);
      exit;
    }

    // Crawler was forced to stop last run, wait extra time before starting up again.
    if (variable_get('boost_crawler_stopped', FALSE) && !isset($this_thread) && !isset($total_threads)) {
      if (_boost_variable_get('boost_crawler_sleeping')) {
        ini_set('max_execution_time', $GLOBALS['_boost_max_execution_time']);
        ini_set('output_buffering', $GLOBALS['_boost_output_buffering']);
        exit;
      }
      _boost_variable_set('boost_crawler_sleeping', TRUE);
      if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_shutdown'])) {
        watchdog('boost', 'Crawler sleeping for @x seconds, do to forced shutdown.', array(
          '@x' => 2 * BOOST_CRAWLER_THREADS * BOOST_CRAWLER_BATCH_SIZE,
        ));
      }
      $i = BOOST_CRAWLER_BATCH_SIZE;
      while ($i > 0) {
        _boost_set_time_limit(0);
        sleep(2 * BOOST_CRAWLER_THREADS);
        $i--;
      }
      variable_set('boost_crawler_stopped', FALSE);
      _boost_variable_set('boost_crawler_sleeping', FALSE);
      boost_async_call_crawler($self, 1, NULL, $expire);
      exit;
    }

    // Add URL's to crawler table, call self and exit
    if (!boost_crawler_seed_tables($expire)) {
      boost_async_call_crawler($self, $this_thread, _boost_variable_get('boost_crawler_number_of_threads'), $expire);
      exit;
    }

    // Calc Threads
    $total = boost_crawler_total_count() - BOOST_CRAWLER_BATCH_SIZE;
    $threads = _boost_variable_get('boost_crawler_number_of_threads');
    $threads = $threads > 0 ? $threads : BOOST_CRAWLER_THREADS;
    if ($total / BOOST_CRAWLER_BATCH_SIZE < BOOST_CRAWLER_THREADS) {
      $threads = floor($total / BOOST_CRAWLER_BATCH_SIZE);
    }

    // Sanity Check
    if (abs($threads) > BOOST_CRAWLER_THREADS) {

      // Kill this thread
      if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_kill'])) {
        watchdog('boost', 'Crawler - Thread %num of %total Killed.', array(
          '%num' => $this_thread,
          '%total' => $total_threads,
        ));
      }
      ini_set('max_execution_time', $GLOBALS['_boost_max_execution_time']);
      ini_set('output_buffering', $GLOBALS['_boost_output_buffering']);
      exit;
    }

    // Start the clock on first run
    if (!_boost_variable_get('boost_crawler_start_time')) {
      _boost_variable_set('boost_crawler_start_time', BOOST_TIME);
      _boost_variable_set('boost_crawler_number_of_threads', (int) $threads);

      // Clock in
      _boost_variable_set('boost_crawler_thread_num_' . $this_thread, BOOST_TIME);
      if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_startup'])) {
        watchdog('boost', 'Crawler - Thread @num of @total started', array(
          '@num' => 1,
          '@total' => $threads,
        ));
      }
    }

    // Spin up threads on demand
    while ($threads > 0 && $this_thread == 1) {
      db_lock_table('variable');
      $thread_time = _boost_variable_get('boost_crawler_thread_num_' . $threads);
      if (!$thread_time || $thread_time + BOOST_MAX_THREAD_TIME < BOOST_TIME) {
        _boost_variable_set('boost_crawler_thread_num_' . $threads, BOOST_TIME);
        db_unlock_tables();
        boost_async_call_crawler($self, $threads, _boost_variable_get('boost_crawler_number_of_threads'), $expire);
        if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_startup'])) {
          watchdog('boost', 'Crawler - Thread @num of @total started', array(
            '@num' => $threads,
            '@total' => _boost_variable_get('boost_crawler_number_of_threads'),
          ));
        }
        _boost_set_time_limit(0);
      }
      db_unlock_tables();
      $threads--;
    }

    // Make sure this thread is supposed to be running.
    $thread = _boost_variable_get('boost_crawler_number_of_threads');
    if ($thread >= 1 && $this_thread > $thread) {

      // Clock out
      if (isset($this_thread)) {
        _boost_variable_set('boost_crawler_thread_num_' . $this_thread, 0);
        if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_kill'])) {
          watchdog('boost', 'Crawler - Thread %num of %total Killed.', array(
            '%num' => $this_thread,
            '%total' => $total_threads,
          ));
        }
      }

      //       elseif (BOOST_VERBOSE >= 5) {
      //         watchdog('boost', 'Crawler - Extra Thread Killed.');
      //       }
      if (!boost_crawler_threads_alive() && _boost_variable_get('boost_crawler_number_of_tries') < 3 && boost_crawler_verify($expire)) {
        variable_set('boost_crawler_number_of_tries', (int) _boost_variable_get('boost_crawler_number_of_tries') + 1);
        _boost_variable_set('boost_crawler_number_of_threads', 1);
        if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_restart'])) {
          watchdog('boost', 'Crawler - Restarting with 1 thread, to try & get the stubborn urls cached.');
        }
        boost_async_call_crawler($self, 1, 1, $expire);
        exit;
      }
      ini_set('max_execution_time', $GLOBALS['_boost_max_execution_time']);
      ini_set('output_buffering', $GLOBALS['_boost_output_buffering']);
      exit;
    }

    // Clock in
    _boost_variable_set('boost_crawler_thread_num_' . $this_thread, BOOST_TIME);

    // Wait 0 to 0.1 seconds before grabbing DB position counter.
    usleep(mt_rand(0, 100000));
    db_lock_table('variable');
    $from = _boost_variable_get('boost_crawler_position');
    _boost_variable_set('boost_crawler_position', $from + BOOST_CRAWLER_BATCH_SIZE);
    db_unlock_tables();
    $results = db_query_range("SELECT DISTINCT hash, url FROM {boost_crawler}", $from, BOOST_CRAWLER_BATCH_SIZE);
    $url = db_fetch_array($results);
    if (!$url) {

      // We Are Done
      // Wait 0 to 0.1 seconds before grabbing number of threads.
      usleep(mt_rand(0, 100000));
      db_lock_table('variable');
      $threads = _boost_variable_get('boost_crawler_number_of_threads');
      _boost_variable_set('boost_crawler_number_of_threads', (int) $threads - 1);

      // Clock out
      _boost_variable_set('boost_crawler_thread_num_' . $this_thread, 0);
      db_unlock_tables();
      if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_done'])) {
        watchdog('boost', 'Crawler - Thread %num of %total Done.', array(
          '%num' => $this_thread,
          '%total' => $total_threads,
        ));
      }

      // Re init crawler if it missed some, try 3 times
      if (!boost_crawler_threads_alive() && _boost_variable_get('boost_crawler_number_of_tries') < 3 && boost_crawler_verify($expire)) {
        variable_set('boost_crawler_number_of_tries', (int) _boost_variable_get('boost_crawler_number_of_tries') + 1);
        _boost_variable_set('boost_crawler_number_of_threads', 1);
        if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_restart'])) {
          watchdog('boost', 'Crawler - Restarting with 1 thread, to try & get the stubborn urls cached.');
        }
        boost_async_call_crawler($self, 1, 1, $expire);
        exit;
      }
      return TRUE;
    }
    else {

      // Delete page right before crawling it
      if (!BOOST_OVERWRITE_FILE && BOOST_LOOPBACK_BYPASS) {
        $kill = db_result(db_query("SELECT filename FROM {boost_cache} WHERE hash_url = '%s'", $url['hash']));
        if ($kill) {
          boost_cache_kill(array(
            array(
              'filename' => $kill,
              'hash' => $url['hash'],
            ),
          ), TRUE);
        }
      }
      drupal_http_request($url['url']);
      if (BOOST_CRAWLER_THROTTLE) {
        usleep(BOOST_CRAWLER_THROTTLE);
      }
      _boost_set_time_limit(0);
    }
    while ($url = db_fetch_array($results)) {

      // Delete page right before crawling it
      if (!BOOST_OVERWRITE_FILE && BOOST_LOOPBACK_BYPASS) {
        $kill = db_result(db_query("SELECT filename FROM {boost_cache} WHERE hash_url = '%s'", $url['hash']));
        if ($kill) {
          boost_cache_kill(array(
            array(
              'filename' => $kill,
              'hash' => $url['hash'],
            ),
          ), TRUE);
        }
      }
      drupal_http_request($url['url']);
      if (BOOST_CRAWLER_THROTTLE) {
        usleep(BOOST_CRAWLER_THROTTLE);
      }
      _boost_set_time_limit(0);
    }

    // Crawler for this round done, call self and exit
    boost_async_call_crawler($self, $this_thread, _boost_variable_get('boost_crawler_number_of_threads'), $expire);
    exit;
  }
  elseif (boost_crawler_threads_alive() || _boost_variable_get('boost_crawler_sleeping')) {
    if (BOOST_VERBOSE >= 3) {
      watchdog('boost', 'Crawler already running');
    }
    drupal_set_message(t('Boost: Crawler is already running. Attempt to start crawler failed.'), 'warning');
  }
  elseif (!BOOST_CRAWL_ON_CRON) {

    // Crawler Not Enabled
    return FALSE;
  }
  elseif (variable_get('cron_semaphore', FALSE) == TRUE) {

    // This function called from cron; reset & call self.
    if (BOOST_VERBOSE >= 5 && isset($_boost['verbose_option_selected']['boost_crawler_run_start'])) {
      watchdog('boost', 'Crawler Start %self', array(
        '%self' => $self,
      ));
    }
    db_query('TRUNCATE {boost_crawler}');
    variable_set('boost_crawler_position', 0);
    variable_set('boost_crawler_loaded_count' . BOOST_FILE_EXTENSION, 0);
    variable_set('boost_crawler_loaded_count' . BOOST_XML_EXTENSION, 0);
    variable_set('boost_crawler_loaded_count' . BOOST_JSON_EXTENSION, 0);
    variable_set('boost_crawler_loaded_count_alias', 0);
    variable_set('boost_crawl_prune_table', FALSE);
    variable_set('boost_crawler_number_of_tries', 0);
    variable_set('boost_crawler_number_of_threads', 0);
    variable_set('boost_crawler_sleeping', FALSE);
    variable_set('boost_crawler_average_generation', max(1, db_result(db_query("SELECT AVG(timer_average) FROM {boost_cache}"))));
    variable_set('boost_crawler_start_time', FALSE);
    variable_set('boost_crawler_stopped', FALSE);
    $threads = BOOST_MAX_THREADS;
    while ($threads > 0) {
      variable_set('boost_crawler_thread_num_' . $threads, 0);
      $threads--;
    }
    boost_async_call_crawler($self, 1, NULL, $expire);
    return TRUE;
  }
}

/**
 * Output text & set php in async mode.
 *
 * @param $output
 *  string - Text to output to open connection.
 * @param $wait
 *  bool - Wait 1 second?
 * @param $content_type
 *  string - Content type header.
 */
function boost_async_opp($output, $wait = TRUE, $content_type = "text/html; charset=utf-8", $length = 0) {
  if (headers_sent()) {
    return FALSE;
  }

  // Calculate Content Length
  if ($length == 0) {
    $output .= "\n";
    $length = boost_strlen($output) - 1;
  }

  // Prime php for background operations
  $loop = 0;
  while (ob_get_level() && $loop < 25) {
    ob_end_clean();
    $loop++;
  }
  header("Connection: close");
  ignore_user_abort();

  // Output headers & data
  ob_start();
  header("Content-type: " . $content_type);
  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
  header("Cache-Control: no-cache");
  header("Cache-Control: must-revalidate");
  header("Content-Length: " . $length);
  header("Connection: close");
  print $output;
  ob_end_flush();
  flush();

  // wait for 1 second
  if ($wait) {
    sleep(1);
  }

  // text returned and connection closed.
  // Do background processing. Time taken after should not effect page load times.
  return TRUE;
}

/**
 * Call a URL with a timeout of 3 seconds.
 *
 * @param $self
 *  URL to restart the loop.
 * @param $expire
 *  Has the site changed, if so get expire column
 */
function boost_async_call_crawler($self, $this_thread = NULL, $total_threads = NULL, $expire) {
  $self .= isset($this_thread) ? '&thread=' . $this_thread : '';
  $self .= isset($total_threads) ? '&total=' . $total_threads : '';
  $self .= isset($expire) ? '&expire=' . (int) $expire : '';
  $GLOBALS['_boost_default_socket_timeout'] = ini_get('default_socket_timeout');
  ini_set('default_socket_timeout', 3);
  boost_drupal_http_request($self, 3, 'POST');
  ini_set('default_socket_timeout', $GLOBALS['_boost_default_socket_timeout']);
  ini_set('max_execution_time', $GLOBALS['_boost_max_execution_time']);
  ini_set('output_buffering', $GLOBALS['_boost_output_buffering']);
}

/**
 * Add URL's to the boost_crawler table.
 *
 * @param $push_setting
 *  Default crawler setting for the content type
 * @param $extension
 *  File extension, controls the content type DB lookup
 * @param $expire
 *  Has the site changed, if so get expire column
 */
function boost_crawler_add_to_table($push_setting, $extension, $expire) {

  // Insert batch of URL's into boost_crawler table
  $count = BOOST_CRAWL_DB_IMPORT_SIZE;
  $total = boost_crawler_count($push_setting, $extension, $expire);
  $loaded = variable_get('boost_crawler_loaded_count' . $extension, 0);
  if ($total > $loaded) {
    if ($push_setting) {
      if ($expire && BOOST_LOOPBACK_BYPASS) {
        @db_query_range("INSERT INTO {boost_crawler} (url, hash) SELECT url, hash_url FROM {boost_cache} WHERE push != 0 AND extension = '%s' AND expire BETWEEN 0 AND %d", $extension, BOOST_TIME, $loaded, $count);
      }
      else {
        @db_query_range("INSERT INTO {boost_crawler} (url, hash) SELECT url, hash_url FROM {boost_cache} WHERE push != 0 AND extension = '%s' AND expire = 0", $extension, $loaded, $count);
      }
    }
    else {
      if ($expire && BOOST_LOOPBACK_BYPASS) {
        @db_query_range("INSERT INTO {boost_crawler} (url, hash) SELECT url, hash_url FROM {boost_cache} WHERE push = 1 AND extension = '%s' AND expire BETWEEN 0 AND %d", $extension, BOOST_TIME, $loaded, $count);
      }
      else {
        @db_query_range("INSERT INTO {boost_crawler} (url, hash) SELECT url, hash_url FROM {boost_cache} WHERE push = 1 AND extension = '%s' AND expire = 0", $extension, $loaded, $count);
      }
    }
    variable_set('boost_crawler_loaded_count' . $extension, $loaded + $count);
    return FALSE;
  }
  else {
    return TRUE;
  }
}

/**
 * Count the number of URL's in the boost_cache table.
 *
 * @param $push_setting
 *  Default crawler setting for the content type
 * @param $extension
 *  File extension, controls the content type DB lookup
 * @param $expire
 *  Has the site changed, if so get expire column
 */
function boost_crawler_count($push_setting, $extension, $expire) {
  if ($push_setting) {
    if ($expire && BOOST_LOOPBACK_BYPASS) {
      return db_result(db_query("SELECT COUNT(*) FROM {boost_cache} WHERE push <> 0 AND extension = '%s' AND expire BETWEEN 0 AND %d", $extension, BOOST_TIME));
    }
    else {
      return db_result(db_query("SELECT COUNT(*) FROM {boost_cache} WHERE push <> 0 AND extension = '%s' AND expire = 0", $extension));
    }
  }
  else {
    if ($expire && BOOST_LOOPBACK_BYPASS) {
      return db_result(db_query("SELECT COUNT(*) FROM {boost_cache} WHERE push = 1 AND extension = '%s' AND expire BETWEEN 0 AND %d", $extension, BOOST_TIME));
    }
    else {
      return db_result(db_query("SELECT COUNT(*) FROM {boost_cache} WHERE push = 1 AND extension = '%s' AND expire = 0", $extension));
    }
  }
}

/**
 * Logic to get boost_crawler table ready.
 *
 * @param $expire
 *  Has the site changed, if so get expire column
 */
function boost_crawler_seed_tables($expire) {
  if (boost_crawler_add_to_table(BOOST_PUSH_HTML, BOOST_FILE_EXTENSION, $expire) && boost_crawler_add_to_table(BOOST_PUSH_XML, BOOST_XML_EXTENSION, $expire) && boost_crawler_add_to_table(BOOST_PUSH_JSON, BOOST_JSON_EXTENSION, $expire) && boost_crawler_add_alias_to_table() && boost_crawler_prune_table($expire)) {

    // All URL's added to boost_crawler table; start hitting URL's
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Get URLs from url alias table
 */
function boost_crawler_add_alias_to_table() {

  // Insert batch of html URL's into boost_crawler table
  global $base_url, $language, $db_type;
  if (!variable_get('boost_crawl_url_alias', FALSE)) {
    return TRUE;
  }
  $count = BOOST_CRAWL_DB_IMPORT_SIZE;

  // Get maximum packet size for mysql
  if (stristr($db_type, 'pgsql')) {

    // Set Max Packet size to 16MB if using postgreSQL.
    $max_packet = 16777216;
  }
  else {

    // Get maximum packet size for mysql
    $result = @db_query("SHOW VARIABLES WHERE Variable_name = 'max_allowed_packet'");
    if ($result) {
      $result = db_fetch_array($result);
      $max_packet = (int) $result['Value'];
    }
    else {

      // default to 1/2 MB
      $max_packet = 524288;
    }

    // Get bulk insert buffer size
    $result = @db_query("SHOW VARIABLES WHERE Variable_name = 'bulk_insert_buffer_size'");
    if ($result) {
      $result = db_fetch_array($result);
      $insert_buffer_size = (int) $insert_buffer_size['Value'];
    }
    else {

      // default to 1/2 MB
      $insert_buffer_size = 524288;
    }

    // Set max
    $max_packet = $max_packet > $insert_buffer_size ? $insert_buffer_size : $max_packet;

    // Make sure its over 128K
    $max_packet = $max_packet > 131072 ? $max_packet : 131072;
  }
  $max_chunk = $max_packet / 512;
  $chunks = 0;
  $loop_counter = 0;

  // Don't crawl user pages if anonymous can't access them
  $hit_users = db_result(db_query("SELECT * FROM {permission} AS p INNER JOIN {role} AS r USING (rid) WHERE (r.name = 'anonymous user' OR r.rid = 1) AND p.perm LIKE '%%access user profiles%%'"));
  if (!$hit_users) {
    $extra = "AND ua.src NOT LIKE 'user/%%'";
  }
  else {
    $extra = '';
  }
  $total = db_result(db_query("SELECT COUNT(*) FROM {url_alias} AS ua LEFT JOIN {node} AS n ON n.nid = CAST(substring(ua.src, 6) AS UNSIGNED) WHERE (n.status = 1 OR n.status IS NULL) {$extra}"));
  $loaded = variable_get('boost_crawler_loaded_count_alias', 0);
  if ($total > $loaded) {
    $list = db_query_range("SELECT ua.dst, ua.language FROM {url_alias} AS ua LEFT JOIN {node} AS n ON n.nid = CAST(substring(ua.src, 6) AS UNSIGNED) WHERE (n.status = 1 OR n.status IS NULL) {$extra}", $loaded, $count);
    $data = array();
    while ($row = db_fetch_array($list)) {
      if (empty($row['language']) || $language->language != $row['language'] && empty($language->prefix)) {
        $url = $base_url . '/' . $row['dst'];
      }
      else {
        $url = $base_url . '/' . $row['language'] . '/' . $row['dst'];
      }
      $md5 = md5($url);
      $data[$chunks][] = $url;
      $data[$chunks][] = $md5;
      $loop_counter++;
      if ($loop_counter > $max_chunk) {
        $chunks++;
        $loop_counter = 0;
      }
    }
    foreach ($data as $values) {
      boost_db_multi_insert('boost_crawler', array(
        'url' => "'%s'",
        'hash' => "'%s'",
      ), $values, FALSE);
    }
    variable_set('boost_crawler_loaded_count_alias', $loaded + $count);
    return FALSE;
  }
  else {
    return TRUE;
  }
}

/**
 * Remove any urls in the crawler table if they are not expired or flushed.
 *
 * @param $expire
 *  Has the site changed, if so get expire column
 */
function boost_crawler_prune_table($expire) {
  if (variable_get('boost_crawl_prune_table', FALSE)) {
    return TRUE;
  }
  if ($expire && BOOST_LOOPBACK_BYPASS) {
    db_query("DELETE FROM {boost_crawler} WHERE hash IN (SELECT ca.hash_url FROM {boost_cache} ca WHERE ca.expire BETWEEN 1 AND 434966399 OR ca.expire > %d)", BOOST_TIME);
  }
  else {
    db_query("DELETE FROM {boost_crawler} WHERE hash IN (SELECT ca.hash_url FROM {boost_cache} ca WHERE ca.expire BETWEEN 1 AND 434966399 OR ca.expire > 434966400)");
  }
  variable_set('boost_crawl_prune_table', TRUE);
  return FALSE;
}

/**
 * Get count of boost_crawler table.
 */
function boost_crawler_total_count() {
  return db_result(db_query("SELECT COUNT(*) FROM {boost_crawler} GROUP BY hash"));
}

/**
 * Reload any url's that did not get cached.
 *
 * @param $expire
 *  Has the site changed, if so get expire column
 */
function boost_crawler_verify($expire) {
  if ($expire && BOOST_LOOPBACK_BYPASS) {
    $list = db_query("SELECT DISTINCT bcrawler.url, bcrawler.hash FROM {boost_cache} bcache INNER JOIN {boost_crawler} bcrawler ON (bcache.hash_url = bcrawler.hash) WHERE bcache.expire BETWEEN 0 AND %d", BOOST_TIME);
  }
  else {
    $list = db_query("SELECT DISTINCT bcrawler.url, bcrawler.hash FROM {boost_cache} bcache INNER JOIN {boost_crawler} bcrawler ON (bcache.hash_url = bcrawler.hash) WHERE bcache.expire = 0");
  }
  db_query('TRUNCATE {boost_crawler}');
  variable_set('boost_crawler_position', 0);
  $recrawl = FALSE;
  while ($url = db_fetch_array($list)) {
    db_query("INSERT INTO {boost_crawler} (url, hash) VALUES ('%s', '%s')", $url['url'], $url['hash']);
    $recrawl = TRUE;
  }
  return $recrawl;
}

/**
 * Check for any dead threads.
 */
function boost_crawler_threads_alive() {

  // Load all thread times
  $result = db_query("SELECT * FROM {variable} WHERE name LIKE 'boost_crawler_thread_num_%'");
  while ($variable = db_fetch_object($result)) {
    $time = unserialize($variable->value);
    if ($time != 0 && $time + BOOST_MAX_THREAD_TIME > BOOST_TIME) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Return average page generation time.
 */
function boost_average_time() {
  return variable_get('boost_crawler_average_generation', 5000) / 1000 + 0.1;
}

/**
 * Perform an HTTP request.
 *
 * @see drupal_http_request()
 *
 * @param $url
 *   A string containing a fully qualified URI.
 * @param $timeout
 *   How many seconds before giving up on request.
 * @param $method
 *   HTTP request method.
 */
function boost_drupal_http_request($url, $timeout = 3, $method = 'GET') {
  global $db_prefix;
  $headers = array();
  $data = NULL;
  $result = new stdClass();

  // Parse the URL and make sure we can handle the schema.
  $uri = parse_url($url);
  if ($uri == FALSE) {
    $result->error = 'unable to parse URL';
    return $result;
  }
  if (!isset($uri['scheme'])) {
    $result->error = 'missing schema';
    return $result;
  }
  switch ($uri['scheme']) {
    case 'http':
      $port = isset($uri['port']) ? $uri['port'] : 80;
      $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $timeout);
      break;
    case 'https':

      // Note: Only works for PHP 4.3 compiled with OpenSSL.
      $port = isset($uri['port']) ? $uri['port'] : 443;
      $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
      $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $timeout);
      break;
    default:
      $result->error = 'invalid schema ' . $uri['scheme'];
      return $result;
  }

  // Make sure the socket opened properly.
  if (!$fp) {

    // When a network error occurs, we use a negative number so it does not
    // clash with the HTTP status codes.
    $result->code = -$errno;
    $result->error = trim($errstr);

    // Mark that this request failed. This will trigger a check of the web
    // server's ability to make outgoing HTTP requests the next time that
    // requirements checking is performed.
    // @see system_requirements()
    variable_set('drupal_http_request_fails', TRUE);
    return $result;
  }

  // Construct the path to act on.
  $path = isset($uri['path']) ? $uri['path'] : '/';
  if (isset($uri['query'])) {
    $path .= '?' . $uri['query'];
  }

  // Create HTTP request.
  $defaults = array(
    // RFC 2616: "non-standard ports MUST, default ports MAY be included".
    // We don't add the port to prevent from breaking rewrite rules checking the
    // host that do not take into account the port number.
    'Host' => "Host: {$host}",
    'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
  );

  // Only add Content-Length if we actually have any content or if it is a POST
  // or PUT request. Some non-standard servers get confused by Content-Length in
  // at least HEAD/GET requests, and Squid always requires Content-Length in
  // POST/PUT requests.
  $content_length = strlen($data);
  if ($content_length > 0 || $method == 'POST' || $method == 'PUT') {
    $defaults['Content-Length'] = 'Content-Length: ' . $content_length;
  }

  // If the server url has a user then attempt to use basic authentication
  if (isset($uri['user'])) {
    $defaults['Authorization'] = 'Authorization: Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
  }

  // If the database prefix is being used by SimpleTest to run the tests in a copied
  // database then set the user-agent header to the database prefix so that any
  // calls to other Drupal pages will run the SimpleTest prefixed database. The
  // user-agent is used to ensure that multiple testing sessions running at the
  // same time won't interfere with each other as they would if the database
  // prefix were stored statically in a file or database variable.
  if (is_string($db_prefix) && preg_match("/^simpletest\\d+\$/", $db_prefix, $matches)) {
    $defaults['User-Agent'] = 'User-Agent: ' . $matches[0];
  }
  foreach ($headers as $header => $value) {
    $defaults[$header] = $header . ': ' . $value;
  }
  $request = $method . ' ' . $path . " HTTP/1.0\r\n";
  $request .= implode("\r\n", $defaults);
  $request .= "\r\n\r\n";
  $request .= $data;
  $result->request = $request;
  fwrite($fp, $request);

  // Fetch response.
  $response = '';
  while (!feof($fp) && ($chunk = fread($fp, 1024))) {
    $response .= $chunk;
  }
  fclose($fp);
}

Functions

Namesort descending Description
boost_async_call_crawler Call a URL with a timeout of 3 seconds.
boost_async_opp Output text & set php in async mode.
boost_average_time Return average page generation time.
boost_backtrace Runs debug_backtrace() and returns string containing that info.
boost_block Implementation of hook_block().
boost_block_db_rm_settings_form_submit Removes page specific settings in the boost cache database.
boost_block_db_settings_form
boost_block_db_settings_form_submit Sets page specific settings in the boost cache database.
boost_block_flush_form
boost_block_form_flush_submit
boost_boost_menu_router Implementation of hook_boost_menu_router().
boost_cache_clear_all Deletes all files currently in the cache.
boost_cache_clear_all_db Resets all entries in database.
boost_cache_css_js_files Cache css and or js files.
boost_cache_delete Deletes files in the cache.
boost_cache_directory Returns the full directory path to the static file cache directory.
boost_cache_expire_all Flushes all expired pages from cache.
boost_cache_expire_all_db Flushes all expired pages via database lookup.
boost_cache_expire_all_filesystem Deletes all expired static files currently in the cache via filesystem.
boost_cache_expire_by_db Expires the static file cache for the given paths via database.
boost_cache_expire_by_filename Expires the static file cache for paths matching a wildcard via filesystem.
boost_cache_expire_derivative Finds all possible paths/redirects/aliases given the root path.
boost_cache_expire_router Expires the static file cache for the given router items.
boost_cache_flush_by_filename Expires the static file cache for files given.
boost_cache_get Returns the cached contents of the specified page, if available.
boost_cache_get_node_relationships Creates a parent child relationship for pages like views.
boost_cache_kill Deletes cached page from file system.
boost_cache_kill_url Deletes cached page from file system & database.
boost_cache_prune_node_relationship Given hash of url delete any old relationships.
boost_cache_set Replaces/Sets the cached contents of the specified page, if stale.
boost_cache_set_node_relationships Creates a parent child relationship for pages like views.
boost_cache_write Writes data to filename in an atomic operation thats compatible with older versions of php (php < 5.2.4 file_put_contents() doesn't lock correctly).
boost_comment Implementation of hook_comment(). Acts on comment modification.
boost_crawler_add_alias_to_table Get URLs from url alias table
boost_crawler_add_to_table Add URL's to the boost_crawler table.
boost_crawler_count Count the number of URL's in the boost_cache table.
boost_crawler_prune_table Remove any urls in the crawler table if they are not expired or flushed.
boost_crawler_run The brains of the crawler.
boost_crawler_seed_tables Logic to get boost_crawler table ready.
boost_crawler_threads_alive Check for any dead threads.
boost_crawler_total_count Get count of boost_crawler table.
boost_crawler_verify Reload any url's that did not get cached.
boost_cron Implementation of hook_cron(). Performs periodic actions.
boost_ctools_render_alter Implementation of hook_ctools_render_alter().
boost_db_get_age
boost_db_get_cache_age
boost_db_get_ttl
boost_db_is_expired
boost_db_multi_delete_in Delete records from the database where IN(...).
boost_db_multi_insert Insert many records into the database.
boost_db_multi_select_in Select records in the database matching where IN(...).
boost_db_multi_update_set Update records in the database where IN(...).
boost_db_prep Figure out what is going in the database & put it in
boost_domainupdate Implementation of hook_domainupdate() - keeps domain whitelist variable current
boost_drupal_get_installed_schema_version Returns the currently installed schema version for a module.
boost_drupal_http_request Perform an HTTP request.
boost_exit Implementation of hook_exit(). Performs cleanup tasks.
boost_expire_node Expires a node from the cache; including related pages.
boost_fast404 Send out a fast 404 and exit.
boost_file_get_age Returns the age of a cached file, measured in seconds since it was last updated.
boost_file_get_ttl Returns the remaining time-to-live for a cached file, measured in seconds.
boost_file_is_expired Determines whether a cached file has expired, i.e. whether its age exceeds the maximum cache lifetime as defined by Drupal's system settings.
boost_file_path Returns the static file path for a Drupal page.
boost_flush_caches
boost_form_alter Implementation of hook_form_alter(). Performs alterations before a form is rendered.
boost_get_all_filenames Returns all possible filenames given the input and current settings
boost_get_base_urls Get all base url's where this node can appear
boost_get_content_type Determines the MIME content type of the current page response based on the currently set Content-Type HTTP header.
boost_get_db Gets boost info from cache database.
boost_get_domains Get domains the node is currently published to
boost_get_generation_time Returns the time it took to generate this cached page.
boost_get_http_status Determines the HTTP response code that the current page request will be returning by examining the HTTP headers that have been output so far.
boost_get_menu_structure Finds parent, siblings and children of the menu item. UGLY CODE...
boost_get_settings_db Gets boost settings from cache settings database.
boost_get_time Checks various timestamps in the database.
boost_glue_url Alt to Drupal's url() function.
boost_has_site_changed Checks various timestamps in the database.
boost_headers_contain
boost_help Implementation of hook_help(). Provides online user help.
boost_htaccess_cache_dir_generate
boost_htaccess_cache_dir_put
boost_init Implementation of hook_init(). Performs page setup tasks if page not cached.
boost_is_cacheable Determines whether a given url can be cached or not by boost.
boost_is_cached Determines whether a given Drupal page is currently cached or not.
boost_menu Implementation of hook_menu().
boost_nodeapi Implementation of hook_nodeapi(). Acts on nodes defined by other modules.
boost_node_get_basics Get some basic node attribues from the database given node id.
boost_path_redirect_load Retrieve a specific URL redirect from the database. http://drupal.org/node/451790
boost_print_r An alternative to print_r that unlike the original does not use output buffering with the return parameter set to true. Thus, Fatal errors that would be the result of print_r in return-mode within ob handlers can be avoided. http://php.net/print-r#75872
boost_put_db Puts boost info into database.
boost_put_settings_db Puts boost info into database.
boost_redirect_handler Grabs drupal_goto requests via boost_exit and looks for redirects.
boost_remove_db Removes info from database. Use on 404 or 403.
boost_remove_settings_db Removes info from boost database.
boost_set_base_dir_in_array Sets the base_dir array key based on settings.
boost_set_cookie Sets a special cookie preventing authenticated users getting served pages from the static page cache.
boost_set_db_page_settings Sets per page configuration.
boost_stats_generate Generate js/html for boost stat counter.
boost_strlen Get the length of a string in bytes
boost_taxonomy Implementation of hook_taxonomy(). Acts on taxonomy changes.
boost_taxonomy_node_get_tids Return taxonomy terms given a nid.
boost_theme Implementation of hook_theme().
boost_user Implementation of hook_user(). Acts on user account actions.
boost_views_async Run views looking for new nodes.
boost_views_generate_default_list Genrate a list of views based off of defaults.
boost_views_get_valid_array Genrate a list of valid views that boost will search for new content.
boost_views_get_valid_list Genrate a list of valid views that boost will search for new content.
boost_views_pre_render Implementation of hook_views_pre_render().
boost_views_pre_view Implementation of hook_views_pre_view().
boost_votingapi_delete Implementation of hook_votingapi_delete().
boost_votingapi_insert Implementation of hook_votingapi_insert().
hook_boost_is_cacheable This hook is run inorder to determine if a page should be cached. Runs in boost_init().
hook_boost_preprocess Edit document before it is put into the boost cache.
theme_boost_cache_status
_boost_cache_insert Shutdown function, gets called at the very end of node creation.
_boost_change_extension Change a file extension in the database.
_boost_copy_css_files Extract css filenames from html and copy them & their children.
_boost_copy_file Copy a file.
_boost_copy_file_get_domains Get the various subdomains from parallel module.
_boost_copy_js_files Extract javascript filenames from html and copy them & their children.
_boost_generate_gzip_test_file Generate iframe gzip cookie test html file.
_boost_get_menu_router Gets menu router contex
_boost_gz_copy_file Copy a file and gzip its contents
_boost_index_exists Add an index if it doesn't exist
_boost_inject_code Inject code into document.
_boost_kill_file_extension Removes file extension from filename.
_boost_microtime_float microtime(TRUE) emulation
_boost_mkdir_p Recursive version of mkdir(), compatible with PHP4.
_boost_ob_handler PHP output buffering callback for static page caching.
_boost_page_have_error Get PHP error if it exists.
_boost_rmdir_rf Recursive version of rmdir(); use with extreme caution.
_boost_set_time_limit Attempts to set the PHP maximum execution time. See http://api.drupal.org/api/function/drupal_set_time_limit/7
_boost_variable_get Internal variable get; don't rely on cache table.
_boost_variable_set Internal variable set; don't rely on cache table.
_boost_views_runit
_boost_view_insert Shutdown function, gets called at the very end of node creation.
_boost_write_file_chmod Create the dir, write the file, chmod the file.

Constants

Namesort descending Description
BOOST_AGGRESSIVE_COOKIE
BOOST_AGGRESSIVE_GZIP
BOOST_ASYNCHRONOUS_OUTPUT
BOOST_BANNER
BOOST_CACHEABILITY_OPTION
BOOST_CACHEABILITY_PAGES
BOOST_CACHE_CSS
BOOST_CACHE_HTML
BOOST_CACHE_JS
BOOST_CACHE_JSON
BOOST_CACHE_JSON_LIFETIME
BOOST_CACHE_LIFETIME
BOOST_CACHE_QUERY
BOOST_CACHE_XML
BOOST_CACHE_XML_LIFETIME
BOOST_CHAR
BOOST_CHECK_BEFORE_CRON_EXPIRE
BOOST_CLEAR_CACHE_OFFLINE
BOOST_COOKIE
BOOST_CRAWLER_BATCH_SIZE
BOOST_CRAWLER_SELF
BOOST_CRAWLER_THREADS
BOOST_CRAWLER_THROTTLE
BOOST_CRAWL_DB_IMPORT_SIZE
BOOST_CRAWL_ON_CRON
BOOST_CRAWL_URL_ALIAS
BOOST_CSS_EXTENSION
BOOST_DISABLE_CLEAN_URL
BOOST_DOMAIN_BLACKLIST_ONLY
BOOST_DOMAIN_BOTH_LISTS
BOOST_DOMAIN_NO_LISTS
BOOST_DOMAIN_WHITELIST_ONLY
BOOST_ENABLED
BOOST_EXIT_IN_HOOK_EXIT
BOOST_EXPIRE_NO_FLUSH
BOOST_FILE_EXTENSION
BOOST_FILE_PATH
BOOST_FLUSH_ALL_MULTISITE
BOOST_FLUSH_CCK_REFERENCES
BOOST_FLUSH_DIR
BOOST_FLUSH_FRONT
BOOST_FLUSH_MENU_ITEMS
BOOST_FLUSH_NODE_TERMS
BOOST_FLUSH_VIEWS
BOOST_FLUSH_VIEWS_INSERT
BOOST_FLUSH_VIEWS_UPDATE
BOOST_GZIP
BOOST_GZIP_DIR
BOOST_GZIP_EXTENSION
BOOST_GZIP_FILE_PATH
BOOST_HALT_ON_ERRORS
BOOST_HALT_ON_MESSAGES
BOOST_HOST
BOOST_IGNORE_HTACCESS_WARNING
BOOST_IGNORE_SAFE_WARNING
BOOST_IGNORE_SUBDIR_LIMIT
BOOST_JSON_EXTENSION
BOOST_JS_EXTENSION
BOOST_LOOPBACK_BYPASS
BOOST_MAX_PATH_DEPTH
BOOST_MAX_THREADS
BOOST_MAX_THREAD_TIME
BOOST_MAX_TIMESTAMP
BOOST_MULTISITE_SINGLE_DB
BOOST_NORMAL_DIR
BOOST_NO_DATABASE
BOOST_ONLY_ASCII_PATH
BOOST_OVERWRITE_FILE
BOOST_PAGER_CLEAN
BOOST_PERMISSIONS_DIR
BOOST_PERMISSIONS_FILE
BOOST_PERM_CHAR
BOOST_PERM_FILE_PATH
BOOST_PERM_GZIP_FILE_PATH
BOOST_PERM_GZ_DIR
BOOST_PERM_NORMAL_DIR
BOOST_PRE_PROCESS_FUNCTION
BOOST_PUSH_HTML
BOOST_PUSH_JSON
BOOST_PUSH_XML
BOOST_ROOT_CACHE_DIR
BOOST_ROOT_FILE
BOOST_SET_FILE_ENCODING
BOOST_TIME
BOOST_VERBOSE
BOOST_VIEWS_LIST_BEHAVIOR
BOOST_XML_EXTENSION

Globals

Namesort descending Description
$base_url