Advertisement
Guest User

nzpost cache Drupal module (example)

a guest
May 15th, 2013
152
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 17.38 KB | None | 0 0
  1. <?php
  2.  
  3. define('NZPOST_CACHE_PER_SESSION', 'X-Varnish-Client-SID');
  4. define('NZPOST_CACHE_PER_ROLES', 'X-Varnish-Client-Roles');
  5. define('NZPOST_CACHE_GLOBAL', 'global');
  6. define('NZPOST_CACHE_AUTHONLY', 'authonly');
  7. define('NZPOST_CACHE_ANONONLY', 'anononly');
  8. define('NZPOST_CACHE_PER_DEFAULTS', 'X-Varnish-Client-ToolPrefs');
  9. define('NZPOST_CACHE_NOCACHE', 'nocache');
  10. define('NZPOST_CACHE_PER_JS', 'X-Varnish-Client-HasJS');
  11. define('NZPOST_CACHE_PER_UA', 'User-Agent');
  12.  
  13. define('NZPOST_CACHE_DEFAULT_PATHS_NOCACHE', "user\r\nuser/*\r\n\r\ncart\r\ncart/*\r\nadmin/*\r\nlogout");
  14. define('NZPOST_CACHE_DEFAULT_PATHS_PERSESSION', '');
  15. define('NZPOST_CACHE_DEFAULT_PATHS_PERROLES', '');
  16. define('NZPOST_CACHE_DEFAULT_PATHS_GLOBAL', '');
  17. define('NZPOST_CACHE_DEFAULT_PATHS_AUTHONLY', '');
  18. define('NZPOST_CACHE_DEFAULT_PATHS_ANONONLY', '');
  19.  
  20. /**
  21.  * Implementation of hook_init()
  22.  */
  23. function nzpost_cache_init() {
  24.   global $user;
  25.  
  26.   $GLOBALS['nzpost_cache_ttl'] = variable_get('nzpost_cache_default_vanish_ttl', 86400);
  27.  
  28.   // If we're on an ESI callback, translate the esi module's cache settings into Vary headers for Varnish    
  29.   if (strpos(request_uri(), '/esi') === 0) { // The ESI module overwrites $_GET['q']
  30.     require_once dirname(__FILE__).'/includes/esi.inc';
  31.     _nzpost_cache_esi_setheaders();
  32.   }
  33.   else {
  34.     // For anything other than ESI, send a fixed Varnish Vary header to vary on whether or not the user is an admin
  35.     drupal_set_header('Vary', 'X-Varnish-Client-IsAdmin', true);
  36.   }
  37.  
  38.   // If the user is an admin, force TTL to 0
  39.   if (_nzpost_cache_is_admin(array_keys($user->roles))) {
  40.     $GLOBALS['nzpost_cache_ttl'] = 0;
  41.   }
  42.  
  43.   // Add into Drupal.settings some per-page settings that may be useful to Javascript
  44.   $page_data = array(
  45.     'nzp_page' => array(
  46.       'path' => str_replace('destination=', '', drupal_get_destination())
  47.     )
  48.   );
  49.   drupal_add_js($page_data, 'setting');
  50.  
  51.   _nzpost_cache_check_varnish();
  52.   _nzpost_cache_check_cookies();
  53. }
  54.  
  55. /**
  56.  * Implementation of hook_exit()
  57.  */
  58. function nzpost_cache_exit() {
  59.   global $user;
  60.  
  61.   if (function_exists('drupal_get_headers')) {
  62.     $existing_headers = drupal_get_headers();
  63.  
  64.     if (strpos($existing_headers, 'content-type: text/html') !== false) {
  65.       // Most of the time we want to allow back button behaviour to work, except when we don't
  66.       // We accomplish this by using no-store when back should be broken, and omitting it when it shouldn't be.
  67.       $block_paths = variable_get('nzpost_cache_force_back_refresh', array(
  68.         'cart',
  69.         'cart/checkout',
  70.         'cart/checkout/review'
  71.       ));
  72.       if (!in_array($_GET['q'], $block_paths)) {
  73.         // Don't allow caching, but don't go as far as no-cache, which IE will take too literally for back button support to work.
  74.         drupal_set_header('Cache-Control', 'private,max-age=0');
  75.  
  76.         // max-age=0 still allows the browser to serve it from the cache within the session (in IE). We set
  77.         // an additional Expires -1 header to bypass this behaviour and always revalidate except on back button.
  78.         drupal_set_header('Expires', '-1');
  79.       }
  80.       else {
  81.         drupal_set_header('Cache-Control', 'no-cache, no-store, pre-check=0, post-check=0, must-revalidate');
  82.       }
  83.     }
  84.  
  85.     // Set up Varnish headers (note Vary headers have probably already been set)    
  86.     if (!isset($GLOBALS['nzpost_cache_mode_set'])) {
  87.       // No module has chosen a particular cache mode for this page.
  88.       // First we check through the path patterns in the variables for a match, but if that fails,
  89.       // we fall back to "anononly" caching, similar to what Boost gave us before.
  90.       if (isset($_GET['q'])) {
  91.         $path = drupal_get_path_alias($_GET['q']);
  92.         foreach (array(
  93.           NZPOST_CACHE_NOCACHE => variable_get('nzpost_cache_varnish_paths_nocache', NZPOST_CACHE_DEFAULT_PATHS_NOCACHE),
  94.           NZPOST_CACHE_PER_SESSION => variable_get('nzpost_cache_varnish_paths_persession', NZPOST_CACHE_DEFAULT_PATHS_PERSESSION),
  95.           NZPOST_CACHE_PER_ROLES => variable_get('nzpost_cache_varnish_paths_perroles', NZPOST_CACHE_DEFAULT_PATHS_PERROLES),
  96.           NZPOST_CACHE_GLOBAL => variable_get('nzpost_cache_varnish_paths_global', NZPOST_CACHE_DEFAULT_PATHS_GLOBAL),
  97.           NZPOST_CACHE_AUTHONLY => variable_get('nzpost_cache_varnish_paths_nocache', NZPOST_CACHE_DEFAULT_PATHS_AUTHONLY),
  98.           NZPOST_CACHE_ANONONLY => variable_get('nzpost_cache_varnish_paths_anononly', NZPOST_CACHE_DEFAULT_PATHS_ANONONLY)
  99.         ) as $set_mode => $variable_val) {
  100.           // Compile to a regex and match the current path
  101.           if (drupal_match_path($_GET['q'], $variable_val) || ($path && drupal_match_path($path, $variable_val))) {
  102.             // Force this mode
  103.             $intended_mode = $set_mode;
  104.             drupal_alter('nzpost_cache_defaultmode', $set_mode, $_GET['q'], $path);
  105.             if ($intended_mode == $set_mode) {
  106.               //d('[nzpost_cache] Matched page path pattern, setting mode to '.$set_mode.' for '.$_GET['q'].($path ? ' ('.$path.')' : ''));
  107.             }
  108.             else {
  109.               //d('[nzpost_cache] Matched page path pattern but overridden by alter hook, setting mode to '.$set_mode.' for '.$_GET['q'].($path ? ' ('.$path.')' : ''));
  110.             }
  111.             nzpost_cache_setmode($set_mode);
  112.             break;
  113.           }
  114.         }
  115.       }
  116.  
  117.       if (!isset($GLOBALS['nzpost_cache_mode_set'])) {
  118.         // Apply the anononly default if the content type is text/html, otherwise nocache
  119.         if (strpos($existing_headers, 'content-type: text/html') !== false) {
  120.           //d('Fallback to ANONONLY');
  121.           nzpost_cache_setmode(NZPOST_CACHE_ANONONLY);
  122.         }
  123.         else {
  124.           //d('Fallback to NOCACHE');
  125.           nzpost_cache_setmode(NZPOST_CACHE_NOCACHE);
  126.         }
  127.       }
  128.     }
  129.  
  130.     if (!empty($GLOBALS['nzpost_cache_esi_enabled'])) {
  131.       drupal_set_header('X-Varnish-ESI', 'on');
  132.     }
  133.     if (isset($GLOBALS['nzpost_cache_ttl'])) {
  134.       _nzpost_cache_set_ttl_header($GLOBALS['nzpost_cache_ttl']);
  135.     }
  136.     else {
  137.       _nzpost_cache_set_ttl_header(0);
  138.     }
  139.   }
  140.  
  141.   if (isset($_COOKIE['NO_CACHE']) && empty($_SESSION['messages']) && isset($_GET['q']) && strpos($_GET['q'], 'esi/') !== 0) {
  142.     // This page view is obviously not cached and the user used cookie_cache_bypass_adv's cookie to bust the cache
  143.     // for this load. Now that we've flushed out any messages they may want to see, we can actually remove the cookie
  144.     // and send them on their merry way back to cacheland.
  145.     global $cookie_domain;
  146.     setcookie('NO_CACHE', '', 1, '/', $cookie_domain);
  147.     nzpost_cache_setmode(NZPOST_CACHE_NOCACHE); // not really required, but for neatness.
  148.   }
  149. }
  150.  
  151. /**
  152.  * Courtesy function for setting the X-VARNISH-TTL header.
  153.  */
  154. function _nzpost_cache_set_ttl_header($ttl) {
  155.   if ($ttl > 0) {
  156.     drupal_set_header('X-VARNISH-TTL', $ttl, false);
  157.   }
  158.   else {
  159.     // Pressflow refuses to send a header set to 0, so we set it to -1 instead
  160.     drupal_set_header('X-VARNISH-TTL', -1, false);
  161.   }
  162. }
  163.  
  164. /**
  165.  * Implementation of hook_esi_roles_hash_alter()
  166.  */
  167. function nzpost_cache_esi_roles_hash_alter(&$hash, $included_rids, $rids, $seed) {
  168.   // For identification of admin users by Varnish, we append a special "admin bit" on the end of the roles hash
  169.   // that our Varnish config knows to look for (and translates into X-Varnish-Client-IsAdmin)
  170.   if (_nzpost_cache_is_admin($rids)) {
  171.     //d('Detected admin role. Setting admin bit in ESI roles hash');
  172.     $hash .= 'ADMIN';
  173.   }
  174. }
  175.  
  176. /**
  177.  * Implementation of hook_messages_alter() (custom NZ Post hook in template.php)
  178.  */
  179. function nzpost_cache_messages_alter(&$messages, $display) {
  180.   if (!empty($messages)) {
  181.     // Ban caching on this page
  182.     nzpost_cache_setmode(NZPOST_CACHE_NOCACHE, 0);
  183.   }
  184. }
  185.  
  186. /**
  187.  * Returns true if any of the passed role IDs match one of our "admin" roles that may never be cached in Varnish
  188.  *
  189.  * @param array $roles an array of role IDs
  190.  * @return bool true if the user has an admin role
  191.  */
  192. function _nzpost_cache_is_admin($roles) {
  193.   // Certain permissions make them automatically an admin
  194.   if (user_access('access administration menu')) {
  195.     return true;
  196.   }
  197.   return (bool)array_intersect(array_filter(variable_get('nzpost_cache_varnish_nocache_roles', array())), $roles);
  198. }
  199.  
  200. /**
  201.  * Implementation of hook_theme_registry_alter()
  202.  */
  203. function nzpost_cache_theme_registry_alter(&$theme_registry) {
  204.   if (isset($theme_registry['esi_tag'])) {
  205.     $theme_registry['esi_tag']['function'] = 'nzpost_cache_esi_tag_proxy';
  206.   }
  207. }
  208.  
  209. /**
  210.  * Proxy theme function for theme_esi_tag() that sets the ESI flag for Varnish on the way through
  211.  */
  212. function nzpost_cache_esi_tag_proxy($block, $data) {
  213.   $GLOBALS['nzpost_cache_esi_enabled'] = true;
  214.  
  215.   $content = '<!-- esi -->'.theme_esi_tag($block, $data).'<!-- /esi -->'; // esi.theme.inc is still loaded by the theme system automatically
  216.   $base_url = $base_url_backup;
  217.  
  218.   return $content;
  219. }
  220.  
  221. /**
  222.  * Called by other modules, sets the Varnish cache mode for the current page.
  223.  * Optionally sets the TTL at the same time if you want to override the default.
  224.  *
  225.  * You *can* call this multiple times to add two different cache modes at once, but be sure
  226.  * that you know what you're doing. NZPOST_CACHE_GLOBAL + NZPOST_CACHE_PER_DEFAULTS makes sense, for example.
  227.  *
  228.  * Options are:
  229.  *  NZPOST_CACHE_PER_SESSION (cache the current path only for the current user/session, or anonymous)
  230.  *  NZPOST_CACHE_PER_ROLES (cache the current path only for people with the same roles)
  231.  *  NZPOST_CACHE_GLOBAL (cache the current path the same for everyone, logged in or out)
  232.  *  NZPOST_CACHE_AUTHONLY (helper: cache the current path only for authenticated users, not for anonymous)
  233.  *  NZPOST_CACHE_ANONONLY (helper: cache the current path only for anonymous users, not for authenticated)
  234.  *  NZPOST_CACHE_PER_DEFAULTS (additional: make the current path depenedent on A/B tool preference cookies as well as the above)
  235.  *  NZPOST_CACHE_NOCACHE (forces $ttl 0, no Vary)
  236.  *  NZPOST_CACHE_PER_JS (additional: make the current path dependent on the has_js cookie as well as the above)
  237.  *  NZPOST_CACHE_PER_UA (additional: make the current path dependent on the user-agent header as well as the above)
  238.  *
  239.  * @param string $mode one of the above mode constants
  240.  * @param int $ttl (optional) how long to cache the current page for (note this is context-sensitive, so if NZPOST_CACHE_PER_ROLEHASH is the $mode, then this is the TTL for the current user's role set only. This can be handy!
  241.  */
  242. function nzpost_cache_setmode($mode, $ttl = null) {
  243.   // mark that something has picked an explicit mode
  244.   $GLOBALS['nzpost_cache_mode_set'] = true;
  245.  
  246.   if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD') {
  247.     // We don't want to set headers on POST requests etc
  248.     $GLOBALS['nzpost_cache_ttl'] = 0;
  249.     return;
  250.   }
  251.  
  252.   if ($mode == NZPOST_CACHE_GLOBAL) {
  253.     // Global cache, do nothing.
  254.   }
  255.   else if (strpos($mode, 'X-Varnish-Client') === 0) {
  256.     // This is a Vary header itself
  257.     $GLOBALS['nzpost_cache_varys'][] = $mode;
  258.   }
  259.   else if ($mode == 'authonly') {
  260.     // This is an alias for PER_ROLEHASH with TTL set 0 for anonymous users
  261.     if (user_is_anonymous()) {
  262.       return nzpost_cache_setmode(NZPOST_CACHE_PER_ROLES, 0);
  263.     }
  264.     else {
  265.       if (!$ttl) {
  266.         $ttl = variable_get('nzpost_cache_default_vanish_ttl', 86400);
  267.       }
  268.       return nzpost_cache_setmode(NZPOST_CACHE_PER_ROLES, $ttl);
  269.     }
  270.   }
  271.   else if ($mode == 'anononly') {
  272.     // This is an alias for PER_ROLEHASH with TTL set 0 for authenticated users
  273.     if (user_is_anonymous()) {
  274.       if (!$ttl) {
  275.         $ttl = variable_get('nzpost_cache_default_vanish_ttl', 86400);
  276.       }
  277.       return nzpost_cache_setmode(NZPOST_CACHE_PER_ROLES, $ttl);
  278.     }
  279.     else {
  280.       return nzpost_cache_setmode(NZPOST_CACHE_PER_ROLES, 0);
  281.     }
  282.   }
  283.   else if ($mode == 'nocache') {
  284.     $ttl = 0;
  285.   }
  286.  
  287.   if ($ttl !== null) {
  288.     $GLOBALS['nzpost_cache_ttl'] = $ttl;
  289.     _nzpost_cache_set_ttl_header($ttl);
  290.   }
  291.  
  292.   if ($mode != NZPOST_CACHE_GLOBAL && $mode != NZPOST_CACHE_NOCACHE) {
  293.     drupal_set_header('Vary', $mode, true); // in append mode
  294.   }
  295.  
  296.   $path = isset($_GET['q']) ? $_GET['q'] : '';
  297.   //d('[nzpost_cache] Varnish set to mode '.$mode.', TTL is now '.$GLOBALS['nzpost_cache_ttl'].' ('.$path.')');
  298. }
  299.  
  300. /**
  301.  * Checks incoming variables from Varnish to make sure things are set up OK.
  302.  */
  303. function _nzpost_cache_check_varnish() {
  304.   global $conf, $user;
  305.  
  306.   // Don't interfere with Drush etc! This causes problems when theme cache is cleared in CLI.
  307.   if (PHP_SAPI == 'cli') return;
  308.  
  309.   if (!empty($_SERVER['HTTP_X_VARNISH']) && empty($_SERVER['HTTP_X_MATCHED_SITE'])) {
  310.     // If X-Matched-Site isn't set, Varnish will be in pipe mode and won't parse out ESIs. Warn.
  311.     watchdog('nzpost_cache', 'Varnish VCL is not configured to handle this domain (@domain), check nzpost_local.vcl', array('@domain' => $_SERVER['HTTP_HOST']), WATCHDOG_CRITICAL);
  312.     if (user_access('administer site configuration')) {
  313.       drupal_set_message(t('Varnish VCL is not configured to handle this domain. Expect strangeness.'), 'error', false);
  314.     }    
  315.   }
  316.   else if ($user->uid && ((!isset($_SERVER['HTTP_X_SESSION_NAME']) || $_SERVER['HTTP_X_SESSION_NAME'] != session_name()) || (!isset($_SERVER['HTTP_X_VARNISH_CLIENT_SID']) || $_SERVER['HTTP_X_VARNISH_CLIENT_SID'] != session_id()))) {
  317.     // If X-Matched-Site isn't set, Varnish will be in pipe mode and won't parse out ESIs. Warn.
  318.     watchdog('nzpost_cache', 'Varnish VCL is not extracting correct session information. Check session cookie name matches nzpost_local.vcl. SN: @sn, VSN: @vsn, SID: @sid, VSID: @vsid', array('@sn' => session_name(), '@vsn' => $_SERVER['HTTP_X_SESSION_NAME'], '@sid' => session_id(), '@vsid' => $_SERVER['HTTP_X_VARNISH_CLIENT_SID']), WATCHDOG_CRITICAL);
  319.     if (user_access('administer site configuration')) {
  320.       drupal_set_message(t('Varnish VCL is not extracting your correct session information (name,ID) from requests. Expect strangeness.'), 'error', false);
  321.     }
  322.   }
  323. }
  324.  
  325.  
  326. /**
  327.  * Implementation of hook_form_alter()
  328.  */
  329. function nzpost_cache_form_alter(&$form, &$form_state, $form_id) {
  330.   // Attach an #after_build to every form so we can detect whether form tokens are in use
  331.   if (!isset($form['#after_build'])) {
  332.     $form['#after_build'] = array();
  333.   }
  334.   $form['#after_build'][] = '_nzpost_cache_form_afterbuild';
  335. }
  336.  
  337. /**
  338.  * An #after_build attached to every form that checks if form tokens are in use. If so,
  339.  * the page the form is on is not cacheable to prevent validation errors.
  340.  * See Fog#3963 for background.
  341.  */
  342. function _nzpost_cache_form_afterbuild($form) {
  343.   if (!empty($form['form_token'])) {
  344.     //d('[nzpost_cache] Disabling caching for the current page because a form on it ('.$form['form_id']['#value'].') has uncacheable form tokens (form_token).');
  345.     nzpost_cache_setmode(NZPOST_CACHE_NOCACHE);
  346.   }
  347.   return $form;
  348. }
  349.  
  350. /**
  351.  * Does some work that the ESI module doesn't, and ensures users have the appropriate roles cookie.
  352.  * This is to prevent ugly situations where a user has the wrong roles cookie, and caches a per-role ESI
  353.  * or page with content for the wrong roles, which then gets shown for other users who actually have the roles.
  354.  * A common example is the top-right login links, where the "authenticated user" version of the links are cached
  355.  * by someone who is actually logged out, but still somehow has the authenticated user role hash. Other authenticated
  356.  * users will then see the "Login" and "Register" links, even though they're logged in.
  357.  */
  358. function _nzpost_cache_check_cookies() {
  359.   global $user;
  360.   $roles_cookie_name = 'R'.session_name();
  361.   if ($user->uid == 0 && isset($_COOKIE[$roles_cookie_name])) {
  362.     // ESI module doesn't catch this case - eg. session cookie deleted manually etc
  363.     // Kill the roles cookie.
  364.     if (function_exists('drupal_alter')) {
  365.       $edit = array();
  366.       esi_user('logout', $edit, $user);
  367.     }
  368.     d('Removed wrong roles cookie');
  369.     watchdog('nzpost_cache', "Killed roles cookie for user who shouldn't have one (is anon)", array(), WATCHDOG_ERROR);
  370.     nzpost_cache_setmode(NZPOST_CACHE_NOCACHE);
  371.   }
  372.   else if (isset($_COOKIE[$roles_cookie_name])) {
  373.     // Perform a quick check to see if the user has the correct roles hash. Maybe we can remove this if we find out why it happens!
  374.     if (function_exists('drupal_alter')) {
  375.       module_load_include('inc', 'esi');
  376.       $correct_hash = _esi__get_roles_hash();
  377.       if ($_COOKIE[$roles_cookie_name] != $correct_hash) {
  378.         $edit = array();
  379.         esi_user('login', $edit, $user);
  380.         nzpost_cache_setmode(NZPOST_CACHE_NOCACHE);
  381.         d('Rebuilt roles cookie: '.$_COOKIE[$roles_cookie_name].' != '.$correct_hash);
  382.         watchdog('nzpost_cache', 'Rebuilt roles cookie for user who had the wrong roles hash cookie', array(), WATCHDOG_ERROR);
  383.       }
  384.     }
  385.   }
  386.   else if ($user->uid > 0 && !isset($_COOKIE[$roles_cookie_name])) {
  387.     // esi_init() will actually set them the cookie, but we need to make sure that the page isn't cached too
  388.     nzpost_cache_setmode(NZPOST_CACHE_NOCACHE);
  389.   }
  390. }
  391.  
  392. /**
  393.  * Implementation of hook_file_download()
  394.  */
  395. function nzpost_error_file_download($filepath) {
  396.   // Use this hook to add a no-cache header to the download (see nginx_accel_redirect module)
  397.   return array(
  398.     'X-VARNISH-TTL' => 0
  399.   );
  400. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement