You are here

class flag_flag in Flag 5

Same name and namespace in other branches
  1. 6.2 flag.inc \flag_flag
  2. 6 flag.inc \flag_flag
  3. 7.3 includes/flag/flag_flag.inc \flag_flag
  4. 7.2 flag.inc \flag_flag

This abstract class represents a flag, or, in Views 2 terminology, "a handler".

This is the base class for all flag implementations. Notable derived classes are flag_node and flag_comment.

Hierarchy

Expanded class hierarchy of flag_flag

File

./flag.inc, line 97
Implements various flags. Uses object oriented style inspired by that of Views 2.

View source
class flag_flag {

  // The database ID. Null for flags that haven't been saved to the database yet.
  var $fid = NULL;

  // The content-type this flag works with.
  var $content_type = NULL;

  // The flag's "machine readable" name.
  var $name = '';

  // Various non-serialized properties of the flag, corresponding directly to
  // database columns.
  var $title = '';
  var $roles = array(
    DRUPAL_AUTHENTICATED_RID,
  );
  var $global = FALSE;

  // The sub-types, e.g. node types, this flag applies to.
  var $types = array();

  /**
   * Creates a flag from a database row. Returns it.
   *
   * This is static method.
   *
   * The reason this isn't a non-static instance method --like Views's init()--
   * is because the class to instantiate changes according to the 'content_type'
   * database column. This design pattern is known as the "Single Table
   * Inheritance".
   *
   * @static
   */
  function factory_by_row($row) {
    $flag = flag_create_handler($row->content_type);

    // Lump all data unto the object...
    foreach ($row as $field => $value) {
      $flag->{$field} = $value;
    }

    // ...but skip the following two.
    unset($flag->options, $flag->type);
    $options = (array) unserialize($row->options);

    // Make the unserialized options accessible as normal properties.
    foreach ($options as $option => $value) {
      $flag->{$option} = $value;
    }
    if (!empty($row->type)) {

      // The loop loading from the database should further populate this property.
      $flag->types[] = $row->type;
    }
    $flag->roles = empty($row->roles) ? array() : explode(',', $row->roles);
    return $flag;
  }

  /**
   * Create a complete flag (except an FID) from an array definition.
   */
  function factory_by_array($config) {
    $flag = flag_create_handler($config['content_type']);
    foreach ($config as $option => $value) {
      $flag->{$option} = $value;
    }
    if (is_array($config['locked'])) {
      $flag->locked = drupal_map_assoc($config['locked']);
    }
    return $flag;
  }

  /**
   * Another factory method. Returns a new, "empty" flag; e.g., one suitable for
   * the "Add new flag" page.
   *
   * @static
   */
  function factory_by_content_type($content_type) {
    return flag_create_handler($content_type);
  }

  /**
   * Declares the options this flag supports, and their default values.
   *
   * Derived classes should want to override this.
   */
  function default_options() {
    return array(
      'flag_short' => '',
      'flag_long' => '',
      'flag_message' => '',
      'flag_confirmation' => '',
      'unflag_short' => '',
      'unflag_long' => '',
      'unflag_message' => '',
      'unflag_confirmation' => '',
      'link_type' => 'toggle',
    );
  }

  /**
   * Provides a form for setting options.
   *
   * Derived classes should want to override this.
   */
  function options_form(&$form) {
  }

  /**
   * Default constructor. Loads the default options.
   */
  function construct() {
    $options = $this
      ->default_options();
    foreach ($options as $option => $value) {
      $this->{$option} = $value;
    }
  }

  /**
   * Update the flag with settings entered in a form.
   */
  function form_input($form_values) {

    // Load the form fields indiscriminately unto the flag (we don't care about
    // stray FormAPI fields because we aren't touching unknown properties anyway.
    foreach ($form_values as $field => $value) {
      $this->{$field} = $value;
    }

    // But checkboxes need some massaging:
    $this->roles = array_values(array_filter($this->roles));
    $this->types = array_values(array_filter($this->types));

    // Clear internal titles cache:
    $this
      ->get_title(NULL, TRUE);
  }

  /**
   * Validates a flag settings
   */
  function validate() {
    $this
      ->validate_name();
  }
  function validate_name() {

    // Ensure a safe machine name.
    if (!preg_match('/^[a-z_][a-z0-9_]*$/', $this->name)) {
      form_set_error('name', t('The flag name may only contain lowercase letters, underscores, and numbers.'));
    }

    // Ensure the machine name is unique.
    if (!isset($this->fid)) {
      $flag = flag_get_flag($this->name);
      if (!empty($flag)) {
        form_set_error('name', t('Flag names must be unique. This flag name is already in use.'));
      }
    }
  }

  /**
   * Fetches, possibly from some cache, a content object this flag works with.
   */
  function fetch_content($content_id, $object_to_remember = NULL) {
    static $cache = array();
    if (isset($object_to_remember)) {
      $cache[$content_id] = $object_to_remember;
    }
    if (!array_key_exists($content_id, $cache)) {
      $content = $this
        ->_load_content($content_id);
      $cache[$content_id] = $content ? $content : NULL;
    }
    return $cache[$content_id];
  }

  /**
   * Loads a content object this flag works with.
   * Derived classes must implement this.
   *
   * @abstract
   * @private
   * @static
   */
  function _load_content($content_id) {
    return NULL;
  }

  /**
   * Stores some object in fetch_content()'s cache, so subsequenet calls to
   * fetch_content() return it.
   *
   * This is needed because otherwise fetch_object() loads the object from the
   * database (by calling _load_content()), whereas sometimes we want to fetch
   * an object that hasn't yet been saved to the database. See flag_nodeapi().
   */
  function remember_content($content_id, $object) {
    $this
      ->fetch_content($content_id, $object);
  }

  /**
   * 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
      ->fetch_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) {
    if ($this->show_on_profile) {
      return TRUE;
    }
    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)) {
      $account = $GLOBALS['user'];
    }
    $matched_roles = array_intersect($this->roles, array_keys($account->roles));
    return !empty($matched_roles) || empty($this->roles) || $account->uid == 1;
  }

  /**
   * Flags, on unflags, an item.
   *
   * @param $action
   *   Either 'flag' or 'unflag'.
   * @param $content_id
   *   The ID of the item to flag or unflag.
   * @param $account
   *   The user on whose behalf to flag. Leave empty for the current user.
   * @param $skip_permission_check
   *   Flag the item even if the $account user don't have permission to do so.
   * @return
   *   FALSE if some error occured (e.g., user has no permission, flag isn't
   *   applicable to the item, etc.), TRUE otherwise.
   */
  function flag($action, $content_id, $account = NULL, $skip_permission_check = FALSE) {
    if (!isset($account)) {
      $account = $GLOBALS['user'];
    }
    if (!$account) {
      return FALSE;
    }
    if (!$account->uid) {

      // Anonymous users can't flag with this system. For now.
      //
      // @todo This is legacy code. $flag->user_access() should handle this.
      // This will also make it posible to have flags that do support anonymous
      // users.
      return FALSE;
    }
    if (!$skip_permission_check && !$this
      ->user_access($account)) {

      // User has no permission to use this flag.
      return FALSE;
    }
    if (!$this
      ->applies_to_content_id($content_id)) {

      // Flag does not apply to this content.
      return FALSE;
    }

    // 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);

    // 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) {
      $this
        ->_unflag($content_id, $uid);

      // Let other modules perform actions.
      module_invoke_all('flag', 'unflag', $this, $content_id, $account);
    }
    elseif ($action == 'flag' && !$flagged) {
      $this
        ->_flag($content_id, $uid);

      // Let other modules perform actions.
      module_invoke_all('flag', 'flag', $this, $content_id, $account);
    }
    return TRUE;
  }

  /**
   * Returns TRUE if a certain user has flagged this content.
   *
   * Thanks to using a cache, inquiring several different flags about the same
   * item results in only one SQL query.
   *
   * @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() does caching.
    $user_flags = flag_get_user_flags($this->content_type, $content_id, $uid);
    return isset($user_flags[$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);
    }
  }

  /**
   * Returns the number of times an item is flagged.
   *
   * Thanks to using a cache, inquiring several different flags about the same
   * item results in only one SQL query.
   */
  function get_count($content_id) {
    $counts = flag_get_counts($this->content_type, $content_id);
    return isset($counts[$this->name]) ? $counts[$this->name] : 0;
  }

  /**
   * Returns the number of items a user has flagged.
   *
   * For global flags, pass '0' as the user 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));
  }

  /**
   * Processes a flag label for display. This means language translation and
   * token replacements.
   *
   * You should always call this function and not get at the label directly.
   * E.g., do `print $flag->get_label('title')` instead of `print
   * $flag->title`.
   *
   * @param $label
   *   The label to get, e.g. 'title', 'flag_short', 'unflag_short', etc.
   * @param $content_id
   *   The ID in whose context to interpret tokens. If not given, only global
   *   tokens will be substituted.
   * @return
   *   The processed label.
   */
  function get_label($label, $content_id = NULL) {
    if (!isset($this->{$label})) {
      return;
    }
    $label = t($this->{$label});
    if (strpos($label, '[') !== FALSE && module_exists('token')) {
      $label = $this
        ->replace_tokens($label, array(
        'global' => NULL,
      ), $content_id);
    }
    return filter_xss_admin($label);
  }

  /**
   * Replaces tokens in a label. Only the 'global' token context is regognized
   * by default, so derived classes should override this method to add all
   * token contexts they understand.
   */
  function replace_tokens($label, $contexts, $content_id) {
    return token_replace_multiple($label, $contexts);
  }

  /**
   * Returns the token types this flag understands in labels. These are used
   * for narrowing down the token list shown in the help box to only the
   * relevant ones.
   *
   * Derived classes should override this.
   */
  function get_labels_token_types() {
    return array(
      'global',
    );
  }

  /**
   * A convenience method for getting the flag title.
   *
   * `$flag->get_title()` is shorthand for `$flag->get_label('title')`.
   */
  function get_title($content_id = NULL, $reset = FALSE) {
    static $titles = array();
    if ($reset) {
      $titles = array();
    }
    $slot = intval($content_id);

    // Convert NULL to 0.
    if (!isset($titles[$this->fid][$slot])) {
      $titles[$this->fid][$slot] = $this
        ->get_label('title', $content_id);
    }
    return $titles[$this->fid][$slot];
  }

  /**
   * Returns a 'flag action' object. It exists only for the sake of its
   * informative tokens. Currently, it's utilized only for the 'mail' action.
   *
   * Derived classes should populate the 'content_title' and 'content_url'
   * slots.
   */
  function get_flag_action($content_id) {
    $flag_action = new stdClass();
    $flag_action->flag = $this->name;
    $flag_action->content_type = $this->content_type;
    $flag_action->content_id = $content_id;
    return $flag_action;
  }

  /**
   * @addtogroup actions
   * @{
   * Methods that can be overridden to support Actions.
   */

  /**
   * Returns an array of all actions that are executable with this flag.
   */
  function get_valid_actions() {
    $actions = module_invoke_all('action_info');
    foreach ($actions as $callback => $action) {
      if ($action['type'] != $this->content_type && !isset($action['hooks'][$this->content_type])) {
        unset($actions[$callback]);
      }
    }
    return $actions;
  }

  /**
   * Returns objects the action may possibly need. This method should return at
   * least the 'primary' object the action operates on.
   *
   * This method is needed because get_valid_actions() returns actions that
   * don't necessarily operate on an object of a type this flag manages. For
   * example, flagging a comment may trigger an 'Unpublish post' action on a
   * node; So the comment flag needs to tell the action about some node.
   *
   * Derived classes must implement this.
   *
   * @abstract
   */
  function get_relevant_action_objects($content_id) {
    return array();
  }

  /**
   * @} End of "addtogroup actions".
   */

  /**
   * Methods that can be overridden to support the Rules module.
   *
   * @addtogroup rules
   * @{
   */

  /**
   * Defines the Rules arguments involved in a flag event.
   */
  function rules_get_event_arguments_definition() {
    return array();
  }

  /**
   * Defines the Rules argument for flag actions or conditions
   */
  function rules_get_element_argument_definition() {
    return array();
  }

  /**
   * @} End of "addtogroup rules".
   */

  /**
   * @addtogroup views
   * @{
   * Methods that can be overridden to support the Views module.
   */

  /**
   * Returns information needed for Views integration. E.g., the Views table
   * holding the flagged content, its primary key, and various labels. See
   * derived classes for examples.
   *
   * @static
   */
  function get_views_info() {
    return array();
  }

  /**
   * Similar to applies_to_content_id() but works on a bunch of IDs. It is
   * called in the pre_render() stage of the 'Flag links' field to find out where
   * that link applies. The reason we do a separate DB query, and not lump this
   * test in the Views query, is to make 'many to one' tests possible without
   * interfering with the rows, and also to reduce the complexity of the code.
   */
  function applies_to_content_id_array($content_ids) {
    return array();
  }

  /**
   * @} End of "addtogroup views".
   */

  /**
   * Saves a flag to the database. It is a wrapper around update() and insert().
   */
  function save() {
    if (isset($this->fid)) {
      $this
        ->update();
    }
    else {
      $this
        ->insert();
    }
  }

  /**
   * Saves an existing flag to the database. Better use save().
   */
  function update() {
    db_query("UPDATE {flags} SET name = '%s', title = '%s', roles = '%s', global = %d, options = '%s' WHERE fid = %d", $this->name, $this->title, implode(',', $this->roles), $this->global, $this
      ->get_serialized_options(), $this->fid);
    db_query("DELETE FROM {flag_types} WHERE fid = %d", $this->fid);
    foreach ($this->types as $type) {
      db_query("INSERT INTO {flag_types} (fid, type) VALUES (%d, '%s')", $this->fid, $type);
    }
  }

  /**
   * Saves a new flag to the database. Better use save().
   */
  function insert() {
    if (function_exists('db_last_insert_id')) {

      // Drupal 6. We have a 'serial' primary key.
      db_query("INSERT INTO {flags} (content_type, name, title, roles, global, options) VALUES ('%s', '%s', '%s', '%s', %d, '%s')", $this->content_type, $this->name, $this->title, implode(',', $this->roles), $this->global, $this
        ->get_serialized_options());
      $this->fid = db_last_insert_id('flags', 'fid');
    }
    else {

      // Drupal 5. We have an 'integer' primary key.
      $this->fid = db_next_id('{flags}_fid');
      db_query("INSERT INTO {flags} (fid, content_type, name, title, roles, global, options) VALUES (%d, '%s', '%s', '%s', '%s', %d, '%s')", $this->fid, $this->content_type, $this->name, $this->title, implode(',', $this->roles), $this->global, $this
        ->get_serialized_options());
    }
    foreach ($this->types as $type) {
      db_query("INSERT INTO {flag_types} (fid, type) VALUES (%d, '%s')", $this->fid, $type);
    }
  }

  /**
   * Options are stored serialized in the database.
   */
  function get_serialized_options() {
    $option_names = array_keys($this
      ->default_options());
    $options = array();
    foreach ($option_names as $option) {
      $options[$option] = $this->{$option};
    }
    return serialize($options);
  }

  /**
   * Deletes a flag from the database.
   */
  function delete() {
    db_query('DELETE FROM {flags} WHERE fid = %d', $this->fid);
    db_query('DELETE FROM {flag_content} WHERE fid = %d', $this->fid);
    db_query('DELETE FROM {flag_types} WHERE fid = %d', $this->fid);
    db_query('DELETE FROM {flag_counts} WHERE fid = %d', $this->fid);
  }

  /**
   * Disable a flag provided by a module.
   */
  function disable() {
    if (isset($this->module)) {
      $flag_status = variable_get('flag_default_flag_status', array());
      $flag_status[$this->name] = FALSE;
      variable_set('flag_default_flag_status', $flag_status);
    }
  }

  /**
   * Enable a flag provided by a module.
   */
  function enable() {
    if (isset($this->module)) {
      $flag_status = variable_get('flag_default_flag_status', array());
      $flag_status[$this->name] = TRUE;
      variable_set('flag_default_flag_status', $flag_status);
    }
  }

  /**
   * Renders a flag/unflag link. This is a wrapper around theme('flag') that,
   * in Drupal 6, easily channels the call to the right template file.
   *
   * For parameters docmentation, see theme_flag().
   */
  function theme($action, $content_id, $after_flagging = FALSE) {
    if (!_flag_is_drupal_5()) {

      // We're running Drupal 6.
      return theme($this
        ->theme_suggestions(), $this, $action, $content_id, $after_flagging);
    }
    else {

      // We're running Drupal 5. Noting to do: The theme_suggestions[] are
      // handed to phptemplate in phptemplate_flag(), if the user bothered to
      // copy that function into her 'template.php'.
      return theme('flag', $this, $action, $content_id, $after_flagging);
    }
  }

  /**
   * Provides an array of possible themes to try for a given flag.
   */
  function theme_suggestions() {
    $suggestions = array();
    $suggestions[] = 'flag__' . $this->name;
    $suggestions[] = 'flag';
    return $suggestions;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
flag_flag::$content_type property
flag_flag::$fid property
flag_flag::$global property
flag_flag::$name property
flag_flag::$roles property
flag_flag::$title property
flag_flag::$types property
flag_flag::applies_to_content_id function Returns TRUE if the flag applies to the content with the given ID.
flag_flag::applies_to_content_id_array function Similar to applies_to_content_id() but works on a bunch of IDs. It is called in the pre_render() stage of the 'Flag links' field to find out where that link applies. The reason we do a separate DB query, and not lump this test in the Views…
flag_flag::applies_to_content_object function Returns TRUE if the flag applies to the given content. Derived classes must implement this.
flag_flag::construct function Default constructor. Loads the default options.
flag_flag::default_options function Declares the options this flag supports, and their default values.
flag_flag::delete function Deletes a flag from the database.
flag_flag::disable function Disable a flag provided by a module.
flag_flag::enable function Enable a flag provided by a module.
flag_flag::factory_by_array function Create a complete flag (except an FID) from an array definition.
flag_flag::factory_by_content_type function Another factory method. Returns a new, "empty" flag; e.g., one suitable for the "Add new flag" page.
flag_flag::factory_by_row function Creates a flag from a database row. Returns it.
flag_flag::fetch_content function Fetches, possibly from some cache, a content object this flag works with.
flag_flag::flag function Flags, on unflags, an item.
flag_flag::form_input function Update the flag with settings entered in a form.
flag_flag::get_content_id function Given a content object, returns its ID. Derived classes must implement this.
flag_flag::get_count function Returns the number of times an item is flagged.
flag_flag::get_flag_action function Returns a 'flag action' object. It exists only for the sake of its informative tokens. Currently, it's utilized only for the 'mail' action.
flag_flag::get_label function Processes a flag label for display. This means language translation and token replacements.
flag_flag::get_labels_token_types function Returns the token types this flag understands in labels. These are used for narrowing down the token list shown in the help box to only the relevant ones.
flag_flag::get_relevant_action_objects function Returns objects the action may possibly need. This method should return at least the 'primary' object the action operates on.
flag_flag::get_serialized_options function Options are stored serialized in the database.
flag_flag::get_title function A convenience method for getting the flag title.
flag_flag::get_user_count function Returns the number of items a user has flagged.
flag_flag::get_valid_actions function Returns an array of all actions that are executable with this flag.
flag_flag::get_views_info function Returns information needed for Views integration. E.g., the Views table holding the flagged content, its primary key, and various labels. See derived classes for examples.
flag_flag::insert function Saves a new flag to the database. Better use save().
flag_flag::is_flagged function Returns TRUE if a certain user has flagged this content.
flag_flag::options_form function Provides a form for setting options.
flag_flag::remember_content function Stores some object in fetch_content()'s cache, so subsequenet calls to fetch_content() return it.
flag_flag::replace_tokens function Replaces tokens in a label. Only the 'global' token context is regognized by default, so derived classes should override this method to add all token contexts they understand.
flag_flag::rules_get_element_argument_definition function Defines the Rules argument for flag actions or conditions
flag_flag::rules_get_event_arguments_definition function Defines the Rules arguments involved in a flag event.
flag_flag::save function Saves a flag to the database. It is a wrapper around update() and insert().
flag_flag::theme function Renders a flag/unflag link. This is a wrapper around theme('flag') that, in Drupal 6, easily channels the call to the right template file.
flag_flag::theme_suggestions function Provides an array of possible themes to try for a given flag.
flag_flag::update function Saves an existing flag to the database. Better use save().
flag_flag::user_access function Returns TRUE if user has access to use this flag.
flag_flag::uses_hook_link function Returns TRUE if the flag is configured to show the flag-link using hook_link. Derived classes are likely to implement this.
flag_flag::validate function Validates a flag settings
flag_flag::validate_name function
flag_flag::_flag function A low-level method to flag content.
flag_flag::_is_flagged function Returns TRUE if a certain user has flagged this content.
flag_flag::_load_content function Loads a content object this flag works with. Derived classes must implement this.
flag_flag::_unflag function A low-level method to unflag content.
flag_flag::_update_count function Updates the flag count for this content