Technical notes about "anonymous" flaggings when page caching is turned on

Starting with Flag 2.x, anonymous users too can flag items.

This document explains some technical details that could be useful for programmers wishing to add custom code to their site, and for general "troubleshooting".

Page Caching

A special case of interest, and the subject of this document, is the scenario where page caching is turned on: this poses a challenge to Flag: if the page is cached, and so is shared by all users, how can the page reflect the state of the user viewing it?

The answer is: "with cookies and a bit of JavaScript magic." The flaggings of anonymous users are recorded (when page caching is in effect) in a cookie (as well as in the database). JavaScript code that's run on the page then looks into this cookie(s) and updates the links on the page to reflect their true state.

The exact mechanism is described here.

On the other hand, when you ask Flag (via Views) to display a list of flagged content, Flag doesn't utilize the aforementioned cookie: a normal database lookup is used. The consequence is that if you put a view on you page, and this view involves a flag with anonymous access, Flag will turn off caching for this page.

A Tip

Before we continue the discussion, here's a debugging tip. If you want to know whether the page you're browsing is cached or not, add the following to your theme's 'page.tpl.php':

  if ($user->uid == 0) {
    print 'Page generated at: ' . date('H:i:s', time());
  }

If the time displayed changes when you refresh the page, that means that the page isn't cached. (Either because you asked Drupal not to turn caching on; or because you placed a view on this page that involves a flag with anonymous access.)

A use-case: Displaying a "Your basket" block

(But see the simpler "Alternative: Having a 'My basket (5)' link" section, later on this page.)

Say we have an e-commerce site, and we let users "pick" nodes (by flagging them). A block, on all the pages on our site, will then show our flagged items.

This is seemingly an easy task. Indeed. But suppose we want to use page caching -- will we need to make special arrangements? Yes, because normally the block will turn off the page caching. That's because our Views support will automatically turn off the page caching if the flag for which we list flagged content allows use by anonymous users. (This is not a bug: it's a feature, because otherwise anonymous users will see the (stale) state of some other anonymous user).

So, how can we show a block on our page without actually showing it? Huh, a contradiction!

The solution is to not let Drupal show the block (in ?q=admin/build/block). Instead, we'll show the block through JavaScript: we'll pull it from the server on the page's 'onload' event and inject it into the page. Here's discussion to get you started (make sure to read the comments that follow on that page).

Here's some pseudo JavaScript code to do this:


function refresh_block() {

  // Fetch a block/view from the server and inject it into the page.

  // (Alternatively: unhide, or refresh, an IFRAME.)

}

$(document).bind('flagGlobalAfterLinkUpdate', function(event, data) {

  // Refresh the block whenever a JavaScript flag link is clicked.

  if (data.flagName == 'basket') {
    refresh_block();
  }
}

$(document).ready(function() {

  // We also need to fetch the block on the page's 'onload'.
  //
  // IMPORTANT: Note that we only do this when the we know that the user
  // has flagged some content. Because if we fetch the block unconditionally,
  // we cancel out the benefit of the page cache.

  if (flag's cookie isn't empty) {
    refresh_block() 
  }
});

Somebody has contributed a more complete code to do this: See the sub-page(s) of this document.

But do I have to use custom code?

Let's make it clear: If you don't care about page caching, you're in the wrong section. If you don't care about page caching, then Flag already does everything for you (and you may also use Views Flag Refresh for pyrotechnics, but this isn't required).

Alternative: Having a "My basket (5)" link

The solution described above, of a block displaying your flagged items, is nice, but it's relatively complicated. A much simpler solution is to merely display, to anonymous users, a "My basket (5)" link that will lead to a dedicated page. Of course, the "5" is the number of items that have been flagged. You'll have to inspect the Flag cookie to find out this number. (This idea is courtesy of quicksketch.)

You may also use a "graceful degradation" strategy: For logged in users you will display a complete block listing the flagged content, but for anonymous users you will display just the link.

Sometimes you wish to show the "Flag this!" field in a view, but without limiting the list to only the flagged items. In this case make sure, when you bring in the Flag relationship, to pick "by Any user", instead of "by Current user", or else Flag will turn off the page cache. You'll have to turn on the Distinct option to get rid of duplicate rows. This certainly shows that our Views integration isn't perfect. (Incidentally, Flag Vista doesn't have this glitch.)

A more complete code

Here's a more complete code than the "pseudo JavaScript code" presented earlier. It shows how to implement the "Basket block" for cached

Varnish reverse proxy with flag

To use flag with varnish on top of drupal standard page caching they are different point to check.

Guide maintainers

mooffie's picture