class flag_flag in Flag 5
Same name and namespace in other branches
- 6.2 flag.inc \flag_flag
- 6 flag.inc \flag_flag
- 7.3 includes/flag/flag_flag.inc \flag_flag
- 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
- class \flag_flag
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
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
flag_flag:: |
property | |||
flag_flag:: |
property | |||
flag_flag:: |
property | |||
flag_flag:: |
property | |||
flag_flag:: |
property | |||
flag_flag:: |
property | |||
flag_flag:: |
property | |||
flag_flag:: |
function | Returns TRUE if the flag applies to the content with the given ID. | ||
flag_flag:: |
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:: |
function | Returns TRUE if the flag applies to the given content. Derived classes must implement this. | ||
flag_flag:: |
function | Default constructor. Loads the default options. | ||
flag_flag:: |
function | Declares the options this flag supports, and their default values. | ||
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:: |
function | Create a complete flag (except an FID) from an array definition. | ||
flag_flag:: |
function | Another factory method. Returns a new, "empty" flag; e.g., one suitable for the "Add new flag" page. | ||
flag_flag:: |
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 | Flags, on 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 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 | 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 a certain user has flagged this content. | ||
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 regognized by default, so derived classes should override this method to add all token contexts they understand. | ||
flag_flag:: |
function | Defines the Rules argument for flag actions or conditions | ||
flag_flag:: |
function | Defines the Rules arguments involved in a flag event. | ||
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 | Saves an existing flag to the database. Better use save(). | ||
flag_flag:: |
function | Returns TRUE if user has access to use this flag. | ||
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 a flag settings | ||
flag_flag:: |
function | |||
flag_flag:: |
function | A low-level method to flag content. | ||
flag_flag:: |
function | Returns TRUE 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 | Updates the flag count for this content |