'admin/build/flags',
'title' => t('Flags'),
'callback' => 'flag_admin_page',
'access' => user_access('administer flags'),
'description' => t('Configure flags for marking content with arbitary information (such as offensive or bookmarked).'),
'type' => MENU_NORMAL_ITEM,
);
$items[] = array(
'path' => 'admin/build/flags/edit',
'title' => t('Edit flags'),
'callback' => 'drupal_get_form',
'callback arguments' => array('flag_form'),
'access' => user_access('administer flags'),
'type' => MENU_CALLBACK,
);
$items[] = array('path' => 'admin/build/flags/delete',
'title' => t('Delete flags'),
'callback' => 'drupal_get_form',
'callback arguments' => array('flag_delete_confirm'),
'access' => user_access('administer flags'),
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/build/flags/list',
'title' => t('List'),
'callback' => 'flag_admin_page',
'access' => user_access('administer flags'),
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -1,
);
$items[] = array(
'path' => 'admin/build/flags/add',
'title' => t('Add'),
'callback' => 'flag_add_page',
'access' => user_access('administer flags'),
'type' => MENU_LOCAL_TASK,
);
$items[] = array('path' => 'flag',
'title' => t('Flag'),
'callback' => 'flag_page',
'access' => user_access('access content'),
'type' => MENU_CALLBACK,
);
}
if (module_exists('actions')) {
$items[] = array(
'path' => 'admin/build/flags/actions',
'title' => t('Actions'),
'callback' => 'flag_actions_page',
'access' => user_access('administer actions'),
'type' => MENU_LOCAL_TASK,
);
}
return $items;
}
/**
* Implementation of hook_init().
*/
function flag_init() {
// Only load for non-cached pages.
if (function_exists('drupal_set_content')) {
if (module_exists('actions')) {
include_once drupal_get_path('module', 'flag') .'/flag.actions.inc';
}
if (module_exists('token')) {
include_once drupal_get_path('module', 'flag') .'/flag.token.inc';
}
}
}
/**
* Implementation of hook_perm().
*/
function flag_perm() {
return array('administer flags');
}
/**
* 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.
*/
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;
}
/**
* Content type checking to see if a flag applies to a certain type of data.
*
* @param $flag
* The flag object whose available types are being checked.
* @param $content_type
* The type of content being checked, usually "node".
* @param $content_subtype
* The subtype (node type) being checked.
*
* @return
* Boolean TRUE if the flag is enabled for this type and subtype.
* FALSE otherwise.
*/
function flag_content_enabled($flag, $content_type, $content_subtype = NULL) {
$return = $flag->content_type == $content_type && (!isset($content_subtype) || in_array($content_subtype, $flag->types));
return $return;
}
/**
* Processes the various flag labels for display. This means token replacements
* and language translation.
*
* @param $flag
* The flag object.
* @param $content_type
* The type of content being used for token replacements. Usually 'node'.
* @param $content_id
* The ID in whose context to interpret the tokens. The content will only be
* loaded if tokens are used. Omit to use the 'global' context.
* @param $variables
* Array containing the names of the label variables to process.
* @param $options
* Optional. Options to pass to token_replace().
* @return
* A new flag object with the labels processed.
*/
function flag_process_labels($flag, $content_type = NULL, $content_id = NULL, $variables = NULL, $options = array()) {
$flag = drupal_clone($flag);
if (!isset($variables)) {
$variables = array('flag_short', 'flag_long');
}
foreach ($variables as $primary_var) {
foreach (array($primary_var, 'un'. $primary_var) as $var) {
if (isset($flag->$var)) {
$flag->$var = t(filter_xss_admin($flag->$var));
if (strpos($flag->$var, '[') !== FALSE && module_exists('token')) {
if (isset($content_id) && $content_type == 'node') {
$object = node_load($content_id);
$scope = 'node';
}
$flag->$var = token_replace($flag->$var, isset($scope) ? $scope : 'global', isset($object) ? $object : NULL, '[', ']', $options);
}
}
}
}
return $flag;
}
/**
* Implementation of hook_link().
*/
function flag_link($type, $object = NULL, $teaser = FALSE) {
if (!in_array($type, array('node', 'comment')) || !isset($object)) {
return;
}
global $user;
// Anonymous users can't create flags with this system.
if (!$user || !$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);
foreach ($flags as $flag) {
if ($type == 'node') {
if ($teaser && !$flag->show_on_teaser || !$teaser && !$flag->show_on_page) {
continue;
}
}
else if ($type == 'comment') {
if (!$flag->show_on_comment) {
continue;
}
}
// Token replacements.
$flag = flag_process_labels($flag, $type, $id, array('flag_short', 'flag_long', 'flag_message'), array('teaser' => $teaser));
// 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' => theme('flag', $flag, $type, $id, isset($flag_status[$flag->name])),
'html' => TRUE,
);
}
if (isset($links)) {
return $links;
}
}
/**
* Implementation of hook_form_alter().
*/
function flag_form_alter($form_id, &$form) {
global $user;
if ($form_id == 'node_type_form') {
$flags = flag_get_flags('node', $form['#node_type']->type, $user);
foreach ($flags as $flag) {
if ($flag->show_on_form) {
$var = 'flag_'. $flag->name .'_default';
$form['workflow']['flag'][$var] = array(
'#type' => 'checkbox',
'#title' => $flag->flag_short,
'#default_value' => variable_get($var .'_'. $form['#node_type']->type, 0),
'#return_value' => 1,
'#description' => $flag->flag_long,
);
}
}
}
elseif (($form_id == $form['type']['#value'] .'_node_form')) {
if (!$user || !$user->uid) {
return;
}
if ($form['nid']['#value']) {
$flag_status = flag_get_user_flags('node', $form['nid']['#value']);
}
else {
$flag_status = array();
}
$flags = flag_get_flags('node', $form['type']['#value'], $user);
// Filter out flags which need to be included on the node form.
foreach ($flags as $fid => $flag) {
if (!$flag->show_on_form) {
unset($flags[$fid]);
}
}
if (count($flags)) {
$form['flag'] = array(
'#type' => 'fieldset',
'#weight' => 1,
'#tree' => TRUE,
'#title' => t('Flags'),
'#collapsible' => TRUE,
);
}
foreach ($flags as $flag) {
$var = 'flag_'. $flag->name .'_default';
$form['flag'][$flag->name] = array(
'#type' => 'checkbox',
'#title' => $flag->flag_short,
'#description' => $flag->flag_long,
'#default_value' => empty($form['nid']['#value']) ? variable_get($var .'_'. $form['type']['#value'], 0) : isset($flag_status[$flag->name]),
'#return_value' => 1,
);
}
}
}
/**
* Implementation of hook_nodeapi().
*/
function flag_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
global $user;
switch ($op) {
case 'update':
case 'insert':
// 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);
}
}
break;
case 'delete':
db_query("DELETE FROM {flag_content} WHERE content_type = 'node' AND content_id = %d", $node->nid);
db_query("DELETE FROM {flag_counts} WHERE content_type = 'node' AND content_id = %d", $node->nid);
break;
}
}
/**
* Implementation of hook_user().
*/
function flag_user($op, &$edit, &$account, $category = NULL) {
switch ($op) {
case 'delete':
// Remove flags by this user.
db_query("DELETE FROM {flag_content} WHERE uid = %d", $account->uid);
}
}
/**
* Implementation of hook_node_type().
*/
function flag_node_type($op, $info) {
switch ($op) {
case 'delete':
// Remove entry from flaggable content types.
db_query("DELETE FROM {flag_types} WHERE type = '%s'", $info->type);
break;
}
}
// ---------------------------------------------------------------------------
// Administrative pages
/**
* Flag administration page. Display a list of existing flags.
*/
function flag_admin_page() {
$flags = flag_get_flags();
foreach ($flags as $flag) {
$ops = theme('links', array(
'flags_edit' => array('title' => t('edit'), 'href' => "admin/build/flags/edit/". $flag->name),
'flags_delete' => array('title' => t('delete'), 'href' => "admin/build/flags/delete/". $flag->name),
));
$roles = array_flip(array_intersect(array_flip(user_roles()), $flag->roles));
$rows[] = array($flag->name, $flag->content_type, empty($flag->roles) ? ''. t('No roles') .'' : implode(', ', $roles), implode(', ', $flag->types), $ops);
}
$output = theme('table', array(t('Flag'), t('Flag Type'), t('Roles'), t('Node Types'), t('Operations')), $rows);
return $output;
}
/**
* Menu callback for adding a new flag.
*/
function flag_add_page($type = NULL, $name = NULL) {
if (isset($type) && isset($name)) {
if (in_array($type, array('node', 'comment'))) {
return drupal_get_form('flag_form', $name, $type);
}
else {
drupal_goto('admin/build/flags/add');
}
}
else {
return drupal_get_form('flag_add_form');
}
}
/**
* Present a form for creating a new flag, setting the type of flag.
*/
function flag_add_form() {
$form = array();
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Flag name'),
'#description' => t('The machine-name for this flag. It may be up to 32 characters long and my only contain lowercase letters, underscores, and numbers. It will be used in URLs and in all API calls.'),
'#maxlength' => 32,
'#required' => TRUE,
);
$form['type'] = array(
'#type' => 'radios',
'#title' => t('Flag type'),
'#default_value' => 'node',
'#description' => t('The type of content this flag will affect. An individual flag can only affect one type of content. This cannot be changed once the flag is created.'),
'#required' => TRUE,
'#options' => array(
'node' => t('Node'),
'comment' => t('Comment'),
),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
// Reuse the machine-name validation of the flag_form.
$form['#validate']['flag_form_validate'] = array();
return $form;
}
function flag_add_form_submit($form_id, $form_values) {
drupal_goto('admin/build/flags/add/'. $form_values['type'] .'/'. $form_values['name']);
}
/**
* Add/Edit flag page.
*/
function flag_form($name = NULL, $type = NULL) {
if (isset($name)) {
$flag = flag_get_flag($name);
if (empty($flag) && !in_array($type, array('node', 'comment'))) {
drupal_goto('admin/build/flags');
}
if (!empty($flag)) {
$type = $flag->content_type;
}
}
if (isset($flag)) {
drupal_set_title(t('Edit @title flag', array('@title' => $flag->title)));
}
else {
drupal_set_title(t('Add new flag'));
}
$form['fid'] = array(
'#type' => 'value',
'#value' => isset($flag) ? $flag->fid : NULL,
);
$form['content_type'] = array(
'#type' => 'value',
'#value' => isset($flag) ? $flag->content_type : $type,
);
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
'#default_value' => isset($flag) ? $flag->name : $name,
'#description' => t('The machine-name for this flag. It may be up to 32 characters long and my only contain lowercase letters, underscores, and numbers. It will be used in URLs and in all API calls.'),
'#maxlength' => 32,
'#required' => TRUE,
);
if (isset($flag)) {
$form['name']['#description'] .= ' '. t('Change this value only with great care.') .'';
}
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => isset($flag) ? $flag->title : '',
'#description' => t('A short, descriptive title for this flag. It will be used in administrative interfaces to refer to this flag. Some examples could be Bookmarks, Favorites, or Offensive.'),
'#maxlength' => 255,
'#required' => TRUE,
);
$form['flag_short'] = array(
'#type' => 'textfield',
'#title' => t('Flag link text'),
'#default_value' => isset($flag) ? $flag->flag_short : '',
'#description' => t('The text for the "flag this" link for this flag.'),
'#required' => TRUE,
);
$form['flag_long'] = array(
'#type' => 'textfield',
'#title' => t('Flag link description'),
'#default_value' => isset($flag) ? $flag->flag_long : '',
'#description' => t('The description of the "flag this" link. Usually displayed on mouseover.'),
);
$form['flag_message'] = array(
'#type' => 'textfield',
'#title' => t('Flagged message'),
'#default_value' => isset($flag) ? $flag->flag_message : '',
'#description' => t('Message displayed when the user has clicked the "flag this" link. If javascript is enabled, it will be displayed below the link. If not, it will be displayed in the message area.'),
);
$form['unflag_short'] = array(
'#type' => 'textfield',
'#title' => t('Unflag link text'),
'#default_value' => isset($flag) ? $flag->unflag_short : '',
'#description' => t('The text for the "unflag this" link for this flag.'),
'#required' => TRUE,
);
$form['unflag_long'] = array(
'#type' => 'textfield',
'#title' => t('Unflag link description'),
'#default_value' => isset($flag) ? $flag->unflag_long : '',
'#description' => t('The description of the "unflag this" link. Usually displayed on mouseover.'),
);
$form['unflag_message'] = array(
'#type' => 'textfield',
'#title' => t('Unflagged message'),
'#default_value' => isset($flag) ? $flag->unflag_message : '',
'#description' => t('Message displayed when the user has clicked the "unflag this" link. If javascript is enabled, it will be displayed below the link. If not, it will be displayed in the message area.'),
);
if (module_exists('token')) {
$form['token_help'] = array(
'#title' => t('Token replacement'),
'#type' => 'fieldset',
'#description' => t('The above four options may contain the following wildcard replacements. For example, "Mark Link" could be entered as "Add [title] to your flags" or "Add this [type-name] to your flags". These wildcards will be replaced with the appropriate field from the node.') . theme('token_help'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
}
$form['roles'] = array(
'#type' => 'checkboxes',
'#title' => t('Roles that may use this flag'),
'#options' => user_roles(TRUE),
'#default_value' => isset($flag) ? $flag->roles : array(2),
'#required' => TRUE,
'#description' => t('Checking authenticated user will allow all logged-in users to flag content with this flag. Anonymous users may not flag content.'),
);
$form['global'] = array(
'#type' => 'checkbox',
'#title' => t("Global flag"),
'#default_value' => isset($flag) ? $flag->global : 0,
'#description' => t("If checked, flag is considered 'global' and each node is either flagged or not. If unchecked, each user has his or her own flag flag."),
);
$form['types'] = array(
'#type' => 'checkboxes',
'#title' => t('What nodes this flag may be used on'),
'#options' => node_get_types('names'),
'#default_value' => isset($flag) ? $flag->types : array(),
'#description' => t('Check any node types that this flag may be used on. You must check at least one node type.'),
'#required' => TRUE
);
$form['display'] = array(
'#type' => 'fieldset',
'#title' => t('Display options'),
'#description' => t('Flags are usually controlled through links that allow users to toggle their behavior. You can choose how users interact with flags by changing options here.'),
'#tree' => FALSE,
);
if ($type == 'node') {
$form['display']['show_on_teaser'] = array(
'#type' => 'checkbox',
'#title' => t('Display link on node teaser'),
'#default_value' => isset($flag) ? $flag->show_on_teaser : 1,
);
$form['display']['show_on_page'] = array(
'#type' => 'checkbox',
'#title' => t('Display link on node page'),
'#default_value' => isset($flag) ? $flag->show_on_page : 1,
);
$form['display']['show_on_form'] = array(
'#type' => 'checkbox',
'#title' => t('Show checkbox on node edit form'),
'#default_value' => isset($flag) ? $flag->show_on_form : 0,
);
}
if ($type == 'comment') {
$form['display']['show_on_comment'] = array(
'#type' => 'checkbox',
'#title' => t('Show checkbox on comments'),
'#default_value' => isset($flag) ? $flag->show_on_comment : 1,
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Validate required checkboxes on our form.
*
* FAPI has a problem validating #required checkboxes: if previously non-empty
* checkboxes are cleared, FAPI won't detect this.
*/
function flag_form_validate($form_id, $form_values, $form) {
// Fix checkboxes.
foreach (element_children($form) as $field) {
if ($form[$field]['#type'] == 'checkboxes' && $form[$field]['#required'] && !array_filter($form_values[$field])) {
form_error($form[$field], t('!name field is required.', array('!name' => $form[$field]['#title'])));
}
}
// Ensure a safe machine name.
if (!preg_match('/^[a-z0-9_]+$/', $form_values['name'])) {
form_error($form['name'], t('The flag name may only contain lowercase letters, underscores, and numbers.'));
}
// Ensure the machine name is unique.
if (empty($form_values['fid'])) {
$flag = flag_get_flag($form_values['name']);
if (!empty($flag)) {
form_error($form['name'], t('Flag names must be unique. This flag name is already in use.'));
}
}
}
/**
* Add/Edit flag form submit.
*/
function flag_form_submit($form_id, $form_values) {
// Convert options that differ per content-type into an options array.
$option_keys = array(
'show_on_page',
'show_on_teaser',
'show_on_form',
'show_on_comment',
);
$options = array();
foreach ($option_keys as $key) {
if (isset($form_values[$key])) {
$options[$key] = $form_values[$key];
}
}
if ($form_values['fid']) {
$fid = $form_values['fid'];
db_query("UPDATE {flags} SET name = '%s', title = '%s', flag_short = '%s', flag_long = '%s', flag_message = '%s', unflag_short = '%s', unflag_long = '%s', unflag_message = '%s', roles = '%s', global = %d, options = '%s' WHERE fid = %d", $form_values['name'], $form_values['title'], $form_values['flag_short'], $form_values['flag_long'], $form_values['flag_message'], $form_values['unflag_short'], $form_values['unflag_long'], $form_values['unflag_message'], implode(',', array_filter($form_values['roles'])), $form_values['global'], serialize($options), $form_values['fid']);
db_query("DELETE FROM {flag_types} WHERE fid = %d", $form_values['fid']);
}
else {
$fid = db_next_id('{flags}_fid');
db_query("INSERT INTO {flags} (fid, content_type, name, title, flag_short, flag_long, flag_message, unflag_short, unflag_long, unflag_message, roles, global, options) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s')", $fid, $form_values['content_type'], $form_values['name'], $form_values['title'], $form_values['flag_short'], $form_values['flag_long'], $form_values['flag_message'], $form_values['unflag_short'], $form_values['unflag_long'], $form_values['unflag_message'], implode(',', array_filter($form_values['roles'])), $form_values['global'], serialize($options));
}
foreach ($form_values['types'] as $type) {
if ($type) {
db_query("INSERT INTO {flag_types} (fid, type) VALUES (%d, '%s')", $fid, $type);
}
}
drupal_set_message(t('Flag @name has been saved.', array('@name' => $form_values['title'])));
views_invalidate_cache();
return 'admin/build/flags';
}
/**
* Delete flag page.
*/
function flag_delete_confirm($name) {
$flag = flag_get_flag($name);
if (empty($flag)) {
drupal_goto('admin/build/flags');
}
$form['fid'] = array('#type' => 'value', '#value' => $flag->fid);
$form['title'] = array('#type' => 'value', '#value' => $flag->title);
return confirm_form($form,
t('Are you sure you want to delete %title?', array('%title' => $flag->title)),
$_GET['destination'] ? $_GET['destination'] : 'admin/build/flags',
t('This action cannot be undone.'),
t('Delete'), t('Cancel')
);
}
function flag_delete_confirm_submit($form_id, $form_values) {
if ($form_values['confirm']) {
db_query("DELETE FROM {flags} WHERE fid = %d", $form_values['fid']);
db_query("DELETE FROM {flag_content} WHERE fid = %d", $form_values['fid']);
db_query("DELETE FROM {flag_types} WHERE fid = %d", $form_values['fid']);
db_query("DELETE FROM {flag_counts} WHERE fid = %d", $form_values['fid']);
views_invalidate_cache();
}
drupal_set_message(t('Flag @name has been deleted.', array('@name' => $form_values['title'])));
return 'admin/build/flags';
}
/**
* Menu callback for (un)flagging a node.
*
* 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);
$js = isset($_REQUEST['js']);
if (!$result) {
if ($js) {
drupal_set_header('Content-Type: text/javascript; charset=utf-8');
print drupal_to_js(array('status' => FALSE));
exit;
}
else {
drupal_access_denied();
return;
}
}
if ($js) {
drupal_set_header('Content-Type: text/javascript; charset=utf-8');
print drupal_to_js(array('status' => TRUE));
exit;
}
else {
$flag = flag_get_flag($flag_name);
// Token replacements.
if ($content_type == 'node') {
$flag = flag_process_labels($flag, $content_type, $content_id, array('flag_message'));
}
drupal_set_message($flag->{$action .'_message'});
drupal_goto();
}
}
/**
* Action for flagging or unflagging a piece of content.
*/
function flag($action, $flag_name, $content_type, $content_id, $uid = NULL) {
if (!isset($uid)) {
global $user;
$account = $user;
}
else {
$account = user_load(array('uid' => $uid));
}
// Anonymous users can't create flags with this system.
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;
}
if (empty($object)) {
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)) {
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));
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);
}
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());
}
// 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;
}
/**
* Implementation of hook_flag(). Trigger actions if any are available.
*/
function flag_flag($action, $flag_name, $content_type, $content_id, $uid) {
if (module_exists('actions')) {
// TODO: Create a real class for flag actions?
$flag_action = new stdClass();
$flag_action->action = $action;
$flag_action->flag = $flag_name;
$flag_action->content_type = $content_type;
$flag_action->content_id = $content_id;
switch ($content_type) {
case 'node':
$node = node_load($content_id);
$flag_action->content_title = $node->title;
$flag_action->content_url = url('node/'. $node->nid, NULL, NULL, TRUE);
break;
}
$flag = flag_get_flag($flag_name);
$context = (array)$flag_action;
$aids = _actions_get_hook_aids('flag', $action);
foreach ($aids as $aid => $action_info) {
actions_do($aid, $flag, $context);
}
}
}
/**
* Implementation of hook_node_operations().
*
* Add additional options on the admin/build/node page.
*/
function flag_node_operations() {
global $user;
$flags = flag_get_flags(NULL, NULL, $user);
$operations = array();
foreach ($flags as $flag) {
// Token replacements.
$flag = flag_process_labels($flag, NULL, NULL, array('flag_short'));
$operations['flag_'. $flag->name] = array(
'label' => $flag->flag_short,
'callback' => 'flag_nodes',
);
$operations['unflag_'. $flag->name] = array(
'label' => $flag->unflag_short,
'callback' => 'flag_nodes',
);
}
return $operations;
}
/**
* Callback function for hook_node_operations().
*/
function flag_nodes($nodes) {
$matches = array();
preg_match('/((un)?flag)_(.*)/', $_POST['operation'], $matches);
$action = $matches[1];
$name = $matches[3];
foreach ($nodes as $nid) {
flag($action, $name, 'node', $nid);
}
}
/**
* Theme an entire flag link, including JavaScript behaviors.
*
* @param $flag
* The flag object.
* @param $content_type
* The type of content being flagged. Usually "node".
* @param $content_id
* The ID of the content being flagged.
* @param $is_flagged
* Boolean value of the current status of the flag being output.
*/
function theme_flag($flag, $content_type, $content_id, $is_flagged) {
static $added_flags, $js_added;
// Add initial JS/CSS to the page.
if (!isset($js_added)) {
drupal_add_css(drupal_get_path('module', 'flag') .'/flag.css');
drupal_add_js(drupal_get_path('module', 'flag') .'/flag.js');
drupal_add_js(array('flag' => array('cleanUrl' => variable_get('clean_url', 0))), 'setting');
$js_added = TRUE;
}
// Add JavaScript copies of the links so we can swap them out when the user clicks.
if (!(isset($added_flags[$flag->name][$content_type][$content_id]))) {
$flag_html = theme('flag_link', $flag, $content_type, $content_id, 'flag', TRUE);
$unflag_html = theme('flag_link', $flag, $content_type, $content_id, 'unflag', TRUE);
drupal_add_js(array('flag' => array('flags' => array($flag->name => array($content_type .'_'. $content_id => array('flag' => $flag_html, 'unflag' => $unflag_html))))), 'setting');
$added_flags[$flag->name][$content_type][$content_id] = TRUE;
}
return theme('flag_link', $flag, $content_type, $content_id, $is_flagged ? 'unflag' : 'flag');
}
/**
* Theme an individual link and a message after the link is marked.
*
* @param $flag
* The flag object.
* @param $content_type
* The type of content being flagged. Usually "node".
* @param $content_id
* The ID of the content being flagged.
* @param $action
* Either "flag" or "unflag".
* @param $after_flagging
* This function is called for both the link both before and after being
* flagged. If displaying to the user immediately after flagging, this value
* will be boolean TRUE. This is usually used in conjunction with immedate
* JavaScript-based toggling of flags.
*/
function theme_flag_link($flag, $content_type, $content_id, $action, $after_flagging = FALSE) {
$output = '';
$output .= l($flag->{$action .'_short'}, 'flag/'. $action .'/'. $flag->name .'/'. $content_type .'/'. $content_id, array('title' => $flag->{$action .'_long'}, 'class' => $action .' flag-'. $flag->name . ($after_flagging ? ($action == 'flag' ? ' unflagged' : ' flagged') : '')), drupal_get_destination());
if ($after_flagging) {
$output .= ''. $flag->{($action == 'flag' ? 'unflag' : 'flag') .'_message'} .'';
}
$output .= '';
return $output;
}
/**
* Format a string containing a count of items.
*
* _flag_format_plural() is a version of format_plural() which
* accepts the format string as a single argument, where the singular and
* plural forms are separated by pipe. A 'zero' form is allowed as well.
*
* _flag_format_plural() is used where we want the admin, not the
* programmer, to be able to nicely and easily format a number.
*
* If three forms are provided, separated by pipes, then the first is
* considered the zero form and is used if $count is 0. The zero form may
* well be an empty string.
*
* @param $count
* The item count to display.
* @param $format
* The singular, plural, and optionally the zero, forms separated
* by pipe characters.
* @return
* A formatted, translated string.
*
* Examples for $format:
*
* @code
* "@count"
* "1 vote|@count votes"
* "needs voting|1 vote|@count votes"
* "|1 vote|@count votes"
* "|@count|@count"
* @endcode
*/
function _flag_format_plural($count, $format) {
$elements = explode('|', $format ? $format : '@count', 3);
if (count($elements) == 3) {
list($zero, $singular, $plural) = $elements;
}
elseif (count($elements) == 2) {
list($singular, $plural) = $elements;
$zero = NULL;
}
else { // count($elements) == 1
$singular = $plural = $elements[0];
$zero = NULL;
}
if (isset($zero) && intval($count) == 0) {
return $zero;
}
else {
return format_plural(intval($count), $singular, $plural);
}
}
// ---------------------------------------------------------------------------
// Non-Views public API
/**
* Get flag counts for all flags on a node.
*
* @param $content_type
* The content type (usually 'node').
* @param $content_id
* The content ID (usually the node ID).
* @param $reset
* Reset the internal cache and execute the SQL query another time.
*
* @return $flags
* An array of the structure [name] => [number of flags].
*/
function flag_get_counts($content_type, $content_id, $reset = FALSE) {
static $counts;
if (!isset($counts[$content_type][$content_id])) {
$counts[$content_type][$content_id] = array();
$result = db_query("SELECT f.name, fc.count FROM {flags} f LEFT JOIN {flag_counts} fc ON f.fid = fc.fid WHERE fc.content_type = '%s' AND fc.content_id = %d", $content_type, $content_id);
while ($row = db_fetch_object($result)) {
$counts[$content_type][$content_id][$row->name] = $row->count;
}
}
return $counts[$content_type][$content_id];
}
/**
* Load a single flag either by name or by flag ID.
*
* @param $name
* The the flag name.
* @param $fid
* The the flag id.
*/
function flag_get_flag($name = NULL, $fid = NULL) {
$flags = flag_get_flags();
if (isset($fid)) {
return $flags[$fid];
}
elseif (isset($name)) {
foreach ($flags as $flag) {
if ($flag->name == $name) {
return $flag;
}
}
}
}
/**
* List all flags available.
*
* If node type or account are entered, a list of all possible flags will be
* returned.
*
* @param $content_type
* Optional. The type of content for which to load the flags. Usually 'node'.
* @param $content_subtype
* Optional. The node type for which to load the flags.
* @param $account
* Optional. The user accont to filter available flags. If not set, all
* flags for will this node will be returned.
* @param $reset
* Optional. Reset the internal query cache.
*
* @return $flags
* An array of the structure [fid] = flag_object.
*/
function flag_get_flags($content_type = NULL, $content_subtype = NULL, $account = NULL, $reset = FALSE) {
static $flags;
// Retreive a list of all flags, regardless of the parameters.
if (!isset($flags) || $reset) {
$flags = array();
$result = db_query("SELECT f.*, fn.type FROM {flags} f LEFT JOIN {flag_types} fn ON fn.fid = f.fid");
while ($flag = db_fetch_object($result)) {
$flag->roles = empty($flag->roles) ? array() : explode(',', $flag->roles);
foreach ((array)unserialize($flag->options) as $option => $option_value) {
$flag->$option = $option_value;
}
unset($flag->options);
if (!isset($flags[$flag->fid])) {
$flag->types = array($flag->type);
unset($flag->type);
$flags[$flag->fid] = $flag;
}
else {
array_push($flags[$flag->fid]->types, $flag->type);
}
}
}
// Make a variable copy to filter types and account.
$filtered_flags = $flags;
// Filter out flags based on type and subtype.
if (isset($content_type) || isset($content_subtype)) {
foreach ($filtered_flags as $fid => $flag) {
if (!flag_content_enabled($flag, $content_type, $content_subtype)) {
unset($filtered_flags[$fid]);
}
}
}
// Filter out flags based on account permissions.
if (isset($account) && $account->uid != 1) {
foreach ($filtered_flags as $fid => $flag) {
if (!flag_access($flag, $account)) {
unset($filtered_flags[$fid]);
}
}
}
return $filtered_flags;
}
/**
* Find what a user has flagged, either a single node or on the entire site.
*
* @param $content_type
* The type of content that will be retrieved. Usually 'node'.
* @param $content_id
* Optional. The content ID to check for flagging. If none given, all
* content flagged by this user will be returned.
* @param $uid
* Optional. The user ID whose flags we're checking. If none given, the
* current user will be used.
* @param $reset
* Reset the internal cache and execute the SQL query another time.
*
* @return $flags
* If returning a single node's flags, an array of the structure
* [name] => (fid => [fid], uid => [uid] nid => [nid], timestamp => [timestamp])
*
* If returning all nodes, an array of the arrays for each node.
* [nid] => [name] => Object from above.
*
*/
function flag_get_user_flags($content_type, $content_id = NULL, $uid = NULL, $reset = FALSE) {
static $flagged_content;
$uid = !isset($uid) ? $GLOBALS['user']->uid : $uid;
$flags = flag_get_flags($content_type);
if (isset($content_id)) {
if (!isset($flagged_content[$uid][$content_type][$content_id]) || $reset) {
$flagged_content[$uid][$content_type][$content_id] = array();
$result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND content_id = %d AND (uid = %d OR uid = 0)", $content_type, $content_id, $uid);
while ($flag = db_fetch_object($result)) {
$flagged_content[$uid][$content_type][$content_id][$flags[$flag->fid]->name] = $flag;
}
}
return $flagged_content[$uid][$content_type][$content_id];
}
else {
if (!isset($flagged_content[$uid]['all'][$content_type]) || $reset) {
$flagged_content[$uid]['all'][$content_type] = TRUE;
$result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND (uid = %d OR uid = 0)", $content_type, $uid);
while ($flag = db_fetch_object($result)) {
$flagged_content[$uid][$content_type]['all'][$flags[$flag->fid]->name] = $flag;
}
}
return $flagged_content[$uid][$content_type]['all'];
}
}
/**
* Return a list of users who have flagged a piece of content.
*/
function flag_get_content_flags($content_type, $content_id, $reset = FALSE) {
static $content_flags;
if (!isset($content_flags[$content_type][$content_id]) || $reset) {
$flags = flag_get_flags($content_type);
$result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND content_id = %d ORDER BY timestamp DESC", $content_type, $content_id);
while ($flag = db_fetch_object($result)) {
$content_flags[$content_type][$content_id]['users'][$flag->uid][$flags[$flag->fid]->name] = $flag;
}
}
return $content_flags[$content_type][$content_id]['users'];
}