expire.module in Cache Expiration 6
Same filename and directory in other branches
Provides logic for page cache expiration
File
expire.moduleView source
<?php
/**
* @file
* Provides logic for page cache expiration
*/
// Defaults used if variable_get is not set.
define('EXPIRE_FLUSH_NODE_TERMS', TRUE);
define('EXPIRE_FLUSH_MENU_ITEMS', 1);
define('EXPIRE_FLUSH_CCK_REFERENCES', TRUE);
define('EXPIRE_FLUSH_FRONT', TRUE);
define('EXPIRE_INCLUDE_BASE_URL', TRUE);
/**
* Implementation of hook_menu().
*/
function expire_menu() {
$items = array();
$items['admin/settings/performance/default'] = array(
'title' => 'Performance',
'type' => MENU_DEFAULT_LOCAL_TASK,
'file path' => drupal_get_path('module', 'system'),
);
$items['admin/settings/performance/expire'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'Cache Expiration',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'expire_admin_settings_form',
),
'access arguments' => array(
'administer site configuration',
),
'file path' => drupal_get_path('module', 'expire'),
'file' => 'expire.admin.inc',
'weight' => 1,
);
return $items;
}
/**
* Implementation of hook_comment().
*
* Acts on comment modification.
*/
function expire_comment($comment, $op) {
// Convert array to object
if (is_array($comment)) {
$comment = (object) $comment;
}
// Return if no node id is attached to the comment.
if (empty($comment->nid)) {
return;
}
// Select which comment opperations require a cache flush.
$cases = array(
'insert',
'update',
'publish',
'unpublish',
'delete',
);
// Expire the relevant node page from the static page cache to prevent serving stale content.
if (in_array($op, $cases)) {
$node = node_load($comment->nid);
if ($node) {
expire_node($node);
}
}
}
/**
* Implementation of hook_nodeapi().
*
* Acts on nodes defined by other modules.
*/
function expire_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
// Return if no node id is attached to the node object.
if (empty($node->nid)) {
return;
}
// Select which node opperations require a cache flush.
$cases = array(
'insert',
'update',
'delete',
'delete revision',
);
// Expire the relevant node page from the static page cache to prevent serving stale content.
if (in_array($op, $cases)) {
expire_node($node);
}
}
/**
* Implementation of hook_user().
*
* Acts on user account actions: expire the relevant user page from the static
* page cache to prevent serving stale content.
*/
function expire_user($op, &$edit, &$account, $category = NULL) {
// Return if no user id is attached to the user object.
if (empty($account->uid)) {
return;
}
// Select which user opperations require a cache flush.
$cases = array(
'insert',
'update',
'after update',
);
// Expire the relevant user page from the static page cache to prevent serving stale content.
if (in_array($op, $cases)) {
$paths[] = 'user/' . $account->uid;
$flushed = expire_cache_derivative($paths, $account);
watchdog('expire', 'User !op for !uid resulted in !flushed pages being expired from the cache', array(
'!op' => $op,
'!uid' => $account->uid,
'!flushed' => $flushed,
));
}
}
/**
* Implementation of hook_votingapi_insert().
*
* @param $votes
* array of votes
*/
function expire_votingapi_insert($votes) {
_expire_votingapi($votes);
}
/**
* Implementation of hook_votingapi_delete().
*
* @param $votes
* array of votes
*/
function expire_votingapi_delete($votes) {
_expire_votingapi($votes);
}
/**
* Common expiry logic for votingapi.
*/
function _expire_votingapi($votes) {
foreach ($votes as $vote) {
if ($vote['content_type'] == 'comment') {
$nid = db_result(db_query("SELECT nid FROM {comments} WHERE cid = %d", $vote['content_id']));
if (is_numeric($nid)) {
$node = node_load($nid);
if ($node) {
expire_node($node);
}
}
}
if ($vote['content_type'] == 'node') {
$node = node_load($vote['content_id']);
if ($node) {
expire_node($node);
}
}
}
}
/**
* Expires a node from the cache; including related pages.
*
* Expires front page if promoted, taxonomy terms,
*
* @param $node
* node object
*/
function expire_node(&$node) {
$paths = array();
// Check node object
if (empty($node->nid)) {
return FALSE;
}
// Expire this node
$paths['node'] = 'node/' . $node->nid;
// If promoted to front page, expire front page
if (variable_get('expire_flush_front', EXPIRE_FLUSH_FRONT) && $node->promote == 1) {
$paths['front'] = '<front>';
}
// Get taxonomy terms and flush
if (module_exists('taxonomy') && variable_get('expire_flush_node_terms', EXPIRE_FLUSH_NODE_TERMS)) {
// Get old terms from DB
$tids = expire_taxonomy_node_get_tids($node->nid);
// Get old terms from static variable
$terms = taxonomy_node_get_terms($node);
if (!empty($terms)) {
foreach ($terms as $term) {
$tids[$term->tid] = $term->tid;
}
}
// 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;
}
}
}
}
$filenames = array();
foreach ($tids as $tid) {
if (is_numeric($tid)) {
$term = taxonomy_get_term($tid);
$paths['term' . $tid] = taxonomy_term_path($term);
}
}
}
// Get menu and flush related items in the menu.
if (variable_get('expire_flush_menu_items', EXPIRE_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 (variable_get('expire_flush_menu_items', EXPIRE_FLUSH_MENU_ITEMS) == 1) {
$links = expire_get_menu_structure($menu, FALSE, 'node/' . $node->nid, NULL, $tempa, $tempb);
}
elseif (variable_get('expire_flush_menu_items', EXPIRE_FLUSH_MENU_ITEMS) == 2) {
$links = expire_get_menu_structure($menu, NULL, NULL, NULL, $tempa, $tempb);
}
unset($tempa);
unset($tempb);
$paths = array_merge($links, $paths);
}
// Get CCK References and flush.
if (variable_get('expire_flush_cck_references', EXPIRE_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) {
if (is_numeric($item['nid'])) {
$paths['reference' . $item['nid']] = 'node/' . $item['nid'];
}
}
// Look for node referers without using nodereferrer
$info = content_database_info($field);
$table = $info['table'];
$column = $info['columns']['nid']['column'];
$results = db_query("SELECT n.nid\n FROM {%s} nr\n INNER JOIN {node} n USING (vid)\n WHERE nr.%s = %d", $table, $column, $node->nid);
while ($nid = db_result($results)) {
if (is_numeric($nid)) {
$paths['referenceparent' . $nid] = 'node/' . $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'])) {
$paths['referrer' . $nid['nid']] = 'node/' . $nid['nid'];
}
}
}
}
// Flush array of paths
if (!empty($paths)) {
$flushed = expire_cache_derivative($paths, $node);
watchdog('expire', 'Node !nid was flushed resulting in !flushed pages being expired from the cache', array(
'!nid' => $node->nid,
'!flushed' => $flushed,
));
}
}
/**
* 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
*/
function expire_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;
}
}
expire_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 expire_taxonomy_node_get_tids($nid) {
$vid = db_result(db_query("SELECT vid FROM {node} WHERE nid = %d", $nid));
$result = db_query(db_rewrite_sql("SELECT t.tid FROM {term_node} r INNER JOIN {term_data} t ON r.tid = t.tid INNER JOIN {vocabulary} v ON t.vid = v.vid WHERE r.vid = %d ORDER BY v.weight, t.weight, t.name", 't', 'tid'), $vid);
$tids = array();
while ($term = db_result($result)) {
$tids[] = $term;
}
return $tids;
}
/**
* Finds all possible paths/redirects/aliases given the root path.
*
* @param $paths
* Array of current URLs
* @param $node
* node object
*/
function expire_cache_derivative($paths, &$node = NULL) {
global $base_path;
$expire = array();
if (empty($paths)) {
return FALSE;
}
$site_frontpage = variable_get('site_frontpage', 'node');
foreach ($paths as $path) {
// Special front page handling
if ($path == $site_frontpage || $path == '' || $path == '<front>') {
$expire[] = '';
$expire[] = 'rss.xml';
$expire[] = $site_frontpage;
}
// Add given path
if ($path != '<front>') {
$expire[] = $path;
}
// Path alias
$path_alias = url($path, array(
'absolute' => FALSE,
));
// Remove the base path
$expire[] = substr($path_alias, strlen($base_path));
// Path redirects
if (module_exists('path_redirect')) {
$path_redirects = expire_path_redirect_load(array(
'redirect' => $path,
));
if (isset($path_redirects)) {
foreach ($path_redirects as $path_redirect) {
if (!empty($path_redirect['redirect'])) {
$expire[] = $path_redirect['redirect'];
}
}
}
}
}
// Allow other modules to modify the list prior to expiring
drupal_alter('expire_cache', $expire, $node, $paths);
// Expire cached files
if (empty($expire)) {
return FALSE;
}
$expire = array_unique($expire);
// Add on the url to these paths
$urls = array();
global $base_url;
if (variable_get('expire_include_base_url', EXPIRE_INCLUDE_BASE_URL)) {
foreach (expire_get_base_urls($node) as $domain_id) {
foreach ($domain_id as $base) {
foreach ($expire as $path) {
$urls[] = $base . $path;
}
}
}
}
else {
$urls = $expire;
}
// hook_expire_cache
$modules = module_implements('expire_cache');
foreach ($modules as $module) {
module_invoke($module, 'expire_cache', $urls);
}
watchdog('expire', 'Input: !paths <br /> Output: !urls <br /> Modules Using hook_expire_cache(): !modules', array(
'!paths' => expire_print_r($paths),
'!urls' => expire_print_r($urls),
'!modules' => expire_print_r($modules),
));
return count($urls);
}
/**
* Retrieve a specific URL redirect from the database.
* http://drupal.org/node/451790
*
* @param $where
* Array containing 'redirect' => $path
*/
function expire_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;
}
}
/**
* Simple print_r to html function
*
* @param $data
*
* @return string
* print_r contents in nicely formatted html
*/
function expire_print_r($data) {
return str_replace(' ', ' ', nl2br(htmlentities(print_r($data, TRUE))));
}
/**
* Get all base url's where this node can appear. Domain access support.
*
* @param $node
* node object
* @return array
* array(0 => array($base_url . '/'))
*/
function expire_get_base_urls(&$node) {
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/user object
foreach ($node->domains as $key => $domain_id) {
if ($key != $domain_id) {
continue;
}
$domains[$domain_id] = $domain_id;
}
// Get domains from database
foreach (expire_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);
if ($domain['valid'] == 1) {
if (isset($domain['path'])) {
$base_urls[$domain_id][] = $domain['path'];
}
if (is_array($domain['aliases'])) {
foreach ($domain['aliases'] as $alias) {
if ($alias['redirect'] != 1) {
$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;
}
/**
* Get domains the node is currently published to
*
* @param $node
* node object
* @return array
* array('$gid' => $gid)
*/
function expire_get_domains(&$node) {
$domains = array();
if ($node->nid) {
$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;
}
}
elseif ($node->mail && $node->name) {
$result = db_query("SELECT domain_id FROM {domain_editor} WHERE uid = %d", $node->uid);
while ($row = db_fetch_array($result)) {
$gid = $row['domain_id'];
$domains[$gid] = $gid;
}
}
return $domains;
}
function expire_normal_path_check($path) {
$original_map = arg(NULL, $path);
$parts = array_slice($original_map, 0, MENU_MAX_PARTS);
list($ancestors, $placeholders) = menu_get_ancestors($parts);
$router_item = db_fetch_array(db_query_range('SELECT path FROM {menu_router} WHERE path IN (' . implode(',', $placeholders) . ') ORDER BY fit DESC', $ancestors, 0, 1));
return $router_item;
}
Functions
Name | Description |
---|---|
expire_cache_derivative | Finds all possible paths/redirects/aliases given the root path. |
expire_comment | Implementation of hook_comment(). |
expire_get_base_urls | Get all base url's where this node can appear. Domain access support. |
expire_get_domains | Get domains the node is currently published to |
expire_get_menu_structure | Finds parent, siblings and children of the menu item. UGLY CODE... |
expire_menu | Implementation of hook_menu(). |
expire_node | Expires a node from the cache; including related pages. |
expire_nodeapi | Implementation of hook_nodeapi(). |
expire_normal_path_check | |
expire_path_redirect_load | Retrieve a specific URL redirect from the database. http://drupal.org/node/451790 |
expire_print_r | Simple print_r to html function |
expire_taxonomy_node_get_tids | Return taxonomy terms given a nid. |
expire_user | Implementation of hook_user(). |
expire_votingapi_delete | Implementation of hook_votingapi_delete(). |
expire_votingapi_insert | Implementation of hook_votingapi_insert(). |
_expire_votingapi | Common expiry logic for votingapi. |