Index: flag.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.inc,v
retrieving revision 1.1.2.3
diff -u -F^[^a-z]*function -r1.1.2.3 flag.inc
--- flag.inc	29 Jul 2008 19:15:21 -0000	1.1.2.3
+++ flag.inc	4 Aug 2008 18:11:47 -0000
@@ -219,6 +219,150 @@   function validate_name() {
   }
 
   /**
+   * Loads, possibly from some cache, a content object this flag works with.
+   * Derived classes must implement this.
+   *
+   * @abstract
+   */
+  function load_content($content_id) {
+    return NULL;
+  }
+
+  /**
+   * Returns TRUE if the flag applies to the given content.
+   * Derived classes must implement this.
+   *
+   * @abstract
+   */
+  function applies_to_content_object($content) {
+    return FALSE;
+  }
+
+  /**
+   * Returns TRUE if the flag applies to the content with the given ID.
+   *
+   * This is a convenience method that simply loads the object and calls
+   * applies_to_content_object(). If you already have the object, don't call
+   * this function: call applies_to_content_object() directly.
+   */
+  function applies_to_content_id($content_id) {
+    return $this->applies_to_content_object($this->load_content($content_id));
+  }
+  
+  /**
+   * Given a content object, returns its ID.
+   * Derived classes must implement this.
+   *
+   * @abstract
+   */
+  function get_content_id($content) {
+    return NULL;
+  }
+
+  /**
+   * Returns TRUE if the flag is configured to show the flag-link using hook_link.
+   * Derived classes are likely to implement this.
+   */
+  function uses_hook_link($teaser) {
+    return FALSE;
+  }
+
+  /**
+   * Returns TRUE if user has access to use this flag.
+   *
+   * @param $account
+   *   Optional. The user object. If none given, the current user will be used.
+   *
+   * @return
+   *   Boolean TRUE if the user is allowed to flag/unflag. FALSE otherwise.
+   */
+  function user_access($account = NULL) {
+    if (!isset($account)) {
+      global $user;
+      $account = $user;
+    }
+    $matched_roles = array_intersect($this->roles, array_keys($account->roles));
+    return !empty($matched_roles) || empty($this->roles) || $account->uid == 1;
+  }
+
+  /**
+   * Returns TRUE if a certain user has flagged this content.
+   *
+   * This function uses a cache to save on SQL queries.
+   *
+   * @param $uid
+   *   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;
+
+    // flag_get_user_flags() alreday does caching, but nevertheless we manage a
+    // cache of our own to save on function calls.
+    static $flag_status = array();
+    if (!isset($flag_status[$uid][$this->content_type][$content_id])) {
+      $flag_status[$uid][$this->content_type][$content_id] = flag_get_user_flags($this->content_type, $content_id, $uid);
+    }
+
+    return isset($flag_status[$uid][$this->content_type][$content_id][$this->name]);
+  }
+
+  /**
+   * Returns TRUE if a certain user has flagged this content.
+   *
+   * You probably shouldn't call this raw private method: call the
+   * is_flagged() method instead.
+   *
+   * This method is similar to is_flagged() except that it does direct SQL and
+   * doesn't do caching. Use it when you want to not affect the cache, or to
+   * bypass it.
+   *
+   * @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));
+  }
+
+  /**
+   * A low-level method to flag content.
+   *
+   * You probably shouldn't call this raw private method: call the flag()
+   * function instead.
+   *
+   * @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());
+    $this->_update_count($content_id);
+  }
+
+  /**
+   * A low-level method to unflag content.
+   *
+   * You probably shouldn't call this raw private method: call the flag()
+   * function instead.
+   *
+   * @private
+   */
+  function _unflag($content_id, $uid) {
+    db_query("DELETE FROM {flag_content} WHERE fid = %d AND uid = %d AND content_id = %d", $this->fid, $uid, $content_id);
+    $this->_update_count($content_id);
+  }
+
+  /**
+   * Updates the flag count for this content
+   *
+   * @private
+   */
+  function _update_count($content_id) {
+    $count = db_result(db_query("SELECT count(*) FROM {flag_content} WHERE fid = %d AND content_id = %d", $this->fid, $content_id));
+    $result = db_query("UPDATE {flag_counts} set count = %d WHERE fid = %d AND content_id = %d", $count, $this->fid, $content_id);
+    if (!db_affected_rows()) {
+      db_query("INSERT INTO {flag_counts} (fid, content_type, content_id, count) VALUES (%d, '%s', %d, %d)", $this->fid, $this->content_type, $content_id, $count);
+    }
+  }
+
+  /**
    * Saves a flag to the database. It is a wrapper around update() and insert().
    */
   function save() {
@@ -344,6 +488,29 @@   function options_form(&$form) {
       '#default_value' => $this->show_on_form,
     );
   }
+
+  function load_content($content_id) {
+    $node = node_load($content_id);
+    return $node ? $node : NULL;
+  }
+
+  function applies_to_content_object($node) {
+    if ($node && in_array($node->type, $this->types)) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  function get_content_id($node) {
+    return $node->nid;
+  }
+
+  function uses_hook_link($teaser) {
+    if ($teaser && $this->show_on_teaser || !$teaser && $this->show_on_page) {
+      return TRUE;
+    }
+    return FALSE;
+  }
 }
 
 /**
@@ -364,6 +531,31 @@   function options_form(&$form) {
       '#default_value' => $this->show_on_comment,
     );
   }
+
+  function load_content($content_id) {
+    // The Comment module doesn't do caching, so we manage a cache ourselves.
+    static $comments = array();
+    if (!array_key_exists($content_id, $comments)) {
+      $comment = _comment_load($content_id);
+      $comments[$content_id] = $comment ? $comment : NULL;
+    }
+    return $comments[$content_id];
+  }
+
+  function applies_to_content_object($comment) {
+    if ($comment && ($node = node_load($comment->nid)) && in_array($node->type, $this->types)) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+  
+  function get_content_id($comment) {
+    return $comment->cid;
+  }
+
+  function uses_hook_link($teaser) {
+    return $this->show_on_comment;
+  }
 }
 
 /**
Index: flag.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/flag.module,v
retrieving revision 1.11.2.20
diff -u -F^[^a-z]*function -r1.11.2.20 flag.module
--- flag.module	31 Jul 2008 12:16:23 -0000	1.11.2.20
+++ flag.module	4 Aug 2008 18:11:49 -0000
@@ -1,5 +1,5 @@
 <?php
-// $Id: flag.module,v 1.11.2.20 2008/07/31 12:16:23 mooffie Exp $
+// $Id: flag.module,v 1.11.2.19 2008/07/30 07:20:18 mooffie Exp $
 
 include_once dirname(__FILE__) .'/flag.inc';
 
@@ -91,22 +91,10 @@ function flag_perm() {
 /**
  * Access checking to check an account for flagging ability.
  *
- * @param $flag
- *   The flag object being checked for access by a user.
- * @param $account
- *   Optional. The user object. If none given, the current user will be used.
- *
- * @return
- *   Boolean TRUE if the user is allowed to flag/unflag. FALSE otherwise.
+ * See documentation for $flag->user_access()
  */
 function flag_access($flag, $account = NULL) {
-  if (!isset($account)) {
-    global $user;
-    $account = $user;
-  }
-
-  $matched_roles = array_intersect($flag->roles, array_keys($account->roles));
-  return !empty($matched_roles) || empty($flag->roles) || $account->uid == 1;
+  return $flag->user_access($account);
 }
 
 /**
@@ -172,51 +160,41 @@ function flag_process_labels($flag, $con
  * Implementation of hook_link().
  */
 function flag_link($type, $object = NULL, $teaser = FALSE) {
-  if (!in_array($type, array('node', 'comment')) || !isset($object)) {
+  if (!isset($object) || !flag_fetch_definition($type)) {
     return;
   }
   global $user;
 
   // Anonymous users can't create flags with this system.
-  if (!$user || !$user->uid) {
+  if (!$user->uid) {
     return;
   }
 
-  if ($type == 'comment') {
-    $id = $object->cid;
-    $node = node_load($object->nid);
-    $node_type = $node->type;
-  }
-  else {
-    $id = $object->nid;
-    $node_type = $object->type;
-  }
-  // Get the user's flag status for this node.
-  $flag_status = flag_get_user_flags($type, $id);
-
-  // Get all possible flags for this node.
-  $flags = flag_get_flags($type, $node_type, $user);
-
+  // Get all possible flags for this content-type.
+  $flags = flag_get_flags($type);
+  
   foreach ($flags as $flag) {
-    if ($type == 'node') {
-      if ($teaser && !$flag->show_on_teaser || !$teaser && !$flag->show_on_page) {
-        continue;
-      }
+    if (!$flag->user_access($user)) {
+      // User has no permission to use this flag.
+      continue;
     }
-    else if ($type == 'comment') {
-      if (!$flag->show_on_comment) {
-        continue;
-      }
+    if (!$flag->uses_hook_link($teaser)) {
+      // Flag is not configured to show its link here.
+      continue;
+    }
+    if (!$flag->applies_to_content_object($object)) {
+      // Flag does not apply to this content.
+      continue;
     }
 
+    $content_id = $flag->get_content_id($object);
     // Token replacements.
-    $flag = flag_process_labels($flag, $type, $id, array('flag_short', 'flag_long', 'flag_message'), array('teaser' => $teaser));
+    $flag = flag_process_labels($flag, $type, $content_id, array('flag_short', 'flag_long', 'flag_message'), array('teaser' => $teaser));
 
-    $is_flagged = isset($flag_status[$flag->name]);
     // The flag links are actually fully rendered theme functions.
     // The HTML attribute is set to TRUE to allow whatever the themer desires.
     $links['flag-'. $flag->name] = array(
-      'title' => $flag->theme($is_flagged ? 'unflag' : 'flag', $id),
+      'title' => $flag->theme($flag->is_flagged($content_id) ? 'unflag' : 'flag', $content_id),
       'html' => TRUE,
     );
   }
@@ -303,7 +281,7 @@ function flag_nodeapi(&$node, $op, $teas
       // Response to the flag checkboxes added to the form in flag_form_alter().
       if (isset($node->flag)) {
         foreach ($node->flag as $name => $state) {
-          flag($state ? 'flag' : 'unflag', $name, 'node', $node->nid);
+          flag($state ? 'flag' : 'unflag', $name, $node->nid);
         }
       }
       break;
@@ -633,7 +611,7 @@ function _flag_clear_cache() {
  * Used both for the regular callback as well as the JS version.
  */
 function flag_page($action = 'flag', $flag_name, $content_type, $content_id = '') {
-  $result = flag($action, $flag_name, $content_type, $content_id);
+  $result = flag($action, $flag_name, $content_id);
   $js = isset($_REQUEST['js']);
 
   if (!$result) {
@@ -666,8 +644,12 @@ function flag_page($action = 'flag', $fl
 
 /**
  * Action for flagging or unflagging a piece of content.
+ *
+ * @todo There's no reason not to turn this function into a method of the
+ * flag object; More so that this function uses 'private' methods of this flag
+ * object.
  */
-function flag($action, $flag_name, $content_type, $content_id, $uid = NULL) {
+function flag($action, $flag_name, $content_id, $uid = NULL) {
   if (!isset($uid)) {
     global $user;
     $account = $user;
@@ -680,52 +662,35 @@ function flag($action, $flag_name, $cont
   if (!$account || !$account->uid) {
     return FALSE;
   }
-
-  // Check that the node exists.
-  if ($content_type == 'node' && is_numeric($content_id)) {
-    $object = node_load($content_id);
-    $node_type = $object->type;
-  }
-  elseif ($content_type == 'comment' && is_numeric($content_id)) {
-    $object = _comment_load($content_id);
-    $node = node_load($object->nid);
-    $node_type = $node->type;
+  
+  $flag = flag_get_flag($flag_name);
+  if (!$flag) {
+    // Flag does not exist.
+    return FALSE;
   }
-  if (empty($object)) {
+  if (!$flag->user_access($account)) {
+    // User has no permission to use this flag.
     return FALSE;
   }
-
-  // Check if this user has access to this flag.
-  $flag = flag_get_flag($flag_name);
-  $fid = $flag->fid;
-  if (!flag_access($flag, $account)) {
+  if (!$flag->applies_to_content_id($content_id)) {
+    // Flag does not apply to this content.
     return FALSE;
   }
-
+  
   // Perform the flagging or unflagging of this flag.
   $uid = $flag->global ? 0 : $account->uid;
-  $flagged = db_result(db_query("SELECT fid FROM {flag_content} WHERE fid = %d AND uid = %d AND content_type = '%s' AND content_id = %d", $fid, $uid, $content_type, $content_id));
+  $flagged = $flag->_is_flagged($content_id, $uid);
   if ($flagged && $action == 'unflag') {
-    db_query("DELETE FROM {flag_content} WHERE fid = %d AND uid = %d AND content_type = '%s' AND content_id = %d", $fid, $uid, $content_type, $content_id);
+    $flag->_unflag($content_id, $uid);
+    // Let other modules perform actions.
+    module_invoke_all('flag', 'unflag', $flag_name, $flag->content_type, $content_id, $uid);
   }
   elseif (!$flagged && $action == 'flag') {
-    db_query("INSERT INTO {flag_content} (fid, content_type, content_id, uid, timestamp) VALUES (%d, '%s', %d, %d, %d)", $fid, $content_type, $content_id, $uid, time());
+    $flag->_flag($content_id, $uid);
+    // Let other modules perform actions.
+    module_invoke_all('flag', 'flag', $flag_name, $flag->content_type, $content_id, $uid);
   }
 
-  // Update the flag count for this node.
-  $count = db_result(db_query("SELECT count(*) FROM {flag_content} WHERE fid = %d AND content_type = '%s' AND content_id = %d", $fid, $content_type, $content_id));
-  $result = db_query("UPDATE {flag_counts} set count = %d WHERE fid = %d AND content_type = '%s' AND content_id = %d", $count, $fid, $content_type, $content_id);
-  if (!db_affected_rows()) {
-    db_query("INSERT INTO {flag_counts} (fid, content_type, content_id, count) VALUES (%d, '%s', %d, %d)", $fid, $content_type, $content_id, $count);
-  }
-
-  // Let other modules perform actions.
-  if ($flagged && $action == 'unflag') {
-    module_invoke_all('flag', 'unflag', $flag_name, $content_type, $content_id, $uid);
-  }
-  elseif (!$flagged && $action == 'flag') {
-    module_invoke_all('flag', 'flag', $flag_name, $content_type, $content_id, $uid);
-  }
   return TRUE;
 }
 
@@ -795,7 +760,7 @@ function flag_node_operations() {
 function flag_nodes($nodes, $action, $flag_name) {
   $performed = FALSE;
   foreach ($nodes as $nid) {
-    $performed |= flag($action, $flag_name, 'node', $nid);
+    $performed |= flag($action, $flag_name, $nid);
   }
   // Drupal 6 doesn't display a confirmation message itself, so it's our responsibility.
   if ($performed) {
@@ -1158,21 +1123,16 @@ function flag_create_link($flag_name, $c
     // Flag does not exist.
     return;
   }
-  if (!flag_access($flag)) {
+  if (!$flag->user_access()) {
     // User has no permission to use this flag.
     return;
   }
-  if ($flag->content_type == 'node') {
-    $node = node_load($content_id);
-    if (!flag_content_enabled($flag, $flag->content_type, $node->type)) {
-      // Flag does not apply to this node-type.
-      return;
-    }
+  if (!$flag->applies_to_content_id($content_id)) {
+    // Flag does not apply to this content.
+    return;
   }
 
   $flag = flag_process_labels($flag, $flag->content_type, $content_id, array('flag_short', 'flag_long', 'flag_message'));
-  $flag_status = flag_get_user_flags($flag->content_type, $content_id);
-  $is_flagged = isset($flag_status[$flag->name]);
-  return $flag->theme($is_flagged ? 'unflag' : 'flag', $content_id);
+  return $flag->theme($flag->is_flagged($content_id) ? 'unflag' : 'flag', $content_id);
 }
 
