Index: includes/flag_handler_relationships.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/includes/Attic/flag_handler_relationships.inc,v
retrieving revision 1.1.2.9
diff -u -r1.1.2.9 flag_handler_relationships.inc
--- includes/flag_handler_relationships.inc	26 Apr 2009 21:35:12 -0000	1.1.2.9
+++ includes/flag_handler_relationships.inc	27 Oct 2009 05:59:20 -0000
@@ -43,11 +43,16 @@
     if (!$form['flag']['#options']) {
       $form = array(
         'error' => array(
-          '#value' => '<p class="error">' . t('No %type flags exist. You must first <a href="@create-url">create a %type flag</a> before being able to use this relationship type.', array('%type' => $content_type, '@create-url' => url('admin/build/flags'))) . '</p>',
+          '#value' => '<p class="error form-item">' . t('No %type flags exist. You must first <a href="@create-url">create a %type flag</a> before being able to use this relationship type.', array('%type' => $content_type, '@create-url' => url('admin/build/flags'))) . '</p>',
         ),
       );
       $form_state['no flags exist'] = TRUE;
     }
+    if (module_exists('session_api')) {
+      $form['session_warning'] = array(
+        '#value' => '<p class="warning form-item">' . t('<strong>Warning</strong>: Adding this relationship for any flag that contains <strong>anonymous flagging access</strong> will disable page caching for anonymous users when this view is executed. It is recommended to create a dedicated page for views containing anonymous user data.', array('%type' => $content_type, '@create-url' => url('admin/build/flags'))) . '</p>',
+      );
+    }
   }
 
   function options_validate($form, &$form_state) {
@@ -81,6 +86,17 @@
         'value' => '***CURRENT_USER***',
         'numeric' => TRUE,
       );
+      if (array_search(DRUPAL_ANONYMOUS_RID, $flag->roles['flag']) !== FALSE) {
+        // Disable page caching for anonymous users.
+        $GLOBALS['conf']['cache'] = CACHE_DISABLED;
+
+        // Add in the SID from Session API for anonymous users.
+        $this->definition['extra'][] = array(
+          'field' => 'sid',
+          'value' => flag_get_sid(),
+          'numeric' => TRUE,
+        );
+      }
     }
     parent::query();
   }
Index: includes/flag.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/includes/Attic/flag.admin.inc,v
retrieving revision 1.1.4.2.2.6
diff -u -r1.1.4.2.2.6 flag.admin.inc
--- includes/flag.admin.inc	28 Sep 2009 02:05:06 -0000	1.1.4.2.2.6
+++ includes/flag.admin.inc	27 Oct 2009 05:59:19 -0000
@@ -324,19 +324,26 @@
   $form['access']['roles'] = array(
     '#title' => t('Roles that may use this flag'),
     '#access' => empty($flag->locked['roles']),
-    '#description' => t('Users may only unflag content if they have access to flag the content initially. Checking <em>authenticated user</em> will allow access for all logged-in users. Anonymous users may not flag content.'),
+    '#description' => t('Users may only unflag content if they have access to flag the content initially. Checking <em>authenticated user</em> will allow access for all logged-in users.'),
     '#theme' => 'flag_form_roles',
     '#weight' => -2,
   );
+  if (module_exists('session_api')) {
+    $form['access']['roles']['#description'] .= ' ' . t('Support for anonymous users is being provided by <a href="http://drupal.org/project/session_api">Session API</a>.');
+  }
+  else {
+    $form['access']['roles']['#description'] .= ' ' . t('Anonymous users may flag content if the <a href="http://drupal.org/project/session_api">Session API</a> module is installed.');
+  }
+
   $form['access']['roles']['flag'] = array(
     '#type' => 'checkboxes',
-    '#options' => user_roles(TRUE),
+    '#options' => user_roles(!module_exists('session_api')),
     '#default_value' => $flag->roles['flag'],
     '#parents' => array('roles', 'flag'),
   );
   $form['access']['roles']['unflag'] = array(
     '#type' => 'checkboxes',
-    '#options' => user_roles(TRUE),
+    '#options' => user_roles(!module_exists('session_api')),
     '#default_value' => $flag->roles['unflag'],
     '#parents' => array('roles', 'unflag'),
   );
Index: includes/flag_handler_field_ops.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/includes/Attic/flag_handler_field_ops.inc,v
retrieving revision 1.1.2.5.2.1
diff -u -r1.1.2.5.2.1 flag_handler_field_ops.inc
--- includes/flag_handler_field_ops.inc	14 Sep 2009 11:46:40 -0000	1.1.2.5.2.1
+++ includes/flag_handler_field_ops.inc	27 Oct 2009 05:59:19 -0000
@@ -81,6 +81,11 @@
         'value' => '***CURRENT_USER***',
         'numeric' => TRUE,
       );
+      $join->extra[] = array(
+        'field' => 'sid',
+        'value' => flag_get_sid(),
+        'numeric' => TRUE,
+      );
     }
     $flag_table = $this->query->add_table('flag_content', $parent, $join);
     $this->aliases['is_flagged'] = $this->query->add_field($flag_table, 'content_id');
Index: flag.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.install,v
retrieving revision 1.2.2.32.2.6
diff -u -r1.2.2.32.2.6 flag.install
--- flag.install	26 Oct 2009 21:47:41 -0000	1.2.2.32.2.6
+++ flag.install	27 Oct 2009 05:59:19 -0000
@@ -86,17 +86,38 @@
   $t = get_t();
   if ($phase == 'install') {
     if (!defined('MAINTENANCE_MODE') && _flag_flag_content_installed()) {
-      $requirements['flag_content_clash']['title'] = $t('Flag');
-      $requirements['flag_content_clash']['severity'] = REQUIREMENT_ERROR;
-      $requirements['flag_content_clash']['description'] = _flag_flag_content_message();
+      $requirements['flag_content_clash'] = array(
+        'title' => $t('Flag'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => _flag_flag_content_message(),
+      );
     }
   }
 
-  if ($phase == 'runtime' && module_exists('translation') && !module_exists('translation_helpers')) {
-    $requirements['flag_translation']['title'] = $t('Flag');
-    $requirements['flag_translation']['severity'] = REQUIREMENT_ERROR;
-    $requirements['flag_translation']['description'] = $t('To have the flag module work with translations, you need to install and enable the <a href="http://drupal.org/project/translation_helpers">Translation helpers</a> module.');
-    $requirements['flag_translation']['value'] = $t('Translation helpers module not found.');
+  if ($phase == 'runtime') {
+    if (module_exists('translation') && !module_exists('translation_helpers')) {
+      $requirements['flag_translation'] = array(
+        'title' => $t('Flag'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => $t('To have the flag module work with translations, you need to install and enable the <a href="http://drupal.org/project/translation_helpers">Translation helpers</a> module.'),
+        'value' => $t('Translation helpers module not found.'),
+      );
+    }
+    if (module_exists('session_api')) {
+      if (file_exists('./robots.txt')) {
+        $flag_path = url('flag') . '/';
+        $robots_string = 'Disallow: ' . $flag_path;
+        $contents = file_get_contents('./robots.txt');
+        if (strpos($contents, $robots_string) === FALSE) {
+          $requirements['flag_robots'] = array(
+            'title' => $t('Flag robots.txt problem'),
+            'severity' => REQUIREMENT_WARNING,
+            'description' => $t('Flag module may currently be used with anonymous users, however the robots.txt file does not exlude the "@flag-path" path, which may cause search engines to randomly flag and unflag content when they index the site. It is highly recommended to add "@robots-string" to your robots.txt file (located in the root of your Drupal installation).', array('@flag-path' => $flag_path, '@robots-string' => $robots_string)),
+            'value' => $t('Search engines flagging content'),
+          );
+        }
+      }
+    }
   }
   return $requirements;
 }
@@ -202,6 +223,12 @@
         'not null' => TRUE,
         'default' => 0,
       ),
+      'sid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
       'timestamp' => array(
         'type' => 'int',
         'unsigned' => TRUE,
@@ -212,11 +239,11 @@
     ),
     'primary key' => array('fcid'),
     'unique keys' => array(
-      'fid_content_id_uid' => array('fid', 'content_id', 'uid'),
+      'fid_content_id_uid_sid' => array('fid', 'content_id', 'uid', 'sid'),
     ),
     'indexes' => array(
       'content_type_content_id' => array('content_type', 'content_id'),
-      'content_type_uid' => array('content_type', 'uid'),
+      'content_type_uid_sid' => array('content_type', 'uid', 'sid'),
     ),
   );
 
@@ -502,6 +529,26 @@
   return $ret;
 }
 
+/**
+ * Add the sid column and unique index on the flag_content table.
+ */
+function flag_update_6202() {
+  $ret = array();
+
+  // Drop the keys affected by the addition of the SID column.
+  db_drop_unique_key($ret, 'flag_content', 'fid_content_id_uid');
+  db_drop_index($ret, 'flag_content', 'content_type_uid');
+
+  // Add the column.
+  db_add_field($ret, 'flag_content', 'sid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+
+  // Re-add the removed keys.
+  db_add_unique_key($ret, 'flag_content', 'fid_content_id_uid_sid', array('fid', 'content_id', 'uid', 'sid'));
+  db_add_index($ret, 'flag_content', 'content_type_uid_sid', array('content_type', 'uid', 'sid'));
+
+  return $ret;
+}
+
 // This is a replacement for update_sql(). The latter doesn't support placeholders.
 function _flag_update_sql($sql) {
   $args = func_get_args();
Index: flag.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.module,v
retrieving revision 1.11.2.72.2.18
diff -u -r1.11.2.72.2.18 flag.module
--- flag.module	1 Oct 2009 04:36:44 -0000	1.11.2.72.2.18
+++ flag.module	27 Oct 2009 05:59:19 -0000
@@ -88,6 +88,10 @@
   if (module_exists('token')) {
     include_once $path .'/includes/flag.token.inc';
   }
+  if (module_exists('session_api')) {
+    // Set the anonymous user SID immediately, in case the user logs in.
+    flag_set_sid();
+  }
 }
 
 /**
@@ -280,14 +284,8 @@
       );
     }
   }
-  elseif (isset($form['type']) && isset($form['#node'])
-      && ($form_id == $form['type']['#value'] .'_node_form')) {
-    if (!$user->uid) {
-      return;
-    }
-
+  elseif (isset($form['type']) && isset($form['#node']) && ($form_id == $form['type']['#value'] .'_node_form')) {
     $nid = !empty($form['nid']['#value']) ? $form['nid']['#value'] : NULL;
-
     $flags = flag_get_flags('node', $form['type']['#value'], $user);
 
     // Filter out flags which need to be included on the node form.
@@ -407,6 +405,27 @@
  */
 function flag_user($op, &$edit, &$account, $category = NULL) {
   switch ($op) {
+    case 'login':
+      // Migrate anonymous flags to this user's account.
+      if (module_exists('session_api')) {
+        // The @ symbol suppresses errors if the user flags a piece of content
+        // they have already flagged as a logged-in user.
+        @db_query("UPDATE {flag_content} SET uid = %d, sid = 0 WHERE uid = 0 AND sid = %d", $account->uid, flag_get_sid(0));
+        // Delete any remaining flags this user had as an anonymous user.
+        db_query("DELETE FROM {flag_content} WHERE uid = 0 AND sid = %d", flag_get_sid(0));
+        // Clean up anonymous cookies.
+        if (isset($_COOKIE['flags'])) {
+          setcookie('flags', FALSE, 0, base_path());
+          unset($_COOKIE['flags']);
+        }
+        foreach ($_COOKIE as $key => $value) {
+          if (strpos($key, 'flag_global_') === 0) {
+            setcookie($key, FALSE, 0, base_path());
+            unset($_COOKIE[$key]);
+          }
+        }
+      }
+      break;
     case 'delete':
       // Remove flags by this user.
       $result = db_query("SELECT fc.fid, fc.content_id, c.count FROM {flag_content} fc LEFT JOIN {flag_counts} c ON fc.content_id = c.content_id AND fc.content_type = c.content_type WHERE fc.uid = %d", $account->uid);
@@ -448,6 +467,26 @@
 }
 
 /**
+ * Implementation of hook_session_api_cleanup().
+ *
+ * Clear out anonymous user flaggings during Session API cleanup.
+ */
+function flag_session_api_cleanup($arg = 'run') {
+  // Session API 1.1 version:
+  if ($arg == 'run') {
+    $result = db_query("SELECT fc.sid FROM {flag_content} fc LEFT JOIN {session_api} s ON (fc.sid = s.sid) WHERE fc.sid <> 0 AND s.sid IS NULL");
+    while ($row = db_fetch_object($result)) {
+      db_query("DELETE FROM {flag_content} WHERE sid = %d", $row->sid);
+    }
+  }
+  // Session API 1.2+ version.
+  elseif (is_array($arg)) {
+    $outdated_sids = $arg;
+    db_query('DELETE FROM {flag_content} WHERE sid IN (' . implode(',', $outdated_sids) . ')');
+  }
+}
+
+/**
  * Implementation of hook_node_type().
  */
 function flag_node_type($op, $info) {
@@ -640,10 +679,10 @@
     $result = db_query("SELECT nid, uid FROM {node} WHERE nid IN ($nids) AND type in ($placeholders)", $flag->types);
     while ($row = db_fetch_object($result)) {
       if ($flag->access_author == 'own') {
-        $access[$row->nid] = $row->uid != $account->uid ? FALSE : $access[$row->nid];
+        $access[$row->nid] = $row->uid != $account->uid ? FALSE : NULL;
       }
       elseif ($flag->access_author == 'others') {
-        $access[$row->nid] = $row->uid == $account->uid ? FALSE : $access[$row->nid];
+        $access[$row->nid] = $row->uid == $account->uid ? FALSE : NULL;
       }
     }
   }
@@ -796,12 +835,14 @@
  * See 'flag.tpl.php' for their documentation.
  */
 function template_preprocess_flag(&$variables) {
+  global $user;
   static $first_time = TRUE;
 
   // Some typing shotcuts:
   $flag =& $variables['flag'];
   $action = $variables['action'];
   $content_id = $variables['content_id'];
+  $flag_css_name = str_replace('_', '-', $flag->name);
 
   // Generate the link URL.
   $link_type = $flag->get_link_type();
@@ -816,7 +857,9 @@
     unset($link['href']);
   }
 
-  if ($flag->link_type == 'toggle' && $first_time) {
+  // Tell the template file to add JavaScript/CSS if using the toggle link type.
+  // Anonymous users always need the JavaScript to maintain their flag state.
+  if (($flag->link_type == 'toggle' || $user->uid == 0) && $first_time) {
     $variables['setup'] = $first_time;
     $first_time = FALSE;
   }
@@ -827,23 +870,26 @@
   $variables['link_href'] = isset($link['href']) ? check_url(url($link['href'], $link)) : FALSE;
   $variables['link_text'] = isset($link['title']) ? $link['title'] : $flag->get_label($action . '_short', $content_id);
   $variables['link_title'] = isset($link['attributes']['title']) ? check_plain($link['attributes']['title']) : check_plain(strip_tags($flag->get_label($action . '_long', $content_id)));
-  $variables['flag_name_css'] = str_replace('_', '-', $flag->name);
   $variables['last_action'] = ($action == 'flag' ? 'unflagged' : 'flagged');
+
+  $variables['flag_wrapper_classes_array'] = array();
+  $variables['flag_wrapper_classes_array'][] = 'flag-wrapper';
+  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name;
+  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name . '-' . $content_id;
+  $variables['flag_wrapper_classes'] = implode(' ', $variables['flag_wrapper_classes_array']);
+
   $variables['flag_classes_array'] = array();
   $variables['flag_classes_array'][] = 'flag';
   $variables['flag_classes_array'][] = $variables['action'] .'-action';
   $variables['flag_classes_array'][] = 'flag-link-'. $flag->link_type;
-
   if (isset($link['attributes']['class'])) {
     $variables['flag_classes_array'][] = $link['attributes']['class'];
   }
-
   if ($variables['after_flagging']) {
     $inverse_action = ($action == 'flag' ? 'unflag' : 'flag');
     $variables['message_text'] = $flag->get_label($inverse_action . '_message', $content_id);
     $variables['flag_classes_array'][] = $variables['last_action'];
   }
-
   $variables['flag_classes'] = implode(' ', $variables['flag_classes_array']);
 }
 
@@ -1194,6 +1240,10 @@
  * @param $uid
  *   Optional. The user ID whose flags we're checking. If none given, the
  *   current user will be used.
+ * @param $sid
+ *   Optional. The user SID (provided by Session API) whose flags we're
+ *   checking. If none given, the current user will be used. The SID is 0 for
+ *   logged in users.
  * @param $reset
  *   Reset the internal cache and execute the SQL query another time.
  *
@@ -1205,7 +1255,7 @@
  *   [nid] => [name] => Object from above.
  *
  */
-function flag_get_user_flags($content_type, $content_id = NULL, $uid = NULL, $reset = FALSE) {
+function flag_get_user_flags($content_type, $content_id = NULL, $uid = NULL, $sid = NULL, $reset = FALSE) {
   static $flagged_content;
 
   if ($reset) {
@@ -1216,29 +1266,30 @@
   }
 
   $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid;
+  $sid = !isset($sid) ? flag_get_sid($uid) : $sid;
 
   if (isset($content_id)) {
     if (!isset($flagged_content[$uid][$content_type][$content_id])) {
       $flag_names = _flag_get_flag_names();
-      $flagged_content[$uid][$content_type][$content_id] = array();
-      $result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND content_id = %d AND (uid = %d OR uid = 0)", $content_type, $content_id, $uid);
+      $flagged_content[$uid][$sid][$content_type][$content_id] = array();
+      $result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND content_id = %d AND (uid = %d OR uid = 0) AND sid = %s", $content_type, $content_id, $uid, $sid);
       while ($flag_content = db_fetch_object($result)) {
-        $flagged_content[$uid][$content_type][$content_id][$flag_names[$flag_content->fid]] = $flag_content;
+        $flagged_content[$uid][$sid][$content_type][$content_id][$flag_names[$flag_content->fid]] = $flag_content;
       }
     }
-    return $flagged_content[$uid][$content_type][$content_id];
+    return $flagged_content[$uid][$sid][$content_type][$content_id];
   }
 
   else {
-    if (!isset($flagged_content[$uid]['all'][$content_type])) {
+    if (!isset($flagged_content[$uid][$sid]['all'][$content_type])) {
       $flag_names = _flag_get_flag_names();
-      $flagged_content[$uid]['all'][$content_type] = TRUE;
-      $result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND (uid = %d OR uid = 0)", $content_type, $uid);
+      $flagged_content[$uid][$sid]['all'][$content_type] = TRUE;
+      $result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND (uid = %d OR uid = 0) AND sid = %s", $content_type, $uid, $sid);
       while ($flag_content = db_fetch_object($result)) {
-        $flagged_content[$uid][$content_type]['all'][$flag_names[$flag_content->fid]][$flag_content->content_id] = $flag_content;
+        $flagged_content[$uid][$sid][$content_type]['all'][$flag_names[$flag_content->fid]][$flag_content->content_id] = $flag_content;
       }
     }
-    return $flagged_content[$uid][$content_type]['all'];
+    return $flagged_content[$uid][$sid][$content_type]['all'];
   }
 
 }
@@ -1333,13 +1384,47 @@
 /**
  * Get a private token used to protect links from spoofing - CSRF.
  */
-function flag_get_token($nid) {
-  return drupal_get_token($nid);
+function flag_get_token($content_id) {
+  // Anonymous users get a less secure token, since it must be the same for all
+  // anonymous users on the entire site to work with page caching.
+  return ($GLOBALS['user']->uid) ? drupal_get_token($content_id) : md5(drupal_get_private_key() . $content_id);
 }
 
 /**
  * Check to see if a token value matches the specified node.
  */
-function flag_check_token($token, $seed) {
-  return drupal_get_token($seed) == $token;
+function flag_check_token($token, $content_id) {
+  return flag_get_token($content_id) == $token;
+}
+
+/**
+ * Set the Session ID for a user. Utilizes the Session API module.
+ *
+ * This function is only called in flag_init(), to set the current user's
+ * SID in case the user logs in during this request.
+ */
+function flag_set_sid($uid = NULL) {
+  static $sids = array();
+
+  if (!isset($uid)) {
+    $uid = $GLOBALS['user']->uid;
+  }
+
+  if (!isset($sids[$uid])) {
+    if (module_exists('session_api') && session_api_available() && $uid == 0) {
+      $sids[$uid] = session_api_get_sid();
+    }
+    else {
+      $sids[$uid] = 0;
+    }
+  }
+
+  return $sids[$uid];
+}
+
+/**
+ * Get the Session ID for a user. Utilizes the Session API module.
+ */
+function flag_get_sid($uid = NULL) {
+  return flag_set_sid($uid);
 }
Index: flag.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.inc,v
retrieving revision 1.1.2.30.2.12
diff -u -r1.1.2.30.2.12 flag.inc
--- flag.inc	4 Oct 2009 02:42:10 -0000	1.1.2.30.2.12
+++ flag.inc	27 Oct 2009 05:59:19 -0000
@@ -399,8 +399,8 @@
       $account = $GLOBALS['user'];
     }
 
-    // Anonymous user can't use this system. For now.
-    if ($account->uid == 0) {
+    // Anonymous user can't use this system unless Session API is installed.
+    if ($account->uid == 0 && !module_exists('session_api')) {
       return FALSE;
     }
 
@@ -431,7 +431,9 @@
     }
 
     if (!isset($action)) {
-      $action = $this->is_flagged($content_id, $account->uid) ? 'unflag' : 'flag';
+      $uid = $account->uid;
+      $sid = flag_get_sid($uid);
+      $action = $this->is_flagged($content_id, $uid, $sid) ? 'unflag' : 'flag';
     }
 
     // Base initial access on the user's basic permission to use this flag.
@@ -508,6 +510,14 @@
   }
 
   /**
+   * Returns TRUE if this flag requires anonymous user cookies.
+   */
+  function uses_anonymous_cookies() {
+    global $user;
+    return $user->uid == 0 && variable_get('cache', CACHE_DISABLED) > 0;
+  }
+
+  /**
    * Flags, on unflags, an item.
    *
    * @param $action
@@ -546,20 +556,29 @@
     // Clear various caches; We don't want code running after us to report
     // wrong counts or false flaggings.
     flag_get_counts(NULL, NULL, TRUE);
-    flag_get_user_flags(NULL, NULL, NULL, TRUE);
+    flag_get_user_flags(NULL, NULL, NULL, NULL, TRUE);
 
     // Perform the flagging or unflagging of this flag.
     $uid = $this->global ? 0 : $account->uid;
-    $flagged = $this->_is_flagged($content_id, $uid);
-    if ($action == 'unflag' && $flagged) {
-      $fcid = $this->_unflag($content_id, $uid);
-      // Let other modules perform actions.
-      module_invoke_all('flag', 'unflag', $this, $content_id, $account, $fcid);
-    }
-    elseif ($action == 'flag' && !$flagged) {
-      $fcid = $this->_flag($content_id, $uid);
-      // Let other modules perform actions.
-      module_invoke_all('flag', 'flag', $this, $content_id, $account, $fcid);
+    $sid = $this->global ? 0 : flag_get_sid($uid);
+    $flagged = $this->_is_flagged($content_id, $uid, $sid);
+    if ($action == 'unflag') {
+      if ($this->uses_anonymous_cookies()) {
+        $this->_unflag_anonymous($content_id);
+      }
+      if ($flagged) {
+        $fcid = $this->_unflag($content_id, $uid, $sid);
+        module_invoke_all('flag', 'unflag', $this, $content_id, $account, $fcid);
+      }
+    }
+    elseif ($action == 'flag') {
+      if ($this->uses_anonymous_cookies()) {
+        $this->_flag_anonymous($content_id);
+      }
+      if (!$flagged) {
+        $fcid = $this->_flag($content_id, $uid, $sid);
+        module_invoke_all('flag', 'flag', $this, $content_id, $account, $fcid);
+      }
     }
 
     return TRUE;
@@ -575,10 +594,12 @@
    *   Optional. The user ID whose flags we're checking. If none given, the
    *   current user will be used.
    */
-  function is_flagged($content_id, $uid = NULL) {
-    $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid;
+  function is_flagged($content_id, $uid = NULL, $sid = NULL) {
+    $uid = $this->global ? 0 : (!isset($uid) ? $GLOBALS['user']->uid : $uid);
+    $sid = $this->global ? 0 : (!isset($sid) ? flag_get_sid($uid) : $sid);
+
     // flag_get_user_flags() does caching.
-    $user_flags = flag_get_user_flags($this->content_type, $content_id, $uid);
+    $user_flags = flag_get_user_flags($this->content_type, $content_id, $uid, $sid);
     return isset($user_flags[$this->name]);
   }
 
@@ -594,8 +615,8 @@
    *
    * @private
    */
-  function _is_flagged($content_id, $uid) {
-    return db_result(db_query("SELECT fid FROM {flag_content} WHERE fid = %d AND uid = %d AND content_id = %d", $this->fid, $uid, $content_id));
+  function _is_flagged($content_id, $uid, $sid) { 
+    return db_result(db_query("SELECT fid FROM {flag_content} WHERE fid = %d AND uid = %d AND sid = %d AND content_id = %d", $this->fid, $uid, $sid, $content_id));
   }
 
   /**
@@ -606,8 +627,8 @@
    *
    * @private
    */
-  function _flag($content_id, $uid) {
-    db_query("INSERT INTO {flag_content} (fid, content_type, content_id, uid, timestamp) VALUES (%d, '%s', %d, %d, %d)", $this->fid, $this->content_type, $content_id, $uid, time());
+  function _flag($content_id, $uid, $sid) {
+    db_query("INSERT INTO {flag_content} (fid, content_type, content_id, uid, sid, timestamp) VALUES (%d, '%s', %d, %d, %d, %d)", $this->fid, $this->content_type, $content_id, $uid, $sid, time());
     $fcid = db_last_insert_id('flag_content', 'fcid');
     $this->_update_count($content_id);
     return $fcid;
@@ -621,12 +642,16 @@
    *
    * @private
    */
-  function _unflag($content_id, $uid) {
-    $fcid = db_result(db_query("SELECT fcid FROM {flag_content} WHERE fid = %d AND uid = %d AND content_id = %d", $this->fid, $uid, $content_id));
+  function _unflag($content_id, $uid, $sid) {
+    $fcid = db_result(db_query("SELECT fcid FROM {flag_content} WHERE fid = %d AND content_id = %d AND uid = %d AND sid = %d", $this->fid, $content_id, $uid, $sid));
     if ($fcid) {
       db_query("DELETE FROM {flag_content} WHERE fcid = %d", $fcid);
       $this->_update_count($content_id);
     }
+    // Remove anonymous cookies (if any).
+    if (isset($_COOKIE['flag'][$this->name . '_' . $content_id])) {
+      unset($_COOKIE['flags'][$this->name . '_' . $content_id]);
+    }
     return $fcid;
   }
 
@@ -644,6 +669,78 @@
   }
 
   /**
+   * Set a cookie for anonymous users to record their flagging.
+   *
+   * @private
+   */
+  function _flag_anonymous($content_id) {
+    // Global flags persist for the length of the minimum cache lifetime.
+    if ($this->global) {
+      $cookie_key = 'flag_global_' . str_replace('_', '-', $this->name) . '_' . $content_id;
+      $cookie_lifetime = (variable_get('cache', 0) > CACHE_DISABLED) ? variable_get('cache_lifetime', 0) : -1;
+      // Do not let the cookie lifetime be 0 (which is the no cache limit on
+      // anonymous page caching), since it would expire immediately. Usually
+      // the no cache limit means caches are cleared on cron, which usually runs
+      // at least once an hour.
+      if ($cookie_lifetime == 0) {
+        $cookie_lifetime = 3600;
+      }
+      setcookie($cookie_key, 1, time() + $cookie_lifetime, base_path());
+      $_COOKIE[$cookie_key] = 1;
+    }
+    // The anonymous per-user flags are stored in a single cookie, so that all
+    // of them persist as long as the Drupal cookie lifetime.
+    else {
+      $cookie_flags = isset($_COOKIE['flags']) ? $_COOKIE['flags'] : '';
+      $cookie_flags = explode(' ', $cookie_flags);
+      $cookie_key = str_replace('_', '-', $this->name) . '_' . $content_id;
+      $cookie_lifetime = min((int) ini_get('session.cookie_lifetime'), (int) ini_get('session.gc_maxlifetime'));
+      if (array_search($cookie_key, $cookie_flags) === FALSE) {
+        $cookie_flags[] = $cookie_key;
+        $cookie_flags = implode(' ', array_filter($cookie_flags));
+        setcookie('flags', $cookie_flags, time() + $cookie_lifetime, base_path());
+        $_COOKIE['flags'] = $cookie_flags;
+      }
+    }
+  }
+
+  /**
+   * Remove the cookie for anonymous users to record their unflagging.
+   *
+   * @private
+   */
+  function _unflag_anonymous($content_id) {
+    // Global flags are easy, just delete the global cookie.
+    if ($this->global) {
+      $cookie_key = 'flag_global_' . str_replace('_', '-', $this->name) . '_' . $content_id;
+      $cookie_lifetime = (variable_get('cache', 0) > CACHE_DISABLED) ? variable_get('cache_lifetime', 0) : -1;
+      // Do not let the cookie lifetime be 0 (which is the no cache limit on
+      // anonymous page caching), since it would expire immediately. Usually
+      // the no cache limit means caches are cleared on cron, which usually runs
+      // at least once an hour.
+      if ($cookie_lifetime == 0) {
+        $cookie_lifetime = 3600;
+      }
+      setcookie($cookie_key, 0, time() + $cookie_lifetime, base_path());
+      $_COOKIE[$cookie_key] = 0;
+    }
+    // User flags, update the single cookie for all user-data.
+    else {
+      $cookie_flags = isset($_COOKIE['flags']) ? $_COOKIE['flags'] : '';
+      $cookie_flags = explode(' ', $cookie_flags);
+      $cookie_key = str_replace('_', '-', $this->name) . '_' . $content_id;
+      $cookie_lifetime = min((int) ini_get('session.cookie_lifetime'), (int) ini_get('session.gc_maxlifetime'));
+      $cookie_index = array_search($cookie_key, $cookie_flags);
+      if ($cookie_index !== FALSE) {
+        unset($cookie_flags[$cookie_index]);
+        $cookie_flags = implode(' ', $cookie_flags);
+        setcookie('flags', $cookie_flags, $cookie_lifetime, base_path());
+        $_COOKIE['flags'] = $cookie_flags;
+      }
+    }
+  }
+
+  /**
    * Returns the number of times an item is flagged.
    *
    * Thanks to using a cache, inquiring several different flags about the same
@@ -657,10 +754,10 @@
   /**
    * Returns the number of items a user has flagged.
    *
-   * For global flags, pass '0' as the user ID.
+   * For global flags, pass '0' as the user ID and session ID.
    */
-  function get_user_count($uid) {
-    return db_result(db_query('SELECT COUNT(*) FROM {flag_content} WHERE fid = %d AND uid = %d', $this->fid, $uid));
+  function get_user_count($uid, $sid) {
+    return db_result(db_query('SELECT COUNT(*) FROM {flag_content} WHERE fid = %d AND uid = %d AND sid = %d', $this->fid, $uid, $sid));
   }
 
   /**
@@ -846,6 +943,8 @@
     else {
       $this->insert();
     }
+    // Clear the page cache for anonymous users.
+    cache_clear_all('*', 'cache_page', TRUE);
   }
 
   /**
@@ -921,6 +1020,19 @@
    * For parameters docmentation, see theme_flag().
    */
   function theme($action, $content_id, $after_flagging = FALSE) {
+    static $js_added = array();
+
+    // If the flagging user is anonymous and the page cache is enabled, always
+    // show the "flag" link, and then set the unflag link through JavaScript.
+    if ($this->uses_anonymous_cookies() && !$after_flagging) {
+      $js_action = $this->global ? ($action == 'flag' ? 'unflag' : 'flag') : 'flag';
+      if (!isset($js_added[$this->name . '_' . $content_id])) {
+        $js_added[$this->name . '_' . $content_id] = TRUE;
+        $js_template = theme($this->theme_suggestions(), $this, 'unflag', $content_id, $after_flagging);
+        drupal_add_js(array('flag' => array('templates' => array($this->name . '_' . $content_id => $js_template))), 'setting');
+      }
+    }
+
     return theme($this->theme_suggestions(), $this, $action, $content_id, $after_flagging);
   }
 
@@ -1060,10 +1172,9 @@
     return parent::flag($action, $content_id, $account, $skip_permission_check);
   }
 
-
-  function is_flagged($content_id, $uid = NULL) {
+  function is_flagged($content_id, $uid = NULL, $sid = NULL) {
     $content_id = $this->get_translation_id($content_id);
-    return parent::is_flagged($content_id, $uid);
+    return parent::is_flagged($content_id, $uid, $sid);
   }
 
   function get_labels_token_types() {
Index: README.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/README.txt,v
retrieving revision 1.1.4.6
diff -u -r1.1.4.6 README.txt
--- README.txt	7 Oct 2008 16:41:26 -0000	1.1.4.6
+++ README.txt	27 Oct 2009 05:59:18 -0000
@@ -45,15 +45,15 @@
 provides many views filters, fields, and sort criteria to make all sorts of
 displays possible relating to the number of times an item has been flagged.
 
-This module was formerly known as Views Bookmark.
+This module was formerly known as Views Bookmark, which was originally was
+written by Earl Miles. Flag was written by Nathan Haug and mystery man Mooffie.
 
-Ongoing development by Nathan Haug and mystery man Mooffie.
-
-Originally written by Earl Miles.
+This module built by robots: http://www.lullabot.com
 
 Recommended Modules
 -------------------
 - Views
+- Session API
 
 Installation
 ------------
@@ -61,6 +61,11 @@
 
 2) Enable the module using Administer -> Modules (/admin/build/modules)
 
+Optional Installation
+---------------------
+1) The ability for anonymous users to flag content is provided by the Session
+   API module, available at http://drupal.org/project/session_api.
+
 Configuration
 -------------
 The configuration for Flag is spread between Views configuration
Index: theme/flag.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/theme/Attic/flag.js,v
retrieving revision 1.1.2.7.2.3
diff -u -r1.1.2.7.2.3 flag.js
--- theme/flag.js	28 Sep 2009 18:56:49 -0000	1.1.2.7.2.3
+++ theme/flag.js	27 Oct 2009 05:59:20 -0000
@@ -8,6 +8,9 @@
  *   specifically of the <A> element, we say "element" or "the <A> element".
  */
 
+/**
+ * The main behavior to perform AJAX toggling of links.
+ */
 Drupal.flagLink = function(context) {
   /**
    * Helper function. Updates a link's HTML with a new one.
@@ -98,8 +101,101 @@
   $('a.flag-link-toggle:not(.flag-processed)').addClass('flag-processed').click(flagClick);
 };
 
+/**
+ * A behvior specifically for anonymous users. Update links to the proper state.
+ */
+Drupal.flagAnonymousLinks = function(context) {
+  // Swap in current links. Cookies are set by PHP's setcookie() upon flagging.
+
+  // Build a list of user-flags.
+  var userFlags = Drupal.flagCookie('flags');
+  if (userFlags) {
+    userFlags = userFlags.split('+');
+    for (var n in userFlags) {
+      var flagInfo = userFlags[n].split('_');
+      var flagName = flagInfo[0];
+      var contentId = flagInfo[1];
+      // User flags always default to off and the JavaScript toggles them on.
+      $('.flag-' + flagName + '-' + contentId, context).after(Drupal.settings.flag.templates[flagName + '_' + contentId]).remove();
+    }
+  }
+
+  // Build a list of global flags.
+  var globalFlags = document.cookie.match(/flag_global_([a-z0-9\-]+)_([0-9]+)=([01])/ig);
+  if (globalFlags) {
+    for (var n in globalFlags) {
+      var flagInfo = globalFlags[n].match(/flag_global_([a-z0-9\-]+)_([0-9]+)=([01])/i);
+      var flagName = flagInfo[1];
+      var contentId = flagInfo[2];
+      var flagState = (flagInfo[3] == '1') ? 'flag' : 'unflag';
+      // Global flags are tricky, they may or may not be flagged in the page
+      // cache. The template always contains the opposite of the current state.
+      // So when checking global flag cookies, we need to make sure that we
+      // don't swap out the link when it's already in the correct state.
+      $('.flag-' + flagName + '-' + contentId, context).each(function() {
+        if ($(this).find('.' + flagState + '-action').size()) {
+          $(this).after(Drupal.settings.flag.templates[flagName + '_' + contentId]).remove();
+        }
+      });
+    }
+  }
+}
+
+/**
+ * Utility function used to set Flag cookies.
+ *
+ * Note this is a direct copy of the jQuery cookie library.
+ * Written by Klaus Hartl.
+ */
+Drupal.flagCookie = function(name, value, options) {
+  if (typeof value != 'undefined') { // name and value given, set cookie
+    options = options || {};
+    if (value === null) {
+      value = '';
+      options = $.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed
+      options.expires = -1;
+    }
+    var expires = '';
+    if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
+      var date;
+      if (typeof options.expires == 'number') {
+        date = new Date();
+        date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
+      } else {
+        date = options.expires;
+      }
+      expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
+    }
+    // NOTE Needed to parenthesize options.path and options.domain
+    // in the following expressions, otherwise they evaluate to undefined
+    // in the packed version for some reason...
+    var path = options.path ? '; path=' + (options.path) : '';
+    var domain = options.domain ? '; domain=' + (options.domain) : '';
+    var secure = options.secure ? '; secure' : '';
+    document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
+  } else { // only name given, get cookie
+    var cookieValue = null;
+    if (document.cookie && document.cookie != '') {
+      var cookies = document.cookie.split(';');
+      for (var i = 0; i < cookies.length; i++) {
+        var cookie = jQuery.trim(cookies[i]);
+        // Does this cookie string begin with the name we want?
+        if (cookie.substring(0, name.length + 1) == (name + '=')) {
+          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+          break;
+        }
+      }
+    }
+    return cookieValue;
+  }
+};
 
 Drupal.behaviors.flagLink = function(context) {
+  // For anonymous users, swap out links with their current state for the user.
+  if (Drupal.settings.flag && Drupal.settings.flag.templates) {
+    Drupal.flagAnonymousLinks(context);
+  }
+
   // On load, bind the click behavior for all links on the page.
   Drupal.flagLink(context);
 };
Index: theme/flag.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/theme/Attic/flag.tpl.php,v
retrieving revision 1.1.2.7.2.1
diff -u -r1.1.2.7.2.1 flag.tpl.php
--- theme/flag.tpl.php	28 Sep 2009 02:05:06 -0000	1.1.2.7.2.1
+++ theme/flag.tpl.php	27 Oct 2009 05:59:20 -0000
@@ -40,7 +40,7 @@
     drupal_add_js(drupal_get_path('module', 'flag') .'/theme/flag.js');
   }
 ?>
-<span class="flag-wrapper flag-<?php print $flag_name_css; ?>">
+<span class="<?php print $flag_wrapper_classes; ?>">
   <?php if ($link_href): ?>
     <a href="<?php print $link_href; ?>" title="<?php print $link_title; ?>" class="<?php print $flag_classes ?>" rel="nofollow"><?php print $link_text; ?></a><span class="flag-throbber">&nbsp;</span>
   <?php else: ?>
