' . implode("\n", array_slice(explode("\n", @file_get_contents($file)), 2)) . ''; } break; case 'admin/settings/performance/boost': return '

' . '' . '

'; // TODO: add help text. } //hack to get drupal_get_messages before they are destroyed. $GLOBALS['_boost_message_count'] = count(drupal_get_messages(NULL, FALSE)); } /** * Get some basic node attribues from the database given node id. * * @param $nid * node ID * @return node object * node->nid * node->type * node->language * node->path * node->domain * 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; } // View with a path elseif (isset($display->display_options['path'])) { $path = $display->display_options['path']; $hash = $view_name . ' - ' . $display_id . ' - /' . $path; // Path is cacheable via boost & does not take arguments if (boost_is_cacheable($path) && !stristr($path, '%')) { $enabled[$hash] = $hash; } // Path is not cacheable via boost or view takes arguments else { $disabled[$hash] = 0; } } // All other displays mark as disabled else { $disabled[$hash] = 0; } } } ksort($disabled); natcasesort($enabled); $checkboxes = array_merge(array('line-break' => 0), $disabled, $enabled); return $checkboxes; } /** * Genrate a list of valid views that boost will search for new content. * * @return array */ function boost_views_get_valid_list($fresh = FALSE) { // Load saved default values; if not saved, save it. $defaults = variable_get('boost_views_list_default', FALSE); if (!$defaults || $fresh) { $defaults = boost_views_generate_default_list(); variable_set('boost_views_list_default', $defaults); } // Load custom settings $list = variable_get('boost_views_list_custom', array()); // Load defaults. http://php.net/operators.array $list += $defaults; return $list; } /** * Genrate a list of valid views that boost will search for new content. * * @return array */ function boost_views_get_valid_array() { $list = boost_views_get_valid_list(); $views = array(); foreach ($list as $hash => $enabled) { if ($enabled) { $info = explode(' - ', $hash); $page_type = $info[0]; $page_id = $info[1]; $key = $page_type . ' - ' . $page_id; $views[$key]['page_type'] = $page_type; $views[$key]['page_id'] = $page_id; } } return $views; } /** * Implementation of hook_views_pre_view(). * * Hack due to this issue: http://drupal.org/node/619852 */ function boost_views_pre_view(&$view) { } /** * Send out a fast 404 and exit. */ function boost_fast404() { global $base_path; if (!headers_sent()) { header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); } print '' . "\n"; print ''; print '' . t('404 Not Found') . ''; print '

' . t('Not Found') . '

'; print '

' . t('The requested URL was not found on this server.') . '

'; print '

' . t('Home') . '

'; print ''; 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); } // Remove Boost cookie if set to -1 elseif (isset($_COOKIE[BOOST_COOKIE]) && $_COOKIE[BOOST_COOKIE] == '-1') { boost_set_cookie($uid, BOOST_TIME - 86400); } // Set Boost cookie if it doesn't exists and user is logged in elseif (BOOST_AGGRESSIVE_COOKIE && !isset($_COOKIE[BOOST_COOKIE]) && $uid != 0) { boost_set_cookie($uid); } } // Disable all caches when nocache is set if (isset($_GET['nocache'])) { $GLOBALS['conf']['cache'] = CACHE_DISABLED; $GLOBALS['_boost_cache_this'] = FALSE; return; } // Make sure this is not a 404 redirect from the htaccesss file $path = explode($base_path, request_uri()); array_shift($path); $path = implode($base_path, $path); $path = explode('?', $path); $path = array_shift($path); if ($path != '' && empty($_REQUEST['q']) && !stristr($path, '.php') && isset($_SERVER['REDIRECT_STATUS']) && $_SERVER['REDIRECT_STATUS'] == 404) { $GLOBALS['conf']['cache'] = CACHE_DISABLED; $GLOBALS['_boost_cache_this'] = FALSE; if (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_init_404'])) { watchdog('boost', '404 received from server via redirect, going to send a 404. Info: !output', array('!output' => boost_print_r($_SERVER, TRUE, TRUE))); } 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('&', '&', 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; } // Redirect is not to the same domain elseif (strcmp($new_parts['host'], $current_parts['host']) != 0) { //watchdog('boost-redirect', 'redirect is not to the same domain' . str_replace(' ', '    ', 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(' ', '    ', 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 http://drupal.org/node/276615 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('&', '&', 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()
%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.
!domains', array( '%nids' => implode(', ', $nodes), '%count' => count($views), '%times' => $number_views, '%hits' => $number_hits, '%flushed' => $flushed, '!domains' => str_replace(' ', '    ', 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'] = ''; } // 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()
Node !nid was flushed resulting in !flushed pages being expired from the cache
data: !data
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()
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()
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(' ', '    ', 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
', 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 . '
'; } else { $output .= $filename . '
Does not exist.
'; } if ($ttl < 0) { $output .= t('Expired: %interval ago
', array('%interval' => format_interval(abs($ttl)))); } else { $output .= t('Expire In: %interval
', array('%interval' => format_interval(abs($ttl)))); } $tim = boost_db_get_cache_age($filename); $output .= t('Cache Age: %time
', array('%time' => format_interval($tim))); $output .= t('Cache Generated: %time seconds
', array('%time' => round($generate, 2))) . ' '; if (user_access('administer site configuration')) { $output .= drupal_get_form('boost_block_flush_form'); } $output .= '
'; } // If page is not cached, let user know. else { $output .= t('This page is being served live 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 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/boost'))); } if (BOOST_HALT_ON_MESSAGES && $drupal_msg != 0) { $output .= t('MESSAGES: %msg
!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'] = '
' . 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() { $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()
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()
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()
!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()
!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 = <<'; 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. */ 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 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/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 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/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()
HTTP Info: !status - !types
Path: !path
Content Container: !callback
Content Type: !type
ID: !id
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 .= "\n"; $string .= " \n"; $string .= " ExpiresDefault A1\n"; $string .= " \n"; $string .= " \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 .= " \n"; $string .= "\n"; } if (BOOST_CACHE_HTML || BOOST_CACHE_XML || BOOST_CACHE_CSS || BOOST_CACHE_JS || BOOST_CACHE_JSON) { $string .= "\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 .= "\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 .= "\n"; $string .= " ForceType text/html\n"; $string .= "\n"; } $files = ''; if (BOOST_CACHE_XML) { $files .= "$_xml|"; if (BOOST_GZIP) { $files .= "$_xml$gz|"; } $files = trim($files, '|'); $string .= "\n"; $string .= " ForceType text/xml\n"; $string .= "\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 .= "\n"; $string .= " ForceType text/javascript\n"; $string .= "\n"; } $files = ''; if (BOOST_CACHE_CSS) { $files .= "$_css|"; if (BOOST_GZIP) { $files .= "$_css$gz|"; } $files = trim($files, '|'); $string .= "\n"; $string .= " ForceType text/css\n"; $string .= "\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 == '')) { $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()
Following files where flushed:
!list', array('!list' => implode('
', $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()
Following files where flushed:
!list

Input:
!input
Files: !files', array( '!list' => implode('
', $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()
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()
Following files where flushed:
!list', array('!list' => implode('
', $list))); } elseif (BOOST_VERBOSE >= 7 && isset($_boost['verbose_option_selected']['boost_verbose_refined'])) { watchdog('boost', 'Debug: boost_cache_expire_all_db()
!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("(\x5B[0-9]\x5D)", ']', $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 = '\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 = '\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, '' . "\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()
child_page_* was not set.
!data
!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()
"!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} SET base_dir = '%s', page_callback = '%s', page_type = '%s', page_id = '%s', child_page_callback = '%s', child_page_type = '%s', child_page_id = '%s', hash_url = '%s', timestamp = '%d' 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()
!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(' ', '    ', 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
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(); $oldfile = $tempfile . 'old'; 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)); } // 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
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); } 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(' $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('