Commit 400ff386 authored by amitaibu's avatar amitaibu

Better validate group audience fields.

parent e43e7c4c
......@@ -125,13 +125,28 @@ function og_field_widget_form(&$form, &$form_state, $field, $instance, $langcode
}
}
}
else {
// Non-admin user.
$mocked_instance_other_groups = $mocked_instance;
$mocked_instance_other_groups['field_mode'] = 'admin';
if ($other_groups_ids && $valid_ids = entityreference_get_selection_handler($field, $mocked_instance_other_groups, $entity_type, $dummy_entity)->validateReferencableEntities($other_groups_ids)) {
foreach ($valid_ids as $id) {
$element['#other_groups_ids'][] = array('target_id' => $id);
elseif ($other_groups_ids) {
foreach ($other_groups_ids as $id) {
$element['#other_groups_ids'][] = array(
'target_id' => $id,
'field_mode' => 'admin',
);
}
if (!empty($dummy_entity->{$field_name}[$langcode])) {
// Non-admin user.
$ids = array();
foreach ($dummy_entity->{$field_name}[$langcode] as $delta => $value) {
$id = $value['target_id'];
if (!in_array($id, $other_groups_ids)) {
$ids[] = $id;
}
}
// Rekey the field items.
$dummy_entity->{$field_name}[$langcode] = array();
foreach ($ids as $id) {
$dummy_entity->{$field_name}[$langcode][] = array('target_id' => $id);
}
}
}
......@@ -141,7 +156,7 @@ function og_field_widget_form(&$form, &$form_state, $field, $instance, $langcode
// Form is "fresh" (i.e. not call from field_add_more_submit()), so
// re-set the items-count, to show the correct amount for the mocked
// instance.
$dummy_form_state['field'][$field_name][LANGUAGE_NONE]['items_count'] = !empty($dummy_entity->{$field_name}[LANGUAGE_NONE]) ? count($dummy_entity->{$field_name}[LANGUAGE_NONE]) : 0;
$dummy_form_state['field'][$field_name][$langcode]['items_count'] = !empty($dummy_entity->{$field_name}[$langcode]) ? count($dummy_entity->{$field_name}[$langcode]) : 0;
}
$new_element = ctools_field_invoke_field($mocked_instance, 'form', $entity_type, $dummy_entity, $form, $dummy_form_state, array('default' => TRUE));
......@@ -172,6 +187,7 @@ function og_field_widget_form(&$form, &$form_state, $field, $instance, $langcode
}
$form['#after_build']['og'] = 'og_complex_widget_after_build';
$form['#validate'][] = 'og_validate_widgets';
return $element;
}
......@@ -204,6 +220,31 @@ function _og_field_widget_replace_autocomplete_path(&$element, $field_mode) {
}
}
/**
* Register group audience field related form errors.
*
* @param $field_name
* The group audience field
* @param $errors
* Array with errors.
*
* @return
* Return the cached values.
*
* @see og_validate_widgets()
* @see OgBehaviorHandler::validate()
*/
function og_field_widget_register_errors($field_name = NULL, $errors = NULL) {
$cache = &drupal_static(__FUNCTION__, array());
if (!empty($field_name)) {
$cache[$field_name] = $errors;
}
return $cache;
}
/**
* Property info alter; Change mocked field to be non-required.
*/
......@@ -237,19 +278,26 @@ function og_get_mocked_instance($instance, $field_mode) {
*/
function og_complex_widget_element_validate($element, &$form_state, $form) {
$subform = drupal_array_get_nested_value($form_state['values'], $element['#array_parents']);
$ids = $element['#other_groups_ids'];
foreach (array('default', 'admin') as $type) {
if (empty($subform[$type])) {
$ids = array();
foreach (array('default', 'admin') as $field_mode) {
if (empty($subform[$field_mode])) {
continue;
}
foreach ($subform[$type] as $delta => $value) {
foreach ($subform[$field_mode] as $value) {
if (!empty($value['target_id']) && is_numeric($value['target_id'])) {
$ids[] = array('target_id' => $value['target_id']);
$ids[] = array(
'target_id' => $value['target_id'],
// Add the field mode so we can later validate it in
// OgBehaviorHandler::validate()
'field_mode' => $field_mode,
);
}
}
}
$ids = array_merge($ids, $element['#other_groups_ids']);
// Set the form values by directly using drupal_array_set_nested_values(),
// which allows us to control the element parents. In this case we cut off the
// last element that contains the delta 0, as $ids is already keyed with
......@@ -265,6 +313,26 @@ function og_complex_widget_element_validate($element, &$form_state, $form) {
}
}
/**
* Validate handler; Assert group audience fields reference valid groups.
*
* @see field_default_form_errors().
*/
function og_validate_widgets($form, &$form_state) {
if (!$errors = og_field_widget_register_errors()) {
return;
}
foreach ($errors as $field_name => $field_modes) {
foreach ($field_modes as $field_mode => $error_items) {
foreach ($error_items as $error_item) {
$element = $form[$field_name][LANGUAGE_NONE][0][$field_mode];
form_error($element, $error_item['message']);
}
}
}
}
/**
* After build; Remove the "Add more" button.
*
......
......@@ -717,19 +717,68 @@ function og_field_delete_instance($instance) {
}
/**
* Implements hook_form_alter().
* Implements hook_field_attach_form().
*/
function og_form_alter(&$form, $form_state, $form_id) {
if (empty($form['#entity_type']) || empty($form['#bundle'])) {
function og_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
list(,, $bundle) = entity_extract_ids($entity_type, $entity);
if (og_get_group_audience_fields($entity_type, $bundle)) {
$form['#validate'][] = 'og_form_group_reference_validate';
}
if ($entity_type == 'user' || !og_is_group_type($entity_type, $bundle)) {
return;
}
$form['#validate'][] = 'og_form_group_manager_validate';
}
/**
* Validate handler; Make sure group-only content permissions are honored.
*
* If a user does not have site-wide node permissions, throw an error if they
* try to post site-wide instead of within a group.
*
* Note: This function does not check group -access- just if a group has been
* Selected.
*/
function og_form_group_reference_validate($form, &$form_state) {
global $user;
$entity_type = $form['#entity_type'];
if (empty($form_state[$entity_type])) {
// We are inside field settings page.
return;
}
$account = user_load($user->uid);
$bundle = $form['#bundle'];
$entity = $form['#entity'];
list($id) = entity_extract_ids($entity_type, $entity);
if ($entity_type == 'user' || !og_is_group_type($entity_type, $bundle)) {
$op = empty($id) ? 'create' : 'update';
if ($entity_type == 'node') {
$node = empty($id) ? $bundle : $entity;
// We call node_node_access() directly as we just want to check the
// permissions using user_acces().
if (node_node_access($node, $op, $account)) {
// User has site-wide permissions to create or edit the node.
return;
}
}
elseif (entity_access($op, $entity_type, $entity, $account)) {
// User has site-wide permissions to create or edit the entity.
return;
}
$form['#validate'][] = 'og_form_group_manager_validate';
foreach (array_keys(og_get_group_audience_fields($entity_type, $bundle)) as $field_name) {
// If there is at least one group selected, return.
if (!empty($form_state['values'][$field_name][LANGUAGE_NONE])) {
return;
}
}
// No group selected, throw an error.
form_set_error('og', t('You must select one or more groups for this content.'));
}
/**
......
......@@ -1867,3 +1867,225 @@ class OgNonMembersPublishingContentTestCase extends DrupalWebTestCase {
$this->assertText($this->group->title, 'The node is still referenced to the group.');
}
}
/**
* Verify that users only with OG permissions can post only inside a group
*
*/
class OgUserCanPublishGroupContentTypeOnlyInGroup extends DrupalWebTestCase {
public $group;
public $site_user;
public $group_user;
public $group_content;
public static function getInfo() {
return array(
'name' => 'User can publish group content only inside group',
'description' => "User with permission to create a group content only in a group can't publish outside of a group.",
'group' => 'Organic groups',
);
}
public function setUp() {
parent::setUp('og');
// Create a group content type.
$group = $this->drupalCreateContentType();
og_create_field(OG_GROUP_FIELD, 'node', $group->type);
// Create the group.
$settings = array(
'type' => $group->type,
OG_GROUP_FIELD . '[und][0][value]' => 1,
'uid' => 1,
);
$this->group = $this->drupalCreateNode($settings);
// Create the group content content type.
$this->group_content = $this->drupalCreateContentType();
// Attach the audience field.
og_create_field(OG_AUDIENCE_FIELD, 'node', $this->group_content->type);
// Add permission to the group.
$og_roles = og_roles('node', $group->type);
$rid = array_search(OG_AUTHENTICATED_ROLE, $og_roles);
og_role_change_permissions($rid, array(
'create ' . $this->group_content->type . ' content' => 1,
'update own ' . $this->group_content->type . ' content' => 1,
));
// Creating users.
$this->group_user = $this->drupalCreateUser();
$this->site_user = $this->drupalCreateUser(array('create ' . $this->group_content->type . ' content', 'edit own ' . $this->group_content->type . ' content'));
og_group('node', $this->group->nid, array('entity' => $this->group_user));
}
/**
* Grant to a user the permission to publish a node of a group content and
* verify that he can't create a node of that content type outside a group.
*/
public function testGroupUserCanPostGroupContentOnlyInGroup() {
$node_title = $this->randomName();
$this->drupalLogin($this->group_user);
$this->drupalPost('node/add', array('title' => $node_title), t('Save'));
$this->assertText("You must select one or more groups for this content", "The user can't publish a content outside a group");
// Check the user can publish node inside the group.
$edit = array(
'title' => $node_title,
'og_group_ref[und][0][default][]' => array($this->group->nid),
);
$this->drupalPost('node/add', $edit, t('Save'));
$this->assertText($this->group_content->type . " " . $node_title . " has been created.", "The user can create content inside a group.");
// Check the user can edit the node.
$query = new entityFieldQuery();
$result = $query
->entityCondition('entity_type', 'node')
->propertyCondition('title', $node_title)
->fieldCondition(OG_AUDIENCE_FIELD, 'target_id', $this->group->nid)
->execute();
$node_title = $this->randomName();
$edit = array(
'title' => $node_title,
'og_group_ref[und][0][default][]' => array(),
);
$this->drupalPost('node/' . reset($result['node'])->nid . '/edit', $edit, t('Save'));
$this->assertText("You must select one or more groups for this content", "The user can't edit a content outside a group");
$edit = array(
'title' => $node_title,
'og_group_ref[und][0][default][]' => array($this->group->nid),
);
$this->drupalPost('node/' . reset($result['node'])->nid . '/edit', $edit, t('Save'));
$this->assertText($this->group_content->type . " " . $node_title . " has been updated.", "The user can edit content inside a group.");
}
/**
* Verify that non-group user can post group content outside of a group.
*/
public function testNonGroupUserCanPostGroupContentOutsideGroup() {
$this->drupalLogin($this->site_user);
// Set node access strict variable to FALSE for posting outside groups.
variable_set('og_node_access_strict', FALSE);
// Verify that the user can publish group content outside a group.
$node_title = $this->randomName();
$this->drupalPost('node/add', array('title' => $node_title), t('Save'));
$params = array(
'@type' => $this->group_content->type,
'@title' => $node_title,
);
$this->assertText(format_string("@type @title has been created.", $params), "The user can create content outside a group.");
// Check the user can edit the node.
$query = new entityFieldQuery();
$result = $query
->entityCondition('entity_type', 'node')
->propertyCondition('title', $node_title)
->execute();
$node_title = $this->randomName();
$edit = array(
'title' => $node_title,
);
$this->drupalPost('node/' . reset($result['node'])->nid . '/edit', $edit, t('Save'));
$this->assertText($this->group_content->type . " " . $node_title . " has been updated.", "The user can edit content outside a group.");
}
}
/**
* Test for publishing content into group the user is not a member.
*/
class OgAutoCompleteAccessibleGroupsValidation extends DrupalWebTestCase {
var $dummy_content_type;
var $group_node_1;
var $group_node_2;
var $group_owner;
var $group_member;
public static function getInfo() {
return array(
'name' => 'Auto complete for non accessible groups',
'description' => "Verify the user cannot fill in the auto complete field with the name of a un-accessible group name.",
'group' => 'Organic groups',
);
}
function setUp() {
parent::setUp('og');
// Create group content.
$this->drupalCreateContentType(array( 'name' => 'Group content', 'type' => 'group_content'));
$this->drupalCreateContentType(array( 'name' => 'Group', 'type' => 'group'));
$og_field = og_fields_info(OG_AUDIENCE_FIELD);
$og_field['instance']['settings']['behaviors']['og_widget']['default']['widget_type'] = 'entityreference_autocomplete';
og_create_field(OG_AUDIENCE_FIELD, 'node', 'group_content', $og_field);
og_create_field(OG_GROUP_FIELD, 'node', 'group');
// Create users.
$this->group_owner = $this->drupalCreateUser(array('create group_content content', 'administer group'));
$this->group_member = $this->drupalCreateUser(array('create group_content content'));
// Create a group node.
$settings = array();
$settings['type'] = 'group';
$settings[OG_GROUP_FIELD][LANGUAGE_NONE][0]['value'] = TRUE;
$settings['uid'] = $this->group_owner->uid;
$this->group_node_1 = $this->drupalCreateNode($settings);
og_group('node', $this->group_node_1, array(
'entity' => $this->group_member,
));
$settings = array();
$settings['type'] = 'group';
$settings[OG_GROUP_FIELD][LANGUAGE_NONE][0]['value'] = TRUE;
$settings['uid'] = $this->drupalCreateUser()->uid;
$this->group_node_2 = $this->drupalCreateNode($settings);
}
/**
* Verify that a user can't publish content into group that he isn't a member.
*/
function testAuthenticatedUserCantReferenceToPrivateGroup() {
$this->drupalLogin($this->group_member);
// Try to publish content into group the user is not a member.
$this->drupalPost('node/add/group_content', array('title' => 'New group content', 'og_group_ref[und][0][default][0][target_id]' => $this->group_node_2->title . '(' . $this->group_node_2->nid . ')'), 'Save');
$this->assertText('The referenced group (node: ' . $this->group_node_2->nid . ') is invalid.', 'The reference to group the user is not accessible has achieved');
// Try to publish content into group that doesn't exist.
$this->drupalPost('node/add/group_content', array('title' => 'New group content', 'og_group_ref[und][0][default][0][target_id]' => 'foo (900)'), 'Save');
$this->assertText('The referenced group (node: 900) is invalid.', 'The reference to group the user is not accessible has achieved');
// Try to publish content into the group the user is a member of.
$this->drupalPost('node/add/group_content', array('title' => 'New group content 2', 'og_group_ref[und][0][default][0][target_id]' => $this->group_node_1->title . '(' . $this->group_node_1->nid . ')'), 'Save');
$this->assertText('Group content New group content 2 has been created.', 'The group content created successfully');
// Testing the widgets my group and other groups.
$this->drupalLogin($this->group_owner);
$this->drupalPost('node/add/group_content', array('title' => 'New group content 3', 'og_group_ref[und][0][default][0][target_id]' => $this->group_node_2->title . '(' . $this->group_node_2->nid . ')'), 'Save');
$this->assertText('The referenced group (node: ' . $this->group_node_2->nid . ') is invalid.', 'The reference to group the user is not accessible has achieved');
$this->drupalPost('node/add/group_content', array('title' => 'New group content 3', 'og_group_ref[und][0][admin][0][target_id]' => $this->group_node_2->title . '(' . $this->group_node_2->nid . ')'), 'Save');
$this->assertText('Group content New group content 3 has been created.', 'The group content created successfully');
$this->drupalPost('node/add/group_content', array('title' => 'New group content 3', 'og_group_ref[und][0][admin][0][target_id]' => 'foo (900)'), 'Save');
$this->assertText('The referenced group (node: 900) is invalid.', 'The reference to group the user is not accessible has achieved');
// Try to publish content into the group the user is a member of.
$this->drupalPost('node/add/group_content', array('title' => 'New group content 4', 'og_group_ref[und][0][default][0][target_id]' => $this->group_node_1->title . '(' . $this->group_node_1->nid . ')'), 'Save');
$this->assertText('Group content New group content 4 has been created.', 'The group content created successfully');
}
}
......@@ -227,4 +227,56 @@ class OgBehaviorHandler extends EntityReference_BehaviorHandler_Abstract {
unset($data['field_revision_' . $field['field_name']]);
}
}
/**
* Implements EntityReference_BehaviorHandler_Abstract::validate().
*
* Re-build $errors array to be keyed correctly by "default" and "admin" field
* modes.
*
* @todo: Try to get the correct delta so we can highlight the invalid
* reference.
*
* @see entityreference_field_validate().
*/
public function validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
$new_errors = array();
$values = array('default' => array(), 'admin' => array());
foreach ($items as $item) {
$values[$item['field_mode']][] = $item['target_id'];
}
list(,, $bundle) = entity_extract_ids($entity_type, $entity);
$field_name = $field['field_name'];
foreach ($values as $field_mode => $ids) {
if (!$ids) {
continue;
}
if ($field_mode == 'admin' && !user_access('administer group')) {
// No need to validate the admin, as the user has no access to it.
continue;
}
$instance['field_mode'] = $field_mode;
$valid_ids = entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->validateReferencableEntities($ids);
if ($invalid_entities = array_diff($ids, $valid_ids)) {
foreach ($invalid_entities as $id) {
$new_errors[$field_mode][] = array(
'error' => 'og_invalid_entity',
'message' => t('The referenced group (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)),
);
}
}
}
if ($new_errors) {
og_field_widget_register_errors($field_name, $new_errors);
}
$errors = array();
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment