class flag_flag in Flag 7.2
Same name and namespace in other branches
- 5 flag.inc \flag_flag
- 6.2 flag.inc \flag_flag
- 6 flag.inc \flag_flag
- 7.3 includes/flag/flag_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
- class \flag_flag
Expanded class hierarchy of flag_flag
1 string reference to 'flag_flag'
- flag_action_info_alter in includes/
flag.actions.inc - Implements hook_action_info_alter().
File
- ./
flag.inc, line 122 - 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 integer
*/
var $fid = NULL;
/**
* The content type (aka entity type) this flag works with.
*
* @var string
*/
var $content_type = NULL;
/**
* The flag's "machine readable" name.
*
* @var string
*/
var $name = '';
/**
* The human-readable title for this flag.
*
* @var string
*/
var $title = '';
/**
* Whether this flag state should act as a single toggle to all users.
*
* @var bool
*/
var $global = FALSE;
/**
* The sub-types, AKA bundles, this flag applies to.
*
* @var array
*/
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);
// Populate the options with the defaults.
$options = (array) unserialize($row->options);
$options += $flag
->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;
}
return $flag;
}
/**
* Create a complete flag (except an FID) from an array definition.
*/
static function factory_by_array($config) {
$flag = flag_create_handler($config['content_type']);
foreach ($config as $option => $value) {
$flag->{$option} = $value;
}
if (isset($config['locked']) && 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 options() {
$options = array(
// The text for the "flag this" link for this flag.
'flag_short' => '',
// The description of the "flag this" link.
'flag_long' => '',
// Message displayed after flagging content.
'flag_message' => '',
// Likewise but for unflagged.
'unflag_short' => '',
'unflag_long' => '',
'unflag_message' => '',
'unflag_denied_text' => '',
// The link type used by the flag, as defined in hook_flag_link_types().
'link_type' => 'toggle',
'roles' => array(
'flag' => array(
DRUPAL_AUTHENTICATED_RID,
),
'unflag' => array(
DRUPAL_AUTHENTICATED_RID,
),
),
'weight' => 0,
);
// Merge in options from the current link type.
$link_type = $this
->get_link_type();
$options = array_merge($options, $link_type['options']);
// Allow other modules to change the flag options.
drupal_alter('flag_options', $options, $this);
return $options;
}
/**
* 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
->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['flag'] = array_values(array_filter($this->roles['flag']));
$this->roles['unflag'] = array_values(array_filter($this->roles['unflag']));
$this->types = array_values(array_filter($this->types));
// Clear internal titles cache:
$this
->get_title(NULL, TRUE);
}
/**
* Validates this flag's options.
*
* @return
* A list of errors encountered while validating this flag's options.
*/
function validate() {
// TODO: It might be nice if this used automatic method discovery rather
// than hard-coding the list of validate functions.
return array_merge_recursive($this
->validate_name(), $this
->validate_access());
}
/**
* Validates that the current flag's name is valid.
*
* @return
* A list of errors encountered while validating this flag's name.
*/
function validate_name() {
$errors = array();
// Ensure a safe machine name.
if (!preg_match('/^[a-z_][a-z0-9_]*$/', $this->name)) {
$errors['name'][] = array(
'error' => 'flag_name_characters',
'message' => t('The flag name may only contain lowercase letters, underscores, and numbers.'),
);
}
// Ensure the machine name is unique.
$flag = flag_get_flag($this->name);
if (!empty($flag) && (!isset($this->fid) || $flag->fid != $this->fid)) {
$errors['name'][] = array(
'error' => 'flag_name_unique',
'message' => t('Flag names must be unique. This flag name is already in use.'),
);
}
return $errors;
}
/**
* Validates that the current flag's access settings are valid.
*/
function validate_access() {
$errors = array();
// Require an unflag access denied message a role is not allowed to unflag.
if (empty($this->unflag_denied_text)) {
foreach ($this->roles['flag'] as $key => $rid) {
if ($rid && empty($this->roles['unflag'][$key])) {
$errors['unflag_denied_text'][] = array(
'error' => 'flag_denied_text_required',
'message' => t('The "Unflag not allowed text" is required if any user roles are not allowed to unflag.'),
);
break;
}
}
}
// Do not allow unflag access without flag access.
foreach ($this->roles['unflag'] as $key => $rid) {
if ($rid && empty($this->roles['flag'][$key])) {
$errors['roles'][] = array(
'error' => 'flag_roles_unflag',
'message' => t('Any user role that has the ability to unflag must also have the ability to flag.'),
);
break;
}
}
return $errors;
}
/**
* 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);
}
/**
* @defgroup access Access control
* @{
*/
/**
* 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));
}
/**
* Determines whether the user has access to use this flag.
*
* @param $action
* Optional. The action to test, either "flag" or "unflag". If none given,
* "flag" will be tested, which is the minimum permission to use a 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($action = 'flag', $account = NULL) {
if (!isset($account)) {
$account = $GLOBALS['user'];
}
// Anonymous user can't use this system unless Session API is installed.
if ($account->uid == 0 && !module_exists('session_api')) {
return FALSE;
}
$matched_roles = array_intersect($this->roles[$action], array_keys($account->roles));
return !empty($matched_roles) || $account->uid == 1;
}
/**
* Determines whether the user may flag, or unflag, the given content.
*
* This method typically should not be overridden by child classes. Instead
* they should implement type_access(), which is called by this method.
*
* @param $content_id
* The content ID to flag/unflag.
* @param $action
* The action to test. Either 'flag' or 'unflag'. Leave NULL to determine
* by flag status.
* @param $account
* The user on whose behalf to test the flagging action. Leave NULL for the
* current user.
*
* @return
* Boolean TRUE if the user is allowed to flag/unflag the given content.
* FALSE otherwise.
*/
function access($content_id, $action = NULL, $account = NULL) {
if (!isset($account)) {
$account = $GLOBALS['user'];
}
if (isset($content_id) && !$this
->applies_to_content_id($content_id)) {
// Flag does not apply to this content.
return FALSE;
}
if (!isset($action)) {
$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.
$access = $this
->user_access($action, $account);
// Check for additional access rules provided by sub-classes.
$child_access = $this
->type_access($content_id, $action, $account);
if (isset($child_access)) {
$access = $child_access;
}
// Allow modules to disallow (or allow) access to flagging.
$access_array = module_invoke_all('flag_access', $this, $content_id, $action, $account);
foreach ($access_array as $set_access) {
if (isset($set_access)) {
$access = $set_access;
}
}
return $access;
}
/**
* Determine access to multiple objects.
*
* Similar to user_access() but works on multiple IDs at once. Called in the
* pre_render() stage of the 'Flag links' field within Views 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.
*
* This method typically should not be overridden by child classes. Instead
* they should implement type_access_multiple(), which is called by this
* method.
*
* @param $content_ids
* The array of content IDs to check. The keys are the content IDs, the
* values are the actions to test: either 'flag' or 'unflag'.
* @param $account
* Optional. The account for which the actions will be compared against.
* If left empty, the current user will be used.
*
* @return
* An array whose keys are the object IDs and values are booleans indicating
* access.
*
* @see hook_flag_access_multiple()
*/
function access_multiple($content_ids, $account = NULL) {
$account = isset($account) ? $account : $GLOBALS['user'];
$access = array();
// First check basic user access for this action.
foreach ($content_ids as $content_id => $action) {
$access[$content_id] = $this
->user_access($content_ids[$content_id], $account);
}
// Check for additional access rules provided by sub-classes.
$child_access = $this
->type_access_multiple($content_ids, $account);
if (isset($child_access)) {
foreach ($child_access as $content_id => $content_access) {
if (isset($content_access)) {
$access[$content_id] = $content_access;
}
}
}
// Merge in module-defined access.
foreach (module_implements('flag_access_multiple') as $module) {
$module_access = module_invoke($module, 'flag_access_multiple', $this, $content_ids, $account);
foreach ($module_access as $content_id => $content_access) {
if (isset($content_access)) {
$access[$content_id] = $content_access;
}
}
}
return $access;
}
/**
* Implements access() implemented by each child class.
*
* @abstract
*
* @return
* FALSE if access should be denied, or NULL if there is no restriction to
* be made. This should NOT return TRUE.
*/
function type_access($content_id, $action, $account) {
return NULL;
}
/**
* Implements access_multiple() implemented by each child class.
*
* @abstract
*
* @return
* An array keyed by entity ids, whose values represent the access to the
* corresponding entity. The access value may be FALSE if access should be
* denied, or NULL (or not set) if there is no restriction to be made. It
* should NOT be TRUE.
*/
function type_access_multiple($content_ids, $account) {
return array();
}
/**
* @} End of "defgroup access".
*/
/**
* 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 this flag requires anonymous user cookies.
*/
function uses_anonymous_cookies() {
global $user;
return $user->uid == 0 && variable_get('cache', 0);
}
/**
* Flags, or 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) {
// Get the user.
if (!isset($account)) {
$account = $GLOBALS['user'];
}
if (!$account) {
return FALSE;
}
// Check access and applicability.
if (!$skip_permission_check) {
if (!$this
->access($content_id, $action, $account)) {
// User has no permission to flag/unflag this object.
return FALSE;
}
}
else {
// We are skipping permission checks. However, at a minimum we must make
// sure the flag applies to this content type:
if (!$this
->applies_to_content_id($content_id)) {
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, NULL, TRUE);
drupal_static_reset('flag_get_content_flags');
// Find out which user id to use.
$uid = $this->global ? 0 : $account->uid;
// Find out which session id to use.
if ($this->global) {
$sid = 0;
}
else {
$sid = flag_get_sid($uid, TRUE);
// Anonymous users must always have a session id.
if ($sid == 0 && $account->uid == 0) {
return FALSE;
}
}
// Perform the flagging or unflagging of this flag.
$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;
}
/**
* Determines 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.
*
* @return
* TRUE if the content is flagged, FALSE otherwise.
*/
function is_flagged($content_id, $uid = NULL, $sid = NULL) {
return (bool) $this
->get_flagging_record($content_id, $uid, $sid);
}
/**
* Returns the flagging record.
*
* This method returns the "flagging record": the {flag_content} record that
* exists for each flagged item (for a certain user). If the item isn't
* flagged, returns NULL. This method could be useful, for example, when you
* want to find out the 'fcid' or 'timestamp' values.
*
* Thanks to using a cache, inquiring several different flags about the same
* item results in only one SQL query.
*
* Parameters are the same as is_flagged()'s.
*/
function get_flagging_record($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, $sid);
return isset($user_flags[$this->name]) ? $user_flags[$this->name] : NULL;
}
/**
* Determines 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.
*
* @return
* If the content is flagged, returns the value of the 'fcid' column.
* Else, returns FALSE.
*
* @private
*/
function _is_flagged($content_id, $uid, $sid) {
return db_select('flag_content', 'fc')
->fields('fc', array(
'fcid',
))
->condition('fid', $this->fid)
->condition('uid', $uid)
->condition('sid', $sid)
->condition('content_id', $content_id)
->execute()
->fetchField();
}
/**
* A low-level method to flag content.
*
* You probably shouldn't call this raw private method: call the flag()
* function instead.
*
* @return
* The 'fcid' column of the new {flag_content} record.
*
* @private
*/
function _flag($content_id, $uid, $sid) {
$fcid = db_insert('flag_content')
->fields(array(
'fid' => $this->fid,
'content_type' => $this->content_type,
'content_id' => $content_id,
'uid' => $uid,
'sid' => $sid,
'timestamp' => REQUEST_TIME,
))
->execute();
$this
->_increase_count($content_id);
return $fcid;
}
/**
* A low-level method to unflag content.
*
* You probably shouldn't call this raw private method: call the flag()
* function instead.
*
* @return
* If the content was flagged, returns the value of the now deleted 'fcid'
* column. Else, returns FALSE.
*
* @private
*/
function _unflag($content_id, $uid, $sid) {
$fcid = db_select('flag_content', 'fc')
->fields('fc', array(
'fcid',
))
->condition('fid', $this->fid)
->condition('uid', $uid)
->condition('sid', $sid)
->condition('content_id', $content_id)
->execute()
->fetchField();
if ($fcid) {
db_delete('flag_content')
->condition('fcid', $fcid)
->execute();
$this
->_decrease_count($content_id);
}
return $fcid;
}
/**
* Increases the flag count for a piece of content.
*
* @param $content_id
* For which item should the count be increased.
* @param $number
* The amount of counts to increasing. Defaults to 1.
*
* @private
*/
function _increase_count($content_id, $number = 1) {
db_merge('flag_counts')
->key(array(
'fid' => $this->fid,
'content_id' => $content_id,
))
->fields(array(
'content_type' => $this->content_type,
'count' => $number,
'last_updated' => REQUEST_TIME,
))
->updateFields(array(
'last_updated' => REQUEST_TIME,
))
->expression('count', 'count + :inc', array(
':inc' => $number,
))
->execute();
}
/**
* Decreases the flag count for a piece of content.
*
* @param $content_id
* For which item should the count be descreased.
* @param $number
* The amount of counts to decrease. Defaults to 1.
*
* @private
*/
function _decrease_count($content_id, $number = 1) {
// Delete rows with count 0, for data consistency and space-saving.
// Done before the db_update() to prevent out-of-bounds errors on "count".
db_delete('flag_counts')
->condition('fid', $this->fid)
->condition('content_id', $content_id)
->condition('count', $number, '<=')
->execute();
// Update the count with the new value otherwise.
db_update('flag_counts')
->expression('count', 'count - :inc', array(
':inc' => $number,
))
->fields(array(
'last_updated' => REQUEST_TIME,
))
->condition('fid', $this->fid)
->condition('content_id', $content_id)
->execute();
}
/**
* Set a cookie for anonymous users to record their flagging.
*
* @private
*/
function _flag_anonymous($content_id) {
$storage = FlagCookieStorage::factory($this);
$storage
->flag($content_id);
}
/**
* Remove the cookie for anonymous users to record their unflagging.
*
* @private
*/
function _unflag_anonymous($content_id) {
$storage = FlagCookieStorage::factory($this);
$storage
->unflag($content_id);
}
/**
* 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 and session ID.
*/
function get_user_count($uid, $sid = NULL) {
if (!isset($sid)) {
$sid = flag_get_sid($uid);
}
return db_select('flag_content', 'fc')
->fields('fc', array(
'fcid',
))
->condition('fid', $this->fid)
->condition('uid', $uid)
->condition('sid', $sid)
->countQuery()
->execute()
->fetchField();
}
/**
* 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) {
$label = $this
->replace_tokens($label, array(), array(
'sanitize' => FALSE,
), $content_id);
}
return filter_xss_admin($label);
}
/**
* Get the link type for this flag.
*/
function get_link_type() {
$link_types = flag_get_link_types();
return isset($this->link_type) && isset($link_types[$this->link_type]) ? $link_types[$this->link_type] : $link_types['normal'];
}
/**
* Replaces tokens in a label. Only the 'global' token context is recognized
* by default, so derived classes should override this method to add all
* token contexts they understand.
*/
function replace_tokens($label, $contexts, $options, $content_id) {
return token_replace($label, $contexts, $options);
}
/**
* 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();
}
/**
* 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 && !in_array('any', $action['triggers'])) {
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".
*/
/**
* @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();
}
/**
* @} 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();
$this->is_new = FALSE;
}
else {
$this
->insert();
$this->is_new = TRUE;
}
// Clear the page cache for anonymous users.
cache_clear_all('*', 'cache_page', TRUE);
}
/**
* Saves an existing flag to the database. Better use save().
*/
function update() {
db_update('flags')
->fields(array(
'name' => $this->name,
'title' => $this->title,
'global' => $this->global,
'options' => $this
->get_serialized_options(),
))
->condition('fid', $this->fid)
->execute();
db_delete('flag_types')
->condition('fid', $this->fid)
->execute();
foreach ($this->types as $type) {
db_insert('flag_types')
->fields(array(
'fid' => $this->fid,
'type' => $type,
))
->execute();
}
}
/**
* Saves a new flag to the database. Better use save().
*/
function insert() {
$this->fid = db_insert('flags')
->fields(array(
'content_type' => $this->content_type,
'name' => $this->name,
'title' => $this->title,
'global' => $this->global,
'options' => $this
->get_serialized_options(),
))
->execute();
foreach ($this->types as $type) {
db_insert('flag_types')
->fields(array(
'fid' => $this->fid,
'type' => $type,
))
->execute();
}
}
/**
* Options are stored serialized in the database.
*/
function get_serialized_options() {
$option_names = array_keys($this
->options());
$options = array();
foreach ($option_names as $option) {
$options[$option] = $this->{$option};
}
return serialize($options);
}
/**
* Deletes a flag from the database.
*/
function delete() {
db_delete('flags')
->condition('fid', $this->fid)
->execute();
db_delete('flag_content')
->condition('fid', $this->fid)
->execute();
db_delete('flag_types')
->condition('fid', $this->fid)
->execute();
db_delete('flag_counts')
->condition('fid', $this->fid)
->execute();
module_invoke_all('flag_delete', $this);
}
/**
* Returns TRUE if this flag's declared API version is compatible with this
* module.
*
* An "incompatible" flag is one exported (and now being imported or exposed
* via hook_flag_default_flags()) by a different version of the Flag module.
* An incompatible flag should be treated as a "black box": it should not be
* saved or exported because our code may not know to handle its internal
* structure.
*/
function is_compatible() {
if (isset($this->fid)) {
// Database flags are always compatible.
return TRUE;
}
else {
if (!isset($this->api_version)) {
$this->api_version = 1;
}
return $this->api_version == FLAG_API_VERSION;
}
}
/**
* Finds the "default flag" corresponding to this flag.
*
* Flags defined in code ("default flags") can be overridden. This method
* returns the default flag that is being overridden by $this. Returns NULL
* if $this overrides no default flag.
*/
function find_default_flag() {
if ($this->fid) {
$default_flags = flag_get_default_flags(TRUE);
if (isset($default_flags[$this->name])) {
return $default_flags[$this->name];
}
}
}
/**
* Reverts an overriding flag to its default state.
*
* Note that $this isn't altered. To see the reverted flag you'll have to
* call flag_get_flag($this->name) again.
*
* @return
* TRUE if the flag was reverted successfully; FALSE if there was an error;
* NULL if this flag overrides no default flag.
*/
function revert() {
if ($default_flag = $this
->find_default_flag()) {
if ($default_flag
->is_compatible()) {
$default_flag = clone $default_flag;
$default_flag->fid = $this->fid;
$default_flag
->save();
flag_get_flags(NULL, NULL, NULL, TRUE);
return TRUE;
}
else {
return FALSE;
}
}
}
/**
* 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);
}
}
/**
* Returns administrative menu path for carrying out some action.
*/
function admin_path($action) {
if ($action == 'edit') {
// Since 'edit' is the default tab, we omit the action.
return FLAG_ADMIN_PATH . '/manage/' . $this->name;
}
else {
return FLAG_ADMIN_PATH . '/manage/' . $this->name . '/' . $action;
}
}
/**
* 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) {
static $js_added = array();
global $user;
// If the flagging user is anonymous, set a boolean for the benefit of
// JavaScript code. Currently, only our "anti-crawlers" mechanism uses it.
if ($user->uid == 0 && !isset($js_added['anonymous'])) {
$js_added['anonymous'] = TRUE;
drupal_add_js(array(
'flag' => array(
'anonymous' => TRUE,
),
), 'setting');
}
// If the flagging user is anonymous and the page cache is enabled, we
// update the links through JavaScript.
if ($this
->uses_anonymous_cookies() && !$after_flagging) {
if ($this->global) {
// In case of global flags, the JavaScript template is to contain
// the opposite of the current state.
$js_action = $action == 'flag' ? 'unflag' : 'flag';
}
else {
// In case of non-global flags, we always show the "flag!" link,
// and then replace it with the "unflag!" link through JavaScript.
$js_action = 'unflag';
$action = 'flag';
}
if (!isset($js_added[$this->name . '_' . $content_id])) {
$js_added[$this->name . '_' . $content_id] = TRUE;
$js_template = theme($this
->theme_suggestions(), array(
'flag' => $this,
'action' => $js_action,
'content_id' => $content_id,
'after_flagging' => $after_flagging,
));
drupal_add_js(array(
'flag' => array(
'templates' => array(
$this->name . '_' . $content_id => $js_template,
),
),
), 'setting');
}
}
return theme($this
->theme_suggestions(), array(
'flag' => $this,
'action' => $action,
'content_id' => $content_id,
'after_flagging' => $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__' . $this->link_type;
$suggestions[] = 'flag';
return $suggestions;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
flag_flag:: |
property | The content type (aka entity type) this flag works with. | ||
flag_flag:: |
property | The database ID. | ||
flag_flag:: |
property | Whether this flag state should act as a single toggle to all users. | ||
flag_flag:: |
property | The flag's "machine readable" name. | ||
flag_flag:: |
property | The human-readable title for this flag. | ||
flag_flag:: |
property | The sub-types, AKA bundles, this flag applies to. | ||
flag_flag:: |
function | Determines whether the user may flag, or unflag, the given content. | ||
flag_flag:: |
function | Determine access to multiple objects. | ||
flag_flag:: |
function | Returns administrative menu path for carrying out some action. | ||
flag_flag:: |
function | Returns TRUE if the flag applies to the content with the given ID. | ||
flag_flag:: |
function | Returns TRUE if the flag applies to the given content. | ||
flag_flag:: |
function | Default constructor. Loads the default options. | ||
flag_flag:: |
function | Deletes a flag from the database. | ||
flag_flag:: |
function | Disable a flag provided by a module. | ||
flag_flag:: |
function | Enable a flag provided by a module. | ||
flag_flag:: |
static | function | Create a complete flag (except an FID) from an array definition. | |
flag_flag:: |
static | function | Another factory method. Returns a new, "empty" flag; e.g., one suitable for the "Add new flag" page. | |
flag_flag:: |
static | function | Creates a flag from a database row. Returns it. | |
flag_flag:: |
function | Fetches, possibly from some cache, a content object this flag works with. | ||
flag_flag:: |
function | Finds the "default flag" corresponding to this flag. | ||
flag_flag:: |
function | Flags, or unflags, an item. | ||
flag_flag:: |
function | Update the flag with settings entered in a form. | ||
flag_flag:: |
function | Given a content object, returns its ID. Derived classes must implement this. | ||
flag_flag:: |
function | Returns the number of times an item is flagged. | ||
flag_flag:: |
function | Returns the flagging record. | ||
flag_flag:: |
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:: |
function | Processes a flag label for display. This means language translation and token replacements. | ||
flag_flag:: |
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:: |
function | Get the link type for this flag. | ||
flag_flag:: |
function | Returns objects the action may possibly need. This method should return at least the 'primary' object the action operates on. | ||
flag_flag:: |
function | Options are stored serialized in the database. | ||
flag_flag:: |
function | A convenience method for getting the flag title. | ||
flag_flag:: |
function | Returns the number of items a user has flagged. | ||
flag_flag:: |
function | Returns an array of all actions that are executable with this flag. | ||
flag_flag:: |
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:: |
function | Saves a new flag to the database. Better use save(). | ||
flag_flag:: |
function | Returns TRUE if this flag's declared API version is compatible with this module. | ||
flag_flag:: |
function | Determines if a certain user has flagged this content. | ||
flag_flag:: |
function | Declares the options this flag supports, and their default values. | ||
flag_flag:: |
function | Provides a form for setting options. | ||
flag_flag:: |
function | Stores some object in fetch_content()'s cache, so subsequenet calls to fetch_content() return it. | ||
flag_flag:: |
function | Replaces tokens in a label. Only the 'global' token context is recognized by default, so derived classes should override this method to add all token contexts they understand. | ||
flag_flag:: |
function | Reverts an overriding flag to its default state. | ||
flag_flag:: |
function | Saves a flag to the database. It is a wrapper around update() and insert(). | ||
flag_flag:: |
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:: |
function | Provides an array of possible themes to try for a given flag. | ||
flag_flag:: |
function | Implements access() implemented by each child class. | ||
flag_flag:: |
function | Implements access_multiple() implemented by each child class. | ||
flag_flag:: |
function | Saves an existing flag to the database. Better use save(). | ||
flag_flag:: |
function | Determines whether the user has access to use this flag. | ||
flag_flag:: |
function | Returns TRUE if this flag requires anonymous user cookies. | ||
flag_flag:: |
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:: |
function | Validates this flag's options. | ||
flag_flag:: |
function | Validates that the current flag's access settings are valid. | ||
flag_flag:: |
function | Validates that the current flag's name is valid. | ||
flag_flag:: |
function | Decreases the flag count for a piece of content. | ||
flag_flag:: |
function | A low-level method to flag content. | ||
flag_flag:: |
function | Set a cookie for anonymous users to record their flagging. | ||
flag_flag:: |
function | Increases the flag count for a piece of content. | ||
flag_flag:: |
function | Determines if a certain user has flagged this content. | ||
flag_flag:: |
function | Loads a content object this flag works with. Derived classes must implement this. | ||
flag_flag:: |
function | A low-level method to unflag content. | ||
flag_flag:: |
function | Remove the cookie for anonymous users to record their unflagging. |