uid); ////////////////////////////////////////////////////////////////////////////// // Global variables //$GLOBALS['_boost_path'] = ''; //$GLOBALS['_boost_query'] = ''; //$GLOBALS['_boost_message_count'] = ''; //$GLOBALS['_boost_cache_this'] = ''; ////////////////////////////////////////////////////////////////////////////// // 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 '
' . implode("\n", array_slice(explode("\n", @file_get_contents($file)), 2)) . '
'; } break; case 'admin/settings/performance/boost': return '

' . t('') . '

'; // TODO: add help text. } //hack to get drupal_get_messages before they are destroyed. $GLOBALS['_boost_message_count'] = count(drupal_get_messages(NULL, FALSE)); } /** * Implementation of hook_init(). Performs page setup tasks if page not cached. */ function boost_init() { global $user, $base_path; //set variables $GLOBALS['_boost_path'] = $_REQUEST['q']; // Make the proper filename for our query $GLOBALS['_boost_query'] = '_'; foreach ($_GET as $key => $val) { if ($key != 'q' && $key != 'destination') { $GLOBALS['_boost_query'] .= (($GLOBALS['_boost_query'] == '_') ? '' : '&') . $key . '=' . $val; } } if (!empty($user->uid)) { boost_set_cookie($user); if (BOOST_DISABLE_CLEAN_URL) { $GLOBALS['conf']['clean_url'] = 0; db_query('TRUNCATE {cache_filter}'); db_query('TRUNCATE {cache_menu}'); cache_clear_all('*', 'cache_menu'); cache_clear_all('*', 'cache_filter'); } } // Make sure the page is/should be cached according to our current configuration if ( strpos($_SERVER['SCRIPT_FILENAME'], 'index.php') === FALSE || variable_get('site_offline', 0) || $_SERVER['REQUEST_METHOD'] != 'GET' || $_SERVER['SERVER_SOFTWARE'] === 'PHP CLI' || !BOOST_ENABLED || isset($_GET['nocache']) || !boost_is_cacheable($GLOBALS['_boost_path']) ) { $GLOBALS['_boost_cache_this'] = FALSE; return; } // We only generate cached pages for anonymous visitors. if (empty($user->uid)) { if (BOOST_ENABLED != CACHE_AGGRESSIVE) { $GLOBALS['conf']['cache'] = CACHE_DISABLED; } $GLOBALS['_boost_cache_this'] = TRUE; ob_start('_boost_ob_handler'); } } /** * Implementation of hook_exit(). Performs cleanup tasks. * * For POST requests by anonymous visitors, this adds a dummy query string * to any URL being redirected to using drupal_goto(). * * This is pretty much a hack that assumes a bit too much familiarity with * what happens under the hood of the Drupal core function drupal_goto(). * * It's necessary, though, in order for any session messages set on form * submission to actually show up on the next page if that page has been * cached by Boost. */ function boost_exit($destination = NULL) { // Check that hook_exit() was invoked by drupal_goto() for a POST request: if (!empty($destination) && $_SERVER['REQUEST_METHOD'] == 'POST') { // Check that we're dealing with an anonymous visitor. and that some // session messages have actually been set during this page request: global $user; if (empty($user->uid) && ($messages = drupal_set_message())) { // FIXME: call any remaining exit hooks since we're about to terminate? $query_parts = parse_url($destination); // Add a nocache parameter to query. Such pages will never be cached $query_parts['query'] .= (empty($query_parts['query']) ? '' : '&') . 'nocache=1'; // Rebuild the URL with the new query string. Do not use url() since // destination has presumably already been run through url(). $destination = boost_glue_url($query_parts); // Do what drupal_goto() would do if we were to return to it: exit(header('Location: ' . $destination)); } } } /** * Implementation of hook_menu(). */ function boost_menu() { $items['admin/settings/performance/default'] = array( 'title' => 'Performance', 'type' => MENU_DEFAULT_LOCAL_TASK, 'file path' => drupal_get_path('module', 'system'), ); $items['admin/settings/performance/boost'] = array( 'title' => 'Boost Settings', 'description' => 'Advanced boost configuration.', 'page callback' => 'drupal_get_form', 'page arguments' => array('boost_admin_boost_performace_page'), 'access arguments' => array('administer site configuration'), 'weight' => 10, '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, 'file path' => drupal_get_path('module', 'boost'), 'file' => 'stats/boost_stats.ajax.inc', ); $items['boost-crawler'] = array( 'page callback' => 'boost_crawler_init', 'type' => MENU_CALLBACK, 'access callback' => 1, '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) { switch ($form_id) { // Alter Drupal's system performance settings form by hiding the default // cache enabled/disabled control (which will now always default to // CACHE_DISABLED), and inject our own settings in its stead. case 'system_performance_settings': module_load_include('inc', 'boost', 'boost.admin'); $form['page_cache'] = boost_admin_performance_page($form['page_cache']); $form['page_cache']['#title'] = t('Anonymous page caching'); $form['page_cache']['#description'] = t('Enabling the page cache will offer a significant performance boost. Drupal can store and send compressed cached pages requested by anonymous users. By caching the first request to the page, Drupal does not have to construct the page each time it is viewed. The page must first be visited by an anonymous user in order for the cache to work on subsequent requests for that page. Boost & Core caching do not work for logged in users.'); $form['#submit'][] = 'boost_admin_performance_page_submit'; $form['clear_cache']['clear']['#submit'][0] = 'boost_admin_clear_cache_submit'; break; // Alter Drupal's site maintenance settings form in order to ensure that // the static page cache gets wiped if the administrator decides to take // the site offline. case 'system_site_maintenance_settings': module_load_include('inc', 'boost', 'boost.admin'); $form['#submit'][] = 'boost_admin_site_offline_submit'; break; // Alter Drupal's modules build form in order to ensure that // the static page cache gets wiped if the administrator decides to // change enabled modules case 'system_modules': module_load_include('inc', 'boost', 'boost.admin'); $form['#submit'][] = 'boost_admin_modules_submit'; break; // Alter Drupal's theme build form in order to ensure that // the static page cache gets wiped if the administrator decides to // change theme case 'system_themes_form': module_load_include('inc', 'boost', 'boost.admin'); $form['#submit'][] = 'boost_admin_themes_submit'; // Added below due to this bug: http://drupal.org/node/276615 if (variable_get('preprocess_css', FALSE)==TRUE) { if (boost_cache_clear_all()) { drupal_set_message(t('Boost: Static page cache cleared. See http://drupal.org/node/276615 for reason why (core bug).'), 'warning'); } } break; } } /** * Implementation of hook_cron(). Performs periodic actions. */ function boost_cron() { if (!BOOST_ENABLED) { return; } // Expire old content if (variable_get('boost_expire_cron', TRUE) && boost_cache_db_expire()) { if (BOOST_VERBOSE >= 5) { watchdog('boost', 'Expired stale files from static page cache.', array(), WATCHDOG_NOTICE); } } // Update Stats if (module_exists('statistics') && variable_get('boost_block_show_stats', FALSE)) { $block = module_invoke('statistics', 'block', 'view', 0); variable_set('boost_statistics_html', $block['content']); } // Crawl Site if (BOOST_CRAWL_ON_CRON) { boost_crawler_init(); } } /* * 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 (!BOOST_ENABLED) return; switch ($op) { case 'insert': case 'update': // Expire the relevant node page from the static page cache to prevent serving stale content: if (!empty($comment['nid'])) { boost_cache_expire_derivative('node/' . $comment['nid'], TRUE); } break; case 'publish': case 'unpublish': case 'delete': if (!empty($comment->nid)) { boost_cache_expire_derivative('node/' . $comment->nid, TRUE); } break; } } /** * Implementation of hook_nodeapi(). Acts on nodes defined by other modules. */ function boost_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { if (!BOOST_ENABLED) return; switch ($op) { case 'insert': case 'update': case 'delete': // Expire all relevant node pages from the static page cache to prevent serving stale content: if (!empty($node->nid)) { if ($node->promote == 1) { boost_cache_expire_derivative(''); } boost_cache_expire_derivative('node/' . $node->nid, TRUE); } // get terms and flush their page if (BOOST_FLUSH_NODE_TERMS) { $terms = taxonomy_node_get_terms(node_load($node->nid)); $filenames = array(); foreach($terms as $term) { $filenames = array_merge($filenames, boost_get_db_term($term->tid)); } foreach($filenames as $filename) { boost_cache_kill($filename); } } break; } } /** * Implementation of hook_taxonomy(). Acts on taxonomy changes. */ function boost_taxonomy($op, $type, $term = NULL) { if (!BOOST_ENABLED) return; switch ($op) { case 'insert': case 'update': case 'delete': // TODO: Expire all relevant taxonomy pages from the static page cache to prevent serving stale content. break; } } /** * Implementation of hook_user(). Acts on user account actions. */ function boost_user($op, &$edit, &$account, $category = NULL) { if (!BOOST_ENABLED) return; global $user; switch ($op) { case 'login': // Set a special cookie to prevent authenticated users getting served // pages from the static page cache. boost_set_cookie($user); break; case 'logout': boost_set_cookie($user, BOOST_TIME - 86400); break; case 'insert': // TODO: create user-specific cache directory. break; case 'delete': // Expire the relevant user page from the static page cache to prevent serving stale content: if (!empty($account->uid)) { boost_cache_expire_derivative('user/' . $account->uid); } // TODO: recursively delete user-specific cache directory. break; } } /** * Implementation of hook_block(). */ function boost_block($op = 'list', $delta = 0, $edit = array()) { global $user; switch ($op) { case 'list': return array( 'status' => array( 'info' => t('Boost: Pages cache status'), 'region' => 'right', 'weight' => 10, 'cache' => BLOCK_NO_CACHE, ), 'stats' => array( 'info' => t('Boost: AJAX core statistics'), 'region' => 'right', 'weight' => 10, 'cache' => BLOCK_NO_CACHE, ), ); 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; } 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']); } case 'view': $block = array(); switch ($delta) { case 'status': // Don't show the block to anonymous users, nor on any pages that // aren't even cacheable to begin with (e.g. admin/*). if (!empty($user->uid) && boost_is_cacheable($GLOBALS['_boost_path'])) { $output = t('This page is being served live to anonymous visitors, as it is not currently in the static page cache.'); if (boost_is_cached($GLOBALS['_boost_path'])) { $ttl = boost_db_get_ttl(boost_file_path($GLOBALS['_boost_path'])); $output = t('This page is being served to anonymous visitors from the static page cache.') . ' '; $output .= t($ttl < 0 ? 'The cached copy expired %interval ago.' : 'The cached copy will expire in %interval.', array('%interval' => format_interval(abs($ttl)))); $output .= drupal_get_form('boost_block_flush_form'); } $error = FALSE; if (function_exists('error_get_last')) { $error = error_get_last(); } $drupal_msg = max(count(drupal_get_messages(NULL, FALSE)), $GLOBALS['_boost_message_count']); if (BOOST_HALT_ON_ERRORS && ($error || $drupal_msg != 0)) { $output = t('There are php errors or drupal messages on this page, preventing boost from caching.'); if ($error) { $output .= t(' ERROR:
%error
!link
!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'))); } if ($drupal_msg != 0) { $output .= t(' MESSAGES: %msg
!performance', array('%msg' => $drupal_msg, '!performance' => l(t('Turn Off Error Checking'), 'admin/settings/performance'))); } } $output .= drupal_get_form('boost_block_db_settings_form'); $block['subject'] = ''; $block['content'] = theme('boost_cache_status', isset($ttl) ? $ttl : -1, $output); } break; case 'stats': $filename = 'boost_stats.php'; $block = module_invoke('statistics', 'block', 'view', 0); variable_set('boost_statistics_html', $block['content']); if (!( strpos($_SERVER['SCRIPT_FILENAME'], 'index.php') === FALSE || variable_get('site_offline', 0) || $_SERVER['REQUEST_METHOD'] != 'GET' || $_SERVER['SERVER_SOFTWARE'] === 'PHP CLI' || !BOOST_ENABLED || isset($_GET['nocache']) || !boost_is_cacheable($GLOBALS['_boost_path']) || !empty($user->uid) || !module_exists('statistics') )) { $block = array(); $block['subject'] = 'Popular content'; $block['content'] = '
' . boost_stats_generate($filename); } elseif (!variable_get('boost_block_show_stats', FALSE)) { $block['content'] .= '
'; drupal_add_js('$("#boost-stats").parent().parent().hide();', 'inline', 'footer'); } break; } return $block; } } function boost_block_flush_form() { $form['boost_clear']['path'] = array( '#type' => 'hidden', '#value' => $GLOBALS['_boost_path'], ); $form['boost_cache']['clear'] = array( '#type' => 'submit', '#value' => t('Flush Page'), '#submit' => array('boost_block_form_flush_submit'), ); return ($form); } function boost_block_form_flush_submit(&$form_state, $form) { boost_cache_expire_derivative($form['values']['path'], TRUE); } function boost_block_db_settings_form() { // set info $period = drupal_map_assoc(array(-1, 0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 64800, 86400, 2*86400, 3*86400, 4*86400, 5*86400, 6*86400, 604800, 2*604800, 3*604800, 4*604800, 8*604800, 16*604800, 52*604800), 'format_interval'); $period[0] = '<' . t('none') . '>'; $period[-1] = t('default'); //$info = boost_get_db(boost_file_path($GLOBALS['_boost_path'])); $router_item = _boost_get_menu_router(); $settings = boost_get_settings_db($router_item); $default = 0; foreach ($settings as $key => $value) { if ($value != NULL) { $info = $value; $default = $key; break; } } if (!isset($info)) { $info['lifetime'] = -1; $info['push'] = -1; } // create form $form['boost_db_settings']['lifetime'] = array( '#type' => 'select', '#title' => t('Minimum cache lifetime'), '#default_value' => $info['lifetime'], '#options' => $period, '#description' => t('Default: %default', array('%default' => format_interval(BOOST_CACHE_LIFETIME))), ); $form['boost_db_settings']['push'] = array( '#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_arguments'], 2 => 'Content Container: ' . $router_item['page_callback'], ), ); $form['boost_db_settings']['send'] = array( '#type' => 'submit', '#value' => t('Set Configuration'), '#submit' => array('boost_block_db_settings_form_submit'), ); $form['boost_db_rm_settings']['id'] = array( '#type' => 'checkbox', '#title' => t('Page ID'), '#default_value' => $settings[0] != NULL ? FALSE : TRUE, '#disabled' => $settings[0] != NULL ? FALSE : TRUE, '#description' => $period[$settings[0]['lifetime']] . ' - ' . $router_item['page_id'], ); $form['boost_db_rm_settings']['id_value'] = array( '#type' => 'hidden', '#title' => t('id_value'), '#default_value' => $settings[0] != NULL ? $settings[0]['csid'] : FALSE, '#disabled' => $settings[0] != NULL ? FALSE : TRUE, ); $form['boost_db_rm_settings']['type'] = array( '#type' => 'checkbox', '#title' => t('Content Type'), '#default_value' => $settings[1] != NULL ? FALSE : TRUE, '#disabled' => $settings[1] != NULL ? FALSE : TRUE, '#description' => $period[$settings[1]['lifetime']] . ' - ' . $router_item['page_arguments'], ); $form['boost_db_rm_settings']['type_value'] = array( '#type' => 'hidden', '#title' => t('type_value'), '#default_value' => $settings[1] != NULL ? $settings[1]['csid'] : FALSE, '#disabled' => $settings[1] != NULL ? FALSE : TRUE, ); $form['boost_db_rm_settings']['container'] = array( '#type' => 'checkbox', '#title' => t('Content Container'), '#default_value' => $settings[2] != NULL ? FALSE : TRUE, '#disabled' => $settings[2] != NULL ? FALSE : TRUE, '#description' => $period[$settings[2]['lifetime']] . ' - ' . $router_item['page_callback'], ); $form['boost_db_rm_settings']['container_value'] = array( '#type' => 'hidden', '#title' => t('container_value'), '#default_value' => $settings[2] != NULL ? $settings[2]['csid'] : FALSE, '#disabled' => $settings[2] != NULL ? FALSE : TRUE, ); $form['boost_db_rm_settings']['send'] = array( '#type' => 'submit', '#value' => t('Delete Configuration'), '#submit' => array('boost_block_db_rm_settings_form_submit'), '#description' => t('Check the box to delete it'), ); return $form; } /** * Sets page specific settings in the boost cache database. */ function boost_block_db_settings_form_submit(&$form_state, $form) { boost_set_db_page_settings($form['values']['lifetime'], $form['values']['push'], $form['values']['selection']); } /** * Sets page specific settings in the boost cache database. */ function boost_block_db_rm_settings_form_submit(&$form_state, $form) { if ($form['values']['id']) { boost_remove_settings_db($form['values']['id_value']); } if ($form['values']['type']) { boost_remove_settings_db($form['values']['type_value']); } if ($form['values']['container']) { boost_remove_settings_db($form['values']['container_value']); } } /** * Generate js/html for boost stat counter. * * NOTE HTML code could be added to the $buffer directly. Would prevent 2x * counts on first view. Would be hard to do though. * * @param $filename * Name of boost's statistics php file. */ function boost_stats_generate($filename) { Global $base_path; // is node & node count enabled. if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == '' && variable_get('statistics_count_content_views', 0)) { $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 = <<'; 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 '' . $text . ''; } ////////////////////////////////////////////////////////////////////////////// // Output buffering callback /** * PHP output buffering callback for static page caching. * * NOTE: objects have already been destructed so $user is not available. * * @param $buffer * Pages contents */ function _boost_ob_handler($buffer) { // Ensure we're in the correct working directory, since some web servers (e.g. Apache) mess this up here. chdir(dirname($_SERVER['SCRIPT_FILENAME'])); if (function_exists('error_get_last')) { if (BOOST_HALT_ON_ERRORS && $error = error_get_last()) { switch ($error['type']) { case E_NOTICE: //Ignore run-time notices case E_USER_NOTICE: //Ignore user-generated notice message //case E_DEPRECATED: //Ignore run-time notices //case E_USER_DEPRECATED: //Ignore user-generated notice message break; default: //Do not cache page on all other errors if (BOOST_VERBOSE >= 3) { watchdog('boost', 'There are php errors on this page, preventing boost from caching. ERROR:
%error
!link
!performance', array('%error' => boost_print_r($error, TRUE), '!link' => l(t('Lookup Error Type'), 'http://php.net/errorfunc.constants'), '!performance' => l(t('Turn Off Error Checking'), 'admin/settings/performance')), WATCHDOG_WARNING); } return $buffer; } } } if (BOOST_HALT_ON_ERRORS && $GLOBALS['_boost_message_count'] != 0) { if (BOOST_VERBOSE >= 3) { watchdog('boost', 'There are drupal messages on this page, preventing boost from caching. MESSAGES: %msg
!performance', array('%msg' => $GLOBALS['_boost_message_count'], '!performance' => l(t('Turn Off Error Checking'), 'admin/settings/performance')), WATCHDOG_WARNING); } return $buffer; } // Check the currently set content type (at present we can't deal with // anything else than HTML) and the HTTP response code. We're going to be // exceedingly conservative here and only cache 'text/html' pages that // were output with a 200 OK status. Anything more is simply asking for // loads of trouble. if ($GLOBALS['_boost_cache_this'] && _boost_get_http_status() == 200 && strlen($buffer) > 0) { switch (_boost_get_content_type()) { case 'text/html': boost_cache_set($GLOBALS['_boost_path'], $buffer); boost_cache_css_js_files($buffer); break; case 'application/rss': case 'text/xml': case 'application/rss+xml': boost_cache_set($GLOBALS['_boost_path'], $buffer, BOOST_XML_EXTENSION); break; case 'text/javascript': boost_cache_set($GLOBALS['_boost_path'], $buffer, '.js'); break; } } // Allow the page request to finish up normally return $buffer; } /** * Determines the MIME content type of the current page response based on * the currently set Content-Type HTTP header. * * This should normally return the string 'text/html' unless another module * has overridden the content type. * * @param $default * Return this value if it can't be found */ function _boost_get_content_type($default = NULL) { static $regex = '!^Content-Type:\s*([\w\d\/\-]+)!i'; return _boost_get_http_header($regex, $default); } /** * Determines the HTTP response code that the current page request will be * returning by examining the HTTP headers that have been output so far. * * @param $default * Look for a 200 status */ function _boost_get_http_status($default = 200) { static $regex = '!^HTTP/1.1\s+(\d+)!'; return (int)_boost_get_http_header($regex, $default); } /** * Get HTTP header * * @param $regex * Regular expression to get HTTP Header Line * @param $default * Return this value if it can't be found */ function _boost_get_http_header($regex, $default = NULL) { // The last header is the one that counts: $headers = preg_grep($regex, explode("\n", drupal_get_headers())); if (!empty($headers) && preg_match($regex, array_pop($headers), $matches)) { return $matches[1]; // found it } return $default; // no such luck } ////////////////////////////////////////////////////////////////////////////// // Boost API implementation /** * Determines whether a given Drupal page can be cached or not. * * To avoid potentially troublesome situations, the user login page is never * cached, nor are any admin pages. At present, we also refuse to cache any * RSS feeds provided by Drupal, since they would require special handling * in the mod_rewrite ruleset as they shouldn't be sent out using the * text/html content type. * TODO: don't cache pages with unacceptable symbols * * @param $path * Current URL */ function boost_is_cacheable($path) { $path = (empty($path)) ? variable_get('site_frontpage', 'node') : $path; $normal_path = drupal_get_normal_path($path); // normalize path // Never cache the basic user login/registration pages or any administration pages // RSS feeds are not cacheable due to content type restrictions // Don't cache comment reply pages if ( $normal_path == 'user' || preg_match('!^user/(login|register|password)!', $normal_path) || preg_match('!^admin!', $normal_path) || preg_match('!comment/reply$!', $normal_path) ) { 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'] != '_') { return FALSE; } // Match the user's cacheability settings against the path if (BOOST_CACHEABILITY_OPTION == 2) { $result = drupal_eval(BOOST_CACHEABILITY_PAGES); return !empty($result); } $regexp = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote(BOOST_CACHEABILITY_PAGES, '/')) .')$/'; return !(BOOST_CACHEABILITY_OPTION xor preg_match($regexp, $path)); // TODO: investigate if $path or $normal_path should be used on the above line. $normal_path was introduced in a recent patch - around http://drupal.org/node/174380#comment-1477658 // TODO: document the above fat $regexp } /** * 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() { if (BOOST_IGNORE_FLUSH == 0) { boost_cache_clear_all_db(); boost_cache_delete(TRUE); if (BOOST_VERBOSE >= 5) { 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. * OLD FUNCTION */ function boost_cache_expire_all() { boost_cache_delete(FALSE); return TRUE; } /** * Resets all entries in database. */ function boost_cache_clear_all_db() { 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 file_put_contents(BOOST_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH); file_put_contents(BOOST_GZIP_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_GZIP_FILE_PATH); //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 file_put_contents(BOOST_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_FILE_PATH); file_put_contents(BOOST_GZIP_FILE_PATH . '/' . BOOST_ROOT_FILE, BOOST_GZIP_FILE_PATH); } /** * Finds all possible paths/redirects/aliases given the root path. * * @param $path * Current URL * @param $wildcard * If true get all chached files that start with this path. */ function boost_cache_expire_derivative($path, $wildcard = FALSE) { global $base_path; //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)))); } //path redirects if (module_exists('path_redirect')) { $path_redirects = boost_path_redirect_load(array('redirect' => $path)); } //flush caches boost_cache_expire($path_alias, $wildcard); if (isset($path_redirects)) { foreach ($path_redirects as $path_redirect) { boost_cache_expire($path_redirect['path'], $wildcard); } } boost_cache_expire($path, $wildcard); } /** * Expires the static file cache for a given page, or multiple pages * matching a wildcard. * * @param $path * Current URL * @param $wildcard * If true get all chached files that start with this path. * * TODO: Replace glob() with a database opperation. */ function boost_cache_expire($path, $wildcard = FALSE) { // Sanity check if (boost_file_path($path, FALSE) === FALSE) { return FALSE; } // Get list of related files $tempA = glob(boost_file_path($path, FALSE, NULL) . (($wildcard) ? '*' : '') . BOOST_FILE_EXTENSION, GLOB_NOSORT); $tempB = glob(boost_file_path($path, FALSE, NULL) . (($wildcard) ? '*' : '') . BOOST_XML_EXTENSION, GLOB_NOSORT); if (!empty($tempA) && !empty($tempB)) { $filenames = array_filter(array_merge($tempA, $tempB)); } elseif (!empty($tempA)) { $filenames = $tempA; } elseif (!empty($tempB)) { $filenames = $tempB; } else { return FALSE; } if (empty($filenames)) { return FALSE; } // Flush expired files foreach ($filenames as $filename) { boost_cache_kill($filename); } return TRUE; } /** * Deletes cached page from file system * * @param $filename * Name of cached file; primary key in database */ function boost_cache_kill($filename) { if (BOOST_IGNORE_FLUSH < 3 && strstr($filename, BOOST_FILE_PATH)) { db_query("UPDATE {boost_cache} SET expire = 0 WHERE filename = '%s'", $filename); if (file_exists($filename)) { @unlink($filename); } $gz_filename = str_replace(BOOST_FILE_PATH, BOOST_GZIP_FILE_PATH, $filename) . '.gz'; if (file_exists($gz_filename)) { @unlink($gz_filename); } } } /** * Flushes all expired pages from database * * TODO del empty dirs if enabled */ function boost_cache_db_expire() { if (BOOST_IGNORE_FLUSH < 2) { $result = db_query('SELECT filename FROM {boost_cache} WHERE expire BETWEEN 1 AND %d', BOOST_TIME); while ($boost = db_fetch_array($result)) { boost_cache_kill($boost['filename']); } if (BOOST_FLUSH_DIR) { // TO-DO: del empty dirs. } return TRUE; } return FALSE; } /** * 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; } /** * 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) { // Code commenting style baised on what is being cached. switch ($extension) { case '.html': if (!BOOST_CACHE_HTML) { return FALSE; } $comment_start = '\n"; $expire = BOOST_CACHE_LIFETIME; break; case '.xml': if (!BOOST_CACHE_XML) { return FALSE; } $comment_start = '\n"; $expire = BOOST_CACHE_XML_LIFETIME; break; case '.js': if (!BOOST_CACHE_JSON) { return FALSE; } $comment_start = '/* '; $comment_end = " */\n"; $expire = BOOST_CACHE_JSON_LIFETIME; break; } // Append the Boost footer with the relevant timestamps $cached_at = date('Y-m-d H:i:s', BOOST_TIME); $expires_at = date('Y-m-d H:i:s', BOOST_TIME + $expire); $data = rtrim($data) . "\n" . $comment_start . str_replace(array('%cached_at', '%expires_at'), array($cached_at, $expires_at), BOOST_BANNER) . $comment_end; // Invoke hook_boost_preprocess($path, $data) foreach (module_implements('boost_preprocess') as $module) { if (($result = module_invoke($module, $path, $data)) != 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, $data); } db_set_active(); // 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))) { boost_cache_write($filename, $data); if (BOOST_GZIP) { boost_cache_write(str_replace(BOOST_FILE_PATH, BOOST_GZIP_FILE_PATH, $filename) . '.gz', gzencode($data, 9)); } boost_db_prep($filename, $extension, BOOST_TIME + $expire); return TRUE; } else { return FALSE; } } /** * 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; 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_arguments & page_id. * @param $timer * Time it took drupal to build this page. * @param $timer_average * Average time durpal has spent building this page. * @param $extension * Filename extension: Used for content types. */ function boost_put_db($filename, $expire, $lifetime, $push, $router_item, $timer, $timer_average, $extension) { global $base_root; $url = $base_root . request_uri(); db_query("UPDATE {boost_cache} SET expire = %d, lifetime = %d, push = %d, page_callback = '%s', page_arguments = '%s', timer = %d, timer_average = %d, base_dir = '%s', page_id = %d, extension = '%s', url = '%s' WHERE filename = '%s'", $expire, $lifetime, $push, $router_item['page_callback'], $router_item['page_arguments'], $timer, $timer_average, BOOST_FILE_PATH, $router_item['page_id'], $extension, $url, $filename); if (!db_affected_rows()) { @db_query("INSERT INTO {boost_cache} (filename, expire, lifetime, push, page_callback, page_arguments, timer, timer_average, base_dir, page_id, extension, url) VALUES ('%s', %d, %d, %d, '%s', '%s', %d, %d, '%s', %d, '%s', '%s')", $filename, $expire, $lifetime, $push, $router_item['page_callback'], $router_item['page_arguments'], $timer, $timer_average, BOOST_FILE_PATH, $router_item['page_id'], $extension, $url); } } /** * Puts boost info into database. * * @param $filename * Name of cached file; 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_arguments & 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_arguments = '%s' AND base_dir = '%s' AND page_id = %d", $lifetime, $push, $router_item['page_callback'], $router_item['page_arguments'], BOOST_FILE_PATH, $router_item['page_id']); if (!db_affected_rows()) { @db_query("INSERT INTO {boost_cache_settings} (csid, lifetime, push, page_callback, page_arguments, base_dir, page_id) VALUES (NULL, %d, %d, '%s', '%s', '%s', %d)", $lifetime, $push, $router_item['page_callback'], $router_item['page_arguments'], 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_arguments = '%s' AND base_dir = '%s' AND page_id = 0", $lifetime, $push, $router_item['page_callback'], $router_item['page_arguments'], BOOST_FILE_PATH); if (!db_affected_rows()) { @db_query("INSERT INTO {boost_cache_settings} (csid, lifetime, push, page_callback, page_arguments, base_dir, page_id) VALUES (NULL, %d, %d, '%s', '%s', '%s', %d)", $lifetime, $push, $router_item['page_callback'], $router_item['page_arguments'], BOOST_FILE_PATH, 0); } break; case 2: db_query("UPDATE {boost_cache_settings} SET lifetime = %d, push = %d WHERE page_callback = '%s' AND page_arguments = '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} (csid, lifetime, push, page_callback, page_arguments, base_dir, page_id) VALUES (NULL, %d, %d, '%s', '%s', '%s', %d)", $lifetime, $push, $router_item['page_callback'], '0', BOOST_FILE_PATH, 0); } break; } } /** * Removes info from boost database. * * @param $filename * Name of cached file; 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_arguments & page_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 */ 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. boost_cache_expire_derivative($GLOBALS['_boost_path']); //boost_put_db($filename, $info['expire'], $lifetime, $push, $router_item); boost_put_settings_db($lifetime, $push, $router_item, $scope); } /** * Gets boost info from cache database. * * @param $filename * Filename to be looked up in the database */ function boost_get_db($filename) { return db_fetch_array(db_query_range("SELECT * FROM {boost_cache} WHERE filename = '%s'", $filename, 0, 1)); } /** * Gets boost settings from cache settings database. * * @param $router_item * Array containing page_callback, page_arguments & page_id. */ function boost_get_settings_db($router_item) { $settings = array(); // Get a more exact match first $settings[] = db_fetch_array(db_query_range("SELECT * FROM {boost_cache_settings} WHERE page_callback = '%s' AND page_arguments = '%s' AND base_dir = '%s' AND page_id = %d", $router_item['page_callback'], $router_item['page_arguments'], 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_arguments = '%s' AND base_dir = '%s' AND page_id = %d", $router_item['page_callback'], $router_item['page_arguments'], 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_arguments = '%s' AND base_dir = '%s' AND page_id = %d", $router_item['page_callback'], 0, BOOST_FILE_PATH, 0, 0, 1)); return $settings; } /** * Returns all cached pages asscoited with the taxonomy term. */ function boost_get_db_term($term) { $filenames = array(); $result = db_query("SELECT filename FROM {boost_cache} WHERE expire > 0 AND page_id = %d AND page_callback = 'taxonomy'", $term); while ($filename = db_fetch_array($result)) { $filenames[] = $filename['filename']; } return $filenames; } /** * 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 $data * Contents of file */ function boost_cache_write($filename, $data = '') { if (!_boost_mkdir_p(dirname($filename))) { if (BOOST_VERBOSE >= 3) { watchdog('boost', 'Unable to create directory: %dir
Group ID: %gid
User ID: %uid
Current script owner: %user
', array('%dir' => dirname($filename), '%gid' => getmygid(), '%uid' => getmyuid(), '%user' => get_current_user()), WATCHDOG_WARNING); } } $tempfile = $filename . getmypid(); if (@file_put_contents($tempfile, $data) === FALSE) { if (BOOST_VERBOSE >= 3) { watchdog('boost', 'Unable to write temp file: %file
Group ID: %gid
User ID: %uid
Current script owner: %user
', 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)); } // put the temp file in its final location if (@rename($tempfile, $filename) === FALSE) { if (BOOST_VERBOSE >= 5) { watchdog('boost', 'Unable to rename file: %temp to %file
Group ID: %gid
User ID: %uid
Current script owner: %user
', array('%temp' => $tempfile, '%file' => $filename, '%gid' => getmygid(), '%uid' => getmyuid(), '%user' => get_current_user()), WATCHDOG_WARNING); } @unlink($tempfile); return FALSE; } } 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. */ function boost_cache_directory($host = NULL, $absolute = TRUE) { global $base_url; if ($base_url == "http://") { if (BOOST_VERBOSE >= 1) { watchdog('boost', 'base_url is not set in your settings.php file. Please read #7 in boosts INSTALL.txt file.', array(), WATCHDOG_NOTICE); } if (!BOOST_MULTISITE_SINGLE_DB) { $base_url = $base_url . str_replace(BOOST_ROOT_CACHE_PATH . '/', '', variable_get('boost_file_path', boost_cache_directory(NULL, FALSE))); } } $parts = parse_url($base_url); $host = !empty($host) ? $host : $parts['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(BOOST_ROOT_CACHE_PATH, $host, $subdir)) : array_filter(array(getcwd(), BOOST_ROOT_CACHE_PATH, $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 */ function boost_file_path($path, $query = TRUE, $extension = BOOST_FILE_EXTENSION) { //handling of url variables if ($GLOBALS['_boost_query'] != '_') { if ($query) { $path .= $GLOBALS['_boost_query']; } } 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; } // 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; } } return implode('/', array(BOOST_FILE_PATH, $path . (is_null($extension) ? '' : $extension))); } /** * 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) { $boost_db = boost_get_db($filename); return boost_db_get_age($filename) - BOOST_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 $user * User Object * @param $expires * Expiration time */ function boost_set_cookie($user, $expires = NULL) { if (!$expires) { $expires = ini_get('session.cookie_lifetime'); $expires = (!empty($expires) && is_numeric($expires)) ? BOOST_TIME + (int)$expires : 0; setcookie(BOOST_COOKIE, $user->uid, $expires, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure') == '1'); } else { setcookie(BOOST_COOKIE, FALSE, $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) { $css = explode('', array_pop($css)); $css[] = array_shift($temp); $css = implode('',$css); _boost_copy_css_files($css); } if (BOOST_CACHE_JS) { $js = explode('