Disabling page caching for anonymous visitors who have JavaScript disabled

Events happening in the community are now at Drupal community events on www.drupal.org.
neilt17's picture

Our site (http://en.commtap.org), uses a fair amount of JavaScript to modify how its pages are presented according to a visitor's preferences. Unfortunately it appears that a significant number of our visitors don't have JavaScript enabled in their browsers - and do not have the means to enable it - these computers are in primary schools. In this case, the alternative would be for a visitor's preferences to be processed server side when the page is being created in Drupal. This would mean that we would have to turn page caching off, slowing down the delivery of pages for everyone. The other option could be to pass a visitor's entire set of preferences around on the url - so that Drupal would cache each version - but this would create some very long and unwieldy urls.

One thing I have come up with is to arrange it so that Drupal delivers a fresh uncached page for anonymous visitors who don't have JavaScript enabled, and the cached version for all other anonymous visitors. There does not seem to be a great deal of flexibility in what you can do in controlling whether or not Drupal delivers a cached page in response to a request, however Drupal will always deliver a new uncached page if a message has been set, and I have used this idea to solve this problem.

The following function will force Drupal to deliver the next page as a fresh uncached page if a visitor can accept cookies, but the has_js cookie is not set (anything that doesn't accept cookies will get the cached page just as the more usual anonymous visitor with JavaScript enabled).

<?php
function mytheme_nojs_nocache(&$vars) {
 
 
// Make sure our dummy message never actually appears on the page:
 
if (!empty($vars['messages'])) {
   
$vars['messages'] = str_replace("<div class=\"messages nojs_nocache\">\nnojs_nocache</div>\n", '', $vars['messages']);
  }
 
 
// Check if any cookie is set (i.e. can the browser accept cookies at all?), if non found,
  // try to set one:
 
if (empty($_COOKIE)) {
   
setcookie('mytheme_cookie_test', 1, 0);
    return;
  }
 
 
// If cookies can be set, check that has_js is not set/not true:
 
if (!$_COOKIE['has_js']) {
   
// Set a dummy message (to force Drupal to deliver a new page on the next page request)
    // (we will remove the message from the html before the next page gets sent to the browser):
   
drupal_set_message('nojs_nocache', 'nojs_nocache', FALSE);
  }
}
?>

This function would be called from your theme_preprocess_page(&$vars) function in your theme. The visitor will need to have browsed through at least one generally non-cached page in their session for this to start working - I use the CacheExclude module to achieve this (excluding pages from the cache where options can be selected).

Comments

Could't you do the same in

fabianx's picture

Couldn't you do the same in hook_boot? (Be aware that the boot hook is only enabled for a module when it is newly installed. If you add it afterwards, either disable/enable again or set boot in system table to true.)

hook_boot does

a) always run. (unless aggressive caching is activated)
b) you can do the same in there:

<?php
function custom_boot() {

 
// Check if any cookie is set (i.e. can the browser accept cookies at all?), if non found,
  // try to set one:

 
if (!isset($_COOKIE['custom_cookie_test'])) {
   
setcookie('custom_cookie_test', 1, time() + 31536000); // set cookie for one year
   
return;
  }
 
 
// If cookies can be set, check that has_js is not set/not true:
 
if (!isset($_COOKIE['has_js']) || !$_COOKIE['has_js']) {
   
// unset using of the cache for this page
   
$GLOBALS['conf']['cache'] = FALSE;
  }
}
?>

This will always run (unless you have aggressive caching enabled) and will happily serve cached pages for users with JS enabled and non-cached for those with JS disabled - provided cookies work nicely.

I also optimized that the cookie is set for one year - as there is no need to recheck on each browser session.

Best Wishes,

Fabian

Hook boot won't do it

mikeytown2's picture

The cache object was already gotten at this point so hook_boot doesn't have control over serving a page from the cache
http://api.drupal.org/api/drupal/includes--bootstrap.inc/function/_drupa...
http://api.drupal.org/api/drupal/includes--bootstrap.inc/function/_drupa...

Logic needs to go into settings.php most likely; settings.php trick works with aggressive caching as well. If your looking to do a cache variant based on a cookie value; checkout this thread on mobile themes http://drupal.org/node/361832#comment-4204294

Thank you both

neilt17's picture

Thank you both.

As Mike said, hook_boot gets called too late to be able to control whether or not a page is served from the cache, so I've put the code in settings.php as he suggested:

<?php
// Check if any cookie is set (i.e. can the browser accept cookies at all?), if non found,
// try to set one:
if (!isset($_COOKIE['custom_cookie_test'])) {
 
setcookie('custom_cookie_test', 1, time() + 31536000); // set cookie for one year
}
elseif ((!isset(
$_COOKIE['has_js']) || !$_COOKIE['has_js']) && !custom_always_try_cache()) {
 
// unset using of the cache for this page
 
$conf['cache'] = CACHE_DISABLED;
}
?>

A far better solution than my original all-or-nothing one, as this also allows me to configure some pages to be served from cache for the non-javascript visitors where this is appropriate:

<?php
// This is adapted from drupal_match_path - which isn't available to settings.php:

function custom_always_try_cache() {
 
 
$path = $_GET['q'];
 
// Enter the pages which we should always attempt to serve a cached version of:
  // Enter one page per array entry as Drupal paths.
  // The '*' character is a wildcard. Example paths are 'blog' for the blog page and
  // 'blog/*' for every personal blog. '' is the front page.
 
$always_try_cache_pages = array(
   
'example/*',
  );
 
  if (empty(
$always_try_cache_pages)) {
    return
FALSE;
  }
 
 
$patterns = implode("\n", $always_try_cache_pages);
 
$regex = '/^(' . preg_replace(array('/(\r\n?|\n)/', '/\\*/'), array('|', '.*'), preg_quote($patterns, '/')) . ')$/';
  return
preg_match($regex, $path);
}
?>

Hi, I would like to use this

loparr's picture

Hi,
I would like to use this as well on my page. I put the first snippet inside settings.php and the second one inside template.php but the cache is not disabled for users with javascript turned off. Can you help me? Thank you.

In case you hadn't solved this...

neilt17's picture

If you were testing this by disabling javascript in a browser, did you also clear the cookies - specifically the has_js cookie?

High performance

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: