Index: includes/flag.views_default.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/includes/Attic/flag.views_default.inc,v
retrieving revision 1.1.2.5
diff -u -r1.1.2.5 flag.views_default.inc
--- includes/flag.views_default.inc 22 Feb 2009 22:20:39 -0000 1.1.2.5
+++ includes/flag.views_default.inc 28 Sep 2009 01:58:10 -0000
@@ -103,7 +103,7 @@
$access = array(
'type' => 'role',
- 'role' => drupal_map_assoc($flag->roles),
+ 'role' => drupal_map_assoc($flag->roles['flag']),
'perm' => '',
);
Index: includes/flag.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/includes/Attic/flag.admin.inc,v
retrieving revision 1.1.4.2.2.5
diff -u -r1.1.4.2.2.5 flag.admin.inc
--- includes/flag.admin.inc 27 Sep 2009 22:59:01 -0000 1.1.4.2.2.5
+++ includes/flag.admin.inc 28 Sep 2009 01:58:10 -0000
@@ -28,11 +28,11 @@
'flags_delete' => array('title' => t('delete'), 'href' => "admin/build/flags/delete/". $flag->name),
));
- $roles = array_flip(array_intersect(array_flip(user_roles()), $flag->roles));
+ $roles = array_flip(array_intersect(array_flip(user_roles()), $flag->roles['flag']));
$rows[] = array(
$flag->name,
$flag->content_type,
- empty($flag->roles) ? '' . t('No roles') . '' : implode(', ', $roles),
+ empty($flag->roles['flag']) ? '' . t('No roles') . '' : implode(', ', $roles),
$flag->types ? implode(', ', $flag->types) : '-',
$flag->global ? t('Yes') : t('No'),
$ops,
@@ -55,7 +55,7 @@
'flags_enable' => array('title' => t('enable'), 'href' => "admin/build/flags/edit/". $flag->name),
));
- $roles = array_flip(array_intersect(array_flip(user_roles()), $flag->roles));
+ $roles = array_flip(array_intersect(array_flip(user_roles()), $flag->roles['flag']));
$rows[] = array(
$flag->name,
$flag->module,
@@ -201,6 +201,7 @@
'#maxlength' => 32,
'#required' => TRUE,
'#access' => empty($flag->locked['name']),
+ '#weight' => -3,
);
if (!empty($flag->fid)) {
@@ -215,9 +216,24 @@
'#maxlength' => 255,
'#required' => TRUE,
'#access' => empty($flag->locked['title']),
+ '#weight' => -2,
);
- $form['flag_short'] = array(
+ $form['global'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Global flag'),
+ '#default_value' => $flag->global,
+ '#description' => t('If checked, flag is considered "global" and each node is either flagged or not. If unchecked, each user has individual flags on content.'),
+ '#access' => empty($flag->locked['global']),
+ '#weight' => -1,
+ );
+
+ $form['messages'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Messages'),
+ );
+
+ $form['messages']['flag_short'] = array(
'#type' => 'textfield',
'#title' => t('Flag link text'),
'#default_value' => $flag->flag_short,
@@ -226,7 +242,7 @@
'#access' => empty($flag->locked['flag_short']),
);
- $form['flag_long'] = array(
+ $form['messages']['flag_long'] = array(
'#type' => 'textfield',
'#title' => t('Flag link description'),
'#default_value' => $flag->flag_long,
@@ -234,15 +250,7 @@
'#access' => empty($flag->locked['flag_long']),
);
- $form['flag_confirmation'] = array(
- '#type' => 'textfield',
- '#title' => t('Flag confirmation message'),
- '#default_value' => $flag->flag_confirmation,
- '#description' => t('Message displayed if the user has clicked the "flag this" link and confirmation is required. Usually presented in the form of a question such as, "Are you sure you want to flag this content?"'),
- '#access' => empty($flag->locked['flag_confirmation']),
- );
-
- $form['flag_message'] = array(
+ $form['messages']['flag_message'] = array(
'#type' => 'textfield',
'#title' => t('Flagged message'),
'#default_value' => $flag->flag_message,
@@ -250,7 +258,7 @@
'#access' => empty($flag->locked['flag_message']),
);
- $form['unflag_short'] = array(
+ $form['messages']['unflag_short'] = array(
'#type' => 'textfield',
'#title' => t('Unflag link text'),
'#default_value' => $flag->unflag_short,
@@ -259,7 +267,7 @@
'#access' => empty($flag->locked['unflag_short']),
);
- $form['unflag_long'] = array(
+ $form['messages']['unflag_long'] = array(
'#type' => 'textfield',
'#title' => t('Unflag link description'),
'#default_value' => $flag->unflag_long,
@@ -267,15 +275,7 @@
'#access' => empty($flag->locked['unflag_long']),
);
- $form['unflag_confirmation'] = array(
- '#type' => 'textfield',
- '#title' => t('Unflag confirmation message'),
- '#default_value' => $flag->unflag_confirmation,
- '#description' => t('Message displayed if the user has clicked the "unflag this" link and confirmation is required. Usually presented in the form of a question such as, "Are you sure you want to unflag this content?"'),
- '#access' => empty($flag->locked['unflag_confirmation']),
- );
-
- $form['unflag_message'] = array(
+ $form['messages']['unflag_message'] = array(
'#type' => 'textfield',
'#title' => t('Unflagged message'),
'#default_value' => $flag->unflag_message,
@@ -284,7 +284,7 @@
);
if (module_exists('token')) {
- $form['token_help'] = array(
+ $form['messages']['token_help'] = array(
'#title' => t('Token replacement'),
'#type' => 'fieldset',
'#description' => t('The above six 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('flag_token_help', $flag->get_labels_token_types()),
@@ -293,38 +293,21 @@
);
}
else {
- $form['token_help'] = array(
+ $form['messages']['token_help'] = array(
'#value' => '' . t('Note: You don\'t have the Token module installed. If you have it installed, and enabled, you\'ll be able to embed tokens in the six labels above.', array('@token-url' => 'http://drupal.org/project/token')) . '',
);
}
- $form['global'] = array(
- '#type' => 'checkbox',
- '#title' => t('Global flag'),
- '#default_value' => $flag->global,
- '#description' => t('If checked, flag is considered "global" and each node is either flagged or not. If unchecked, each user has individual flags on content.'),
- '#weight' => 1,
- '#access' => empty($flag->locked['global']),
- );
-
- $form['roles'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Roles that may use this flag'),
- '#options' => user_roles(TRUE),
- '#default_value' => $flag->roles,
- '#description' => t('Checking authenticated user will allow all logged-in users to flag content with this flag. Anonymous users may not flag content.'),
- '#weight' => 5,
- '#access' => empty($flag->locked['roles']),
+ $form['access'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Flag access'),
+ '#tree' => FALSE,
+ '#weight' => 10,
);
- // Disabled access breaks checkboxes unless #value is hard coded.
- if (!empty($flag->locked['roles'])) {
- $form['roles']['#value'] = $flag->roles;
- }
-
- $form['types'] = array(
+ $form['access']['types'] = array(
'#type' => 'checkboxes',
- '#title' => t('What nodes this flag may be used on'),
+ '#title' => t('Flaggable content'),
'#options' => array_map('check_plain', node_get_types('names')),
'#default_value' => $flag->types,
'#description' => t('Check any node types that this flag may be used on. You must check at least one node type.'),
@@ -335,9 +318,44 @@
// Disabled access breaks checkboxes unless #value is hard coded.
if (!empty($flag->locked['types'])) {
- $form['types']['#value'] = $flag->types;
+ $form['access']['types']['#value'] = $flag->types;
}
+ $form['access']['roles'] = array(
+ '#title' => t('Roles that may use this flag'),
+ '#access' => empty($flag->locked['roles']),
+ '#description' => t('Users may only unflag content if they have access to flag the content initially. Checking authenticated user will allow access for all logged-in users. Anonymous users may not flag content.'),
+ '#theme' => 'flag_form_roles',
+ '#weight' => -2,
+ );
+ $form['access']['roles']['flag'] = array(
+ '#type' => 'checkboxes',
+ '#options' => user_roles(TRUE),
+ '#default_value' => $flag->roles['flag'],
+ '#parents' => array('roles', 'flag'),
+ );
+ $form['access']['roles']['unflag'] = array(
+ '#type' => 'checkboxes',
+ '#options' => user_roles(TRUE),
+ '#default_value' => $flag->roles['unflag'],
+ '#parents' => array('roles', 'unflag'),
+ );
+
+ // Disabled access breaks checkboxes unless #value is hard coded.
+ if (!empty($flag->locked['roles'])) {
+ $form['access']['roles']['#type'] = 'value';
+ $form['access']['roles']['#value'] = $flag->roles;
+ }
+
+ $form['access']['unflag_denied_text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Unflag not allowed text'),
+ '#default_value' => $flag->unflag_denied_text,
+ '#description' => t('If a user is allowed to flag but not unflag, this text will be displayed after flagging. Often this is the past-tense of the link text, such as "flagged".'),
+ '#access' => empty($flag->locked['unflag_denied_text']),
+ '#weight' => -1,
+ );
+
$form['display'] = array(
'#type' => 'fieldset',
'#title' => t('Display options'),
@@ -351,12 +369,37 @@
'#title' => t('Link type'),
'#options' => _flag_link_type_options(),
'#option_descriptions' => _flag_link_type_descriptions(),
- '#after_build' => array('flag_expand_option_description', 'flag_check_link_types'),
+ '#flag_link_fields' => _flag_link_type_fields(),
+ '#after_build' => array('flag_expand_link_option', 'flag_check_link_types'),
'#default_value' => $flag->link_type,
'#weight' => 2,
'#access' => empty($flag->locked['link_type']),
);
+ $form['link_options'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Link options'),
+ '#description' => t('The selected link type may require these additional settings.'),
+ '#attributes' => array('id' => 'link-options'),
+ '#weight' => 21,
+ );
+
+ $form['link_options']['flag_confirmation'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Flag confirmation message'),
+ '#default_value' => $flag->flag_confirmation,
+ '#description' => t('Message displayed if the user has clicked the "flag this" link and confirmation is required. Usually presented in the form of a question such as, "Are you sure you want to flag this content?"'),
+ '#access' => empty($flag->locked['flag_confirmation']),
+ );
+
+ $form['link_options']['unflag_confirmation'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Unflag confirmation message'),
+ '#default_value' => $flag->unflag_confirmation,
+ '#description' => t('Message displayed if the user has clicked the "unflag this" link and confirmation is required. Usually presented in the form of a question such as, "Are you sure you want to unflag this content?"'),
+ '#access' => empty($flag->locked['unflag_confirmation']),
+ );
+
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
@@ -410,6 +453,36 @@
}
/**
+ * Output the access options for roles in a table.
+ */
+function theme_flag_form_roles($element) {
+ drupal_add_css(drupal_get_path('module', 'flag') . '/theme/flag-admin.css', 'module', 'all', FALSE);
+ drupal_add_js(drupal_get_path('module', 'flag') . '/theme/flag-admin.js', 'module', 'header', FALSE, TRUE, FALSE);
+
+ $header = array(
+ array('class' => 'checkbox', 'data' => t('Flag')),
+ array('class' => 'checkbox', 'data' => t('Unflag')),
+ t('Role'),
+ );
+ $rows = array();
+ foreach (element_children($element['flag']) as $role) {
+ $row = array();
+ $role_name = $element['flag'][$role]['#title'];
+ unset($element['flag'][$role]['#title']);
+ unset($element['unflag'][$role]['#title']);
+ $element['flag'][$role]['#attributes']['class'] = 'flag-access';
+ $element['unflag'][$role]['#attributes']['class'] = 'unflag-access';
+ $row[] = array('class' => 'checkbox', 'data' => drupal_render($element['flag'][$role]));
+ $row[] = array('class' => 'checkbox', 'data' => drupal_render($element['unflag'][$role]));
+ $row[] = $role_name;
+ $rows[] = $row;
+ }
+
+ $element['#children'] = theme('table', $header, $rows, array('class' => 'flag-admin-table', 'id' => 'flag-roles'));
+ return theme('form_element', $element, $element['#children']);
+}
+
+/**
* Delete flag page.
*/
function flag_delete_confirm(&$form_state, $name) {
@@ -442,12 +515,21 @@
/**
* FormAPI after_build function to add descriptions to radio buttons.
*/
-function flag_expand_option_description($element) {
+function flag_expand_link_option($element) {
+ drupal_add_js(drupal_get_path('module', 'flag') . '/theme/flag-admin.js', 'module', 'header', FALSE, TRUE, FALSE);
+
foreach (element_children($element) as $key) {
+ // Add a description to the link option.
if (isset($element['#option_descriptions'][$key])) {
$element[$key]['#description'] = $element['#option_descriptions'][$key];
}
+ // Add a list of fields dependent on this link type using the rel attribute.
+ if (isset($element['#flag_link_fields'][$key])) {
+ $element[$key]['#attributes']['rel'] = implode(' ', $element['#flag_link_fields'][$key]);
+ }
}
+ $element['#attributes']['class'] = 'flag-link-options';
+
return $element;
}
Index: flag.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.install,v
retrieving revision 1.2.2.32.2.1
diff -u -r1.2.2.32.2.1 flag.install
--- flag.install 14 Sep 2009 19:11:10 -0000 1.2.2.32.2.1
+++ flag.install 28 Sep 2009 01:58:09 -0000
@@ -453,6 +453,29 @@
return array();
}
+/**
+ * Convert role access to have separate "flag" and "unflag" permissions.
+ */
+function flag_update_6200() {
+ $ret = array();
+
+ if (db_column_exists('flags', 'roles')) {
+ $result = db_query('SELECT * FROM {flags}');
+ while ($flag = db_fetch_object($result)) {
+ $roles = array_filter(explode(',', $flag->roles));
+ $options = unserialize($flag->options);
+ $options['roles'] = array(
+ 'flag' => $roles,
+ 'unflag' => $roles,
+ );
+ db_query("UPDATE {flags} SET options = '%s' WHERE fid = %d", serialize($options), $flag->fid);
+ }
+ db_drop_field($ret, 'flags', 'roles');
+ }
+
+ return $ret;
+}
+
// This is a replacement for update_sql(). The latter doesn't support placeholders.
function _flag_update_sql($sql) {
$args = func_get_args();
Index: flag.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.module,v
retrieving revision 1.11.2.72.2.12
diff -u -r1.11.2.72.2.12 flag.module
--- flag.module 28 Sep 2009 01:21:54 -0000 1.11.2.72.2.12
+++ flag.module 28 Sep 2009 01:58:09 -0000
@@ -113,7 +113,7 @@
* See documentation for $flag->user_access().
*/
function flag_access($flag, $account = NULL) {
- return $flag->user_access($account);
+ return $flag->user_access('flag', $account);
}
/**
@@ -154,8 +154,10 @@
// Flag is not configured to show its link here.
continue;
}
- if (!$flag->access($content_id)) {
- // User has no permission to use this flag or flag does not apply to this content.
+ if (!$flag->access($content_id) && (!$flag->is_flagged($content_id) || !$flag->access($content_id, 'flag'))) {
+ // User has no permission to use this flag or flag does not apply to this
+ // content. The link is not skipped if the user has "flag" access but
+ // not "unflag" access (this way the unflag denied message is shown).
continue;
}
@@ -192,7 +194,7 @@
function flag_flag_link(&$flag, $action, $content_id) {
$token = flag_get_token($content_id);
return array(
- 'href' => "flag/". ($flag->link_type == 'confirm' ? 'confirm/' : '') ."$action/$flag->name/$content_id",
+ 'href' => 'flag/'. ($flag->link_type == 'confirm' ? 'confirm/' : '') ."$action/$flag->name/$content_id",
'query' => drupal_get_destination() . ($flag->link_type == 'confirm' ? '' : '&token='. $token),
);
}
@@ -213,6 +215,10 @@
'confirm' => array(
'title' => t('Confirmation form'),
'description' => t('The user will be taken to a confirmation form on a separate page to confirm the flag.'),
+ 'options' => array(
+ 'flag_confirmation' => '',
+ 'unflag_confirmation' => '',
+ ),
),
);
}
@@ -599,6 +605,46 @@
}
/**
+ * Implementation of hook_flag_access().
+ */
+function flag_flag_access($flag, $content_id, $action, $account) {
+ // Restrict access by authorship. It's important that TRUE is never returned
+ // here, otherwise we'd grant permission even if other modules denied access.
+ if ($flag->content_type == 'node') {
+ $node = node_load($content_id);
+ if ($flag->access_author == 'own' && $node->uid != $account->uid) {
+ return FALSE;
+ }
+ elseif ($flag->access_author == 'others' && $node->uid == $account->uid) {
+ return FALSE;
+ }
+ }
+}
+
+/**
+ * Implementation of hook_flag_access_multiple().
+ */
+function flag_flag_access_multiple($flag, $content_ids, $account) {
+ if ($flag->content_type == 'node') {
+ // Restrict access by authorship. This is similar to flag_flag_access()
+ // above, but returns an array of 'nid' => $access values. Similarly, we
+ // should never return TRUE in any of these access values, only FALSE if we
+ // want to deny access, or use the current access value provided by Flag.
+ $nids = implode(',', array_map('intval', array_keys($content_ids)));
+ $placeholders = implode(',', array_fill(0, sizeof($flag->types), "'%s'"));
+ $result = db_query("SELECT nid, uid FROM {node} WHERE nid IN ($nids) AND type in ($placeholders)", $flag->types);
+ while ($row = db_fetch_object($result)) {
+ if ($flag->access_author == 'own') {
+ $passed[$row->nid] = $row->uid != $account->uid ? FALSE : $passed[$row->nid];
+ }
+ elseif ($flag->access_author == 'others') {
+ $passed[$row->nid] = $row->uid == $account->uid ? FALSE : $passed[$row->nid];
+ }
+ }
+ return $passed;
+ }
+}
+/**
* Trim a flag to a certain size.
*
* @param $fid
@@ -691,6 +737,9 @@
'flag_admin_page' => array(
'arguments' => array('flags' => NULL, 'default_flags' => NULL),
),
+ 'flag_form_roles' => array(
+ 'arguments' => array('element' => NULL),
+ ),
'flag_rules_radios' => array(
'arguments' => array(),
),
@@ -720,17 +769,18 @@
$content_id = $variables['content_id'];
// Generate the link URL.
- $link_types = flag_get_link_types();
- if (!isset($link_types[$flag->link_type])) {
- // Provide a fallback if the link type no longer exists.
- $flag->link_type = 'normal';
- }
- $link_type_module = $link_types[$flag->link_type]['module'];
- $link = module_invoke($link_type_module, 'flag_link', $flag, $action, $content_id);
+ $link_type = $flag->get_link_type();
+ $link = module_invoke($link_type['module'], 'flag_link', $flag, $action, $content_id);
if (isset($link['title']) && empty($link['html'])) {
$link['title'] = check_plain($link['title']);
}
+ // Replace the link with the access denied text if unable to flag.
+ if ($action == 'unflag' && !$flag->access($content_id, 'unflag')) {
+ $link['title'] = $flag->get_label('unflag_denied_text');
+ unset($link['href']);
+ }
+
if ($flag->link_type == 'toggle' && $first_time) {
$variables['setup'] = $first_time;
$first_time = FALSE;
@@ -739,7 +789,7 @@
$variables['setup'] = FALSE;
}
- $variables['link_href'] = check_url(url($link['href'], $link));
+ $variables['link_href'] = isset($link['href']) ? check_url(url($link['href'], $link)) : FALSE;
$variables['link_text'] = isset($link['title']) ? $link['title'] : strip_tags($flag->get_label($action . '_short', $content_id), '
');
$variables['link_title'] = isset($link['attributes']['title']) ? check_plain($link['attributes']['title']) : check_plain(strip_tags($flag->get_label($action . '_long', $content_id)));
$variables['flag_name_css'] = str_replace('_', '-', $flag->name);
@@ -853,6 +903,18 @@
return $options;
}
+/**
+ * Return an array of flag link fields that are dependent on a link type.
+ */
+function _flag_link_type_fields() {
+ $options = array();
+ $types = flag_get_link_types();
+ foreach ($types as $type_name => $type) {
+ $options[$type_name] = array_keys($type['options']);
+ }
+ return $options;
+}
+
// ---------------------------------------------------------------------------
// Non-Views public API
@@ -1218,13 +1280,16 @@
foreach (module_implements('flag_link_types') as $module) {
$module_types = module_invoke($module, 'flag_link_types');
foreach ($module_types as $type_name => $info) {
- $link_types[$type_name] = array(
- 'module' => $module,
- 'title' => $info['title'],
- 'description' => $info['description']
+ $link_types[$type_name] = $info;
+ $link_types[$type_name]['module'] = $module;
+ $link_types[$type_name] += array(
+ 'title' => '',
+ 'description' => '',
+ 'options' => array(),
);
}
}
+ drupal_alter('flag_link_types', $link_types);
}
return $link_types;
Index: flag.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.inc,v
retrieving revision 1.1.2.30.2.7
diff -u -r1.1.2.30.2.7 flag.inc
--- flag.inc 27 Sep 2009 22:59:00 -0000 1.1.2.30.2.7
+++ flag.inc 28 Sep 2009 01:58:09 -0000
@@ -109,7 +109,6 @@
// 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();
@@ -136,7 +135,10 @@
// ...but skip the following two.
unset($flag->options, $flag->type);
- $options = (array)unserialize($row->options);
+ // 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;
@@ -147,8 +149,6 @@
$flag->types[] = $row->type;
}
- $flag->roles = empty($row->roles) ? array() : explode(',', $row->roles);
-
return $flag;
}
@@ -189,13 +189,21 @@
'flag_short' => '',
'flag_long' => '',
'flag_message' => '',
- 'flag_confirmation' => '',
'unflag_short' => '',
'unflag_long' => '',
'unflag_message' => '',
- 'unflag_confirmation' => '',
+ 'unflag_denied_text' => '',
'link_type' => 'toggle',
+ 'roles' => array(
+ 'flag' => array(DRUPAL_AUTHENTICATED_RID),
+ 'unflag' => array(DRUPAL_AUTHENTICATED_RID),
+ ),
);
+
+ // 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', $this, $options);
return $options;
@@ -229,7 +237,8 @@
$this->$field = $value;
}
// But checkboxes need some massaging:
- $this->roles = array_values(array_filter($this->roles));
+ $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);
@@ -242,9 +251,17 @@
* A list of errors encountered while validating this flag's options.
*/
function validate() {
- return $this->validate_name();
+ // 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.
+ */
function validate_name() {
$errors = array();
@@ -268,6 +285,39 @@
}
/**
+ * 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) {
@@ -335,13 +385,16 @@
/**
* Returns TRUE if 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($account = NULL) {
+ function user_access($action = 'flag', $account = NULL) {
if (!isset($account)) {
$account = $GLOBALS['user'];
}
@@ -351,8 +404,8 @@
return FALSE;
}
- $matched_roles = array_intersect($this->roles, array_keys($account->roles));
- return !empty($matched_roles) || empty($this->roles) || $account->uid == 1;
+ $matched_roles = array_intersect($this->roles[$action], array_keys($account->roles));
+ return !empty($matched_roles) || !empty($this->roles[$action]) || $account->uid == 1;
}
/**
@@ -377,19 +430,23 @@
return FALSE;
}
- // Allow modules to disallow (or allow) access to flagging.
if (!isset($action)) {
$action = $this->is_flagged($content_id, $account->uid) ? 'unflag' : 'flag';
}
+
+ // Base initial access on the user's basic permission to use this flag.
+ $access = $this->user_access($action, $account);
+
+ // 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 $access) {
- if (isset($access)) {
- return $access;
+
+ foreach ($access_array as $set_access) {
+ if (isset($set_access)) {
+ $access = $set_access;
}
}
- // Fall back on the flag's user-specific access check.
- return $this->user_access($account);
+ return $access;
}
/**
@@ -618,6 +675,14 @@
}
/**
+ * Get the link type for this flag.
+ */
+ function get_link_type() {
+ $link_types = flag_get_link_types();
+ return 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 regognized
* by default, so derived classes should override this method to add all
* token contexts they understand.
@@ -771,7 +836,7 @@
* 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("UPDATE {flags} SET name = '%s', title = '%s', global = %d, options = '%s' WHERE fid = %d", $this->name, $this->title, $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);
@@ -782,16 +847,8 @@
* 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());
- }
+ db_query("INSERT INTO {flags} (content_type, name, title, global, options) VALUES ('%s', '%s', '%s', %d, '%s')", $this->content_type, $this->name, $this->title, $this->global, $this->get_serialized_options());
+ $this->fid = db_last_insert_id('flags', 'fid');
foreach ($this->types as $type) {
db_query("INSERT INTO {flag_types} (fid, type) VALUES (%d, '%s')", $this->fid, $type);
}
@@ -881,6 +938,7 @@
'show_on_page' => TRUE,
'show_on_teaser' => TRUE,
'show_on_form' => FALSE,
+ 'access_author' => '',
'i18n' => 0,
);
return $options;
@@ -902,6 +960,19 @@
'#access' => module_exists('translation_helpers'),
'#weight' => 5,
);
+
+ $form['access']['access_author'] = array(
+ '#type' => 'radios',
+ '#title' => t('Flag access by content authorship'),
+ '#options' => array(
+ '' => t('No additional restrictions'),
+ 'own' => t('Users may only flag content they own'),
+ 'others' => t('Users may only flag content of others'),
+ ),
+ '#default_value' => $this->access_author,
+ '#description' => t("Restrict access to this flag based on the user's ownership of the content. Users must also have access to the flag through the role settings."),
+ );
+
$form['display']['show_on_teaser'] = array(
'#type' => 'checkbox',
'#title' => t('Display link on node teaser'),
@@ -939,16 +1010,15 @@
$account = $GLOBALS['user'];
}
- if (!$this->user_access($account)) {
- // User has no permission to use this flag.
- return FALSE;
- }
$passed = array();
$nids = implode(',', array_map('intval', array_keys($content_ids)));
$placeholders = implode(',', array_fill(0, sizeof($this->types), "'%s'"));
$result = db_query("SELECT nid FROM {node} WHERE nid IN ($nids) AND type in ($placeholders)", $this->types);
while ($row = db_fetch_object($result)) {
- $passed[$row->nid] = $content_ids[$row->nid] == 'flag' ? FALSE : TRUE;
+ // First check basic user access for this action.
+ $passed[$row->nid] = $this->user_access($content_ids[$row->nid], $account);
+
+ // Allow other modules to modify access.
$access_array = module_invoke_all('flag_access_multiple', $this, $content_ids, $account);
foreach ($access_array as $access) {
if (isset($access)) {
@@ -1099,16 +1169,15 @@
$account = $GLOBALS['user'];
}
- if (!$this->user_access($account)) {
- // User has no permission to use this flag.
- return FALSE;
- }
$passed = array();
$content_ids = implode(',', array_map('intval', array_keys($content_ids)));
$placeholders = implode(',', array_fill(0, sizeof($this->types), "'%s'"));
$result = db_query("SELECT cid FROM {comments} c INNER JOIN {node} n ON c.nid = n.nid WHERE cid IN ($content_ids) and n.type IN ($placeholders)", $this->types);
while ($row = db_fetch_object($result)) {
- $passed[$row->cid] = TRUE;
+ // First check basic user access for this action.
+ $passed[$row->cid] = $this->user_access($content_ids[$row->cid], $account);
+
+ // Allow other modules to modify access.
$access_array = module_invoke_all('flag_access_multiple', $this, $content_ids, $account);
foreach ($access_array as $access) {
if (isset($access)) {
@@ -1197,17 +1266,25 @@
$options = parent::options();
$options += array(
'show_on_profile' => TRUE,
+ 'access_uid' => '',
);
return $options;
}
function options_form(&$form) {
parent::options_form($form);
- $form['types'] = array(
- // A user flag doesn't support node types. (Maybe will support roles instead, in the future.)
+ $form['access']['types'] = array(
+ // A user flag doesn't support node types.
+ // TODO: Maybe support roles instead of node types.
'#type' => 'value',
'#value' => array(0 => 0),
);
+ $form['access']['access_uid'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Users may flag themselves'),
+ '#description' => t('Disabling this option may be useful when setting up a "friend" flag, when a user flagging themself does not make sense.'),
+ '#default_value' => $this->access_uid ? 0 : 1,
+ );
$form['display']['show_on_profile'] = array(
'#type' => 'checkbox',
'#title' => t('Display link on user profile page'),
@@ -1216,6 +1293,13 @@
);
}
+ function form_input($form_values) {
+ parent::form_input($form_values);
+ // The access_uid value is intentionally backwards from the UI, to avoid
+ // confusion caused by checking a box to disable a feature.
+ $this->access_uid = empty($form_values['access_uid']) ? 'others' : '';
+ }
+
function _load_content($content_id) {
return user_load(array('uid' => $content_id));
}
@@ -1229,20 +1313,41 @@
return FALSE;
}
+ function access($content_id, $action = NULL, $account = NULL) {
+ $access = parent::access($content_id, $action, $account);
+ $account = isset($account) ? $account : $GLOBALS['user'];
+
+ // Prevent users from flagging themselves.
+ if ($this->access_uid == 'others' && $content_id == $account->uid) {
+ $access = FALSE;
+ }
+
+ return $access;
+ }
+
function access_multiple($content_ids, $account = NULL) {
if (!isset($account)) {
$account = $GLOBALS['user'];
}
- if (!$this->user_access($account)) {
- // User has no permission to use this flag.
- return FALSE;
- }
// This user flag doesn't currently support subtypes so all users are
// applicable for flagging.
$passed = array();
foreach (array_keys($content_ids) as $uid) {
- $passed[$uid] = (bool) $uid; // Exclude anonymous.
+ // First check basic user access for this action.
+ $passed[$uid] = $this->user_access($content_ids[$uid], $account);
+
+ // Exclude anonymous.
+ if ($uid == 0) {
+ $passed[$uid] = FALSE;
+ }
+
+ // Prevent users from flagging themselves.
+ if ($this->access_uid == 'others' && $uid == $account->uid) {
+ $passed[$uid] = FALSE;
+ }
+
+ // Allow other modules to modify access.
$access_array = module_invoke_all('flag_access_multiple', $this, $content_ids, $account);
foreach ($access_array as $access) {
if (isset($access)) {
Index: theme/flag.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/theme/Attic/flag.tpl.php,v
retrieving revision 1.1.2.7
diff -u -r1.1.2.7 flag.tpl.php
--- theme/flag.tpl.php 17 Mar 2009 02:10:30 -0000 1.1.2.7
+++ theme/flag.tpl.php 28 Sep 2009 01:58:10 -0000
@@ -40,11 +40,15 @@
drupal_add_js(drupal_get_path('module', 'flag') .'/theme/flag.js');
}
?>
-
-
+
+
+
+
+
+
-
-
+
+
Index: tests/flag.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/tests/Attic/flag.test,v
retrieving revision 1.1.2.1
diff -u -r1.1.2.1 flag.test
--- tests/flag.test 22 Feb 2009 21:32:47 -0000 1.1.2.1
+++ tests/flag.test 28 Sep 2009 01:58:10 -0000
@@ -36,13 +36,12 @@
'title' => $this->randomName(),
'flag_short' => 'flag short [nid]',
'flag_long' => 'flag long [nid]',
- 'flag_confirmation' => 'flag confirm [nid]',
'flag_message' => 'flag message [nid]',
'unflag_short' => 'unflag short [nid]',
'unflag_long' => 'unflag long [nid]',
- 'unflag_confirmation' => 'unflag confirm [nid]',
'unflag_message' => 'unflag message [nid]',
- 'roles[2]' => TRUE,
+ 'roles[flag][2]' => TRUE,
+ 'roles[unflag][2]' => TRUE,
'types[story]' => FALSE,
'types[page]' => TRUE,
'show_on_teaser' => FALSE,
@@ -51,9 +50,9 @@
'link_type' => 'toggle',
);
$saved = $edit;
- $saved['roles'] = array(2);
+ $saved['roles'] = array('flag' => array(2), 'unflag' => array(2));
$saved['types'] = array('page');
- unset($saved['roles[2]'], $saved['types[story]'], $saved['types[page]']);
+ unset($saved['roles[flag][2]'], $saved['roles[unflag][2]'], $saved['types[story]'], $saved['types[page]']);
$this->drupalPost('admin/build/flags/add/node/' . $edit['name'], $edit, t('Submit'));
@@ -73,13 +72,12 @@
'title' => $this->randomName(),
'flag_short' => 'flag 2 short [nid]',
'flag_long' => 'flag 2 long [nid]',
- 'flag_confirmation' => 'flag 2 confirm [nid]',
'flag_message' => 'flag 2 message [nid]',
'unflag_short' => 'unflag 2 short [nid]',
'unflag_long' => 'unflag 2 long [nid]',
- 'unflag_confirmation' => 'unflag 2 confirm [nid]',
'unflag_message' => 'unflag 2 message [nid]',
- 'roles[2]' => TRUE,
+ 'roles[flag][2]' => TRUE,
+ 'roles[unflag][2]' => TRUE,
'types[story]' => TRUE,
'types[page]' => FALSE,
'show_on_teaser' => TRUE,
@@ -88,9 +86,9 @@
'link_type' => 'normal',
);
$saved = $edit;
- $saved['roles'] = array(2);
+ $saved['roles'] = array('flag' => array(2), 'unflag' => array(2));
$saved['types'] = array('story');
- unset($saved['roles[2]'], $saved['types[story]'], $saved['types[page]']);
+ unset($saved['roles[flag][2]'], $saved['roles[unflag][2]'], $saved['types[story]'], $saved['types[page]']);
$this->drupalPost('admin/build/flags/edit/' . $flag->name, $edit, t('Submit'));
Index: theme/flag-admin.css
===================================================================
RCS file: theme/flag-admin.css
diff -N theme/flag-admin.css
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ theme/flag-admin.css 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,15 @@
+/* $Id$ */
+
+table.flag-admin-table {
+ width: auto;
+ margin: 0;
+}
+table.flag-admin-table th {
+ font-weight: normal;
+ padding-left: 2em;
+ padding-right: 2em;
+}
+table.flag-admin-table td {
+ padding-left: 2em;
+ padding-right: 2em;
+}
Index: theme/flag-admin.js
===================================================================
RCS file: theme/flag-admin.js
diff -N theme/flag-admin.js
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ theme/flag-admin.js 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,67 @@
+// $Id$
+
+/**
+ * Behavior to disable the "unflag" option if "flag" is not available.
+ */
+Drupal.behaviors.flagRoles = function(context) {
+ $('#flag-roles input.flag-access', context).change(function() {
+ var unflagCheckbox = $(this).parents('tr:first').find('input.unflag-access').get(0);
+ if (this.checked) {
+ // If "flag" is available, restore the state of the "unflag" checkbox.
+ unflagCheckbox.disabled = false;
+ if (typeof(unflagCheckbox.previousFlagState) != 'undefined') {
+ unflagCheckbox.checked = unflagCheckbox.previousFlagState;
+ }
+ else {
+ unflagCheckbox.checked = true;
+ }
+ }
+ else {
+ // Remember if the "unflag" option was checked or unchecked, then disable.
+ unflagCheckbox.previousFlagState = unflagCheckbox.checked;
+ unflagCheckbox.disabled = true;
+ unflagCheckbox.checked = false;
+ }
+ });
+
+ $('#flag-roles input.unflag-access', context).change(function() {
+ if ($(this).parents('tr:first').find('input.unflag-access:checked:not(:disabled)').size() > 0) {
+ $('#edit-unflag-denied-text-wrapper').slideUp();
+ }
+ else {
+ $('#edit-unflag-denied-text-wrapper').slideDown();
+ }
+ });
+
+ // Hide the link options by default if needed.
+ if ($('#flag-roles input.unflag-access:checked:not(:disabled)').size() > 0) {
+ $('#edit-unflag-denied-text-wrapper').css('display', 'none');
+ }
+};
+
+
+/**
+ * Behavior to make link options dependent on the link radio button.
+ */
+Drupal.behaviors.flagLinkOptions = function(context) {
+ $('.flag-link-options input.form-radio', context).change(function() {
+ var radioButton = this;
+ $('#link-options').slideUp(function() {
+ $('#link-options input').each(function() {
+ $(this).parents('.form-item:first').css('display', 'none');
+ });
+ var linkOptionFields = $(radioButton).attr('rel');
+ if (linkOptionFields) {
+ linkOptionFields = linkOptionFields.split(' ');
+ for (var n in linkOptionFields) {
+ $('#link-options input[name=' + linkOptionFields[n] + ']').parents('.form-item:first').css('display', 'block');
+ }
+ $('#link-options').slideDown();
+ }
+ });
+ });
+ // Hide the link options by default if needed.
+ if (!$('.flag-link-options input.form-radio:checked').attr('rel')) {
+ $('#link-options').css('display', 'none');
+ }
+};