'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', ); 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 !uid was deleted resulting in !flushed pages being expired from the cache', array( '!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'] = ''; } // 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 FROM {%s} nr INNER JOIN {node} n USING (vid) 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 == '') { $expire[] = ''; $expire[] = 'rss.xml'; $expire[] = $site_frontpage; } // Add given path if ($path != '') { $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) { $expire[] = $path_redirect['path']; } } } } // 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
Output: !urls
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; }