diff --git a/modules/gnode/gnode.info b/modules/gnode/gnode.info index 2cc8b3a..46284dc 100644 --- a/modules/gnode/gnode.info +++ b/modules/gnode/gnode.info @@ -5,6 +5,7 @@ package = Group files[] = tests/gnode.test files[] = tests/gnode.multiple.test +files[] = tests/gnode.interface.test dependencies[] = group dependencies[] = node diff --git a/modules/gnode/gnode.module b/modules/gnode/gnode.module index 59aeec2..40f75ed 100644 --- a/modules/gnode/gnode.module +++ b/modules/gnode/gnode.module @@ -689,7 +689,7 @@ function gnode_node_validate($node, $form, &$form_state) { * 3. If it is an existing node, the user can change the groups a node is in * only for groups they are an administrator of. * 4. If the user cannot globally create nodes of this type then they must - * select at least one node. + * select at least one of their groups. * * @see gnode_form_node_form_alter() */ @@ -714,10 +714,36 @@ function gnode_node_submit($node, $form, &$form_state) { if (!$can_bypass_access && !empty($node->nid)) { $existing_gids = $form_state['values']['group_settings']['existing_gids']; - foreach ($existing_gids as $existing_gid) { - // This node is in a group the user is not an administrator of. - if (!isset($specified_gids[$existing_gid])) { - $specified_gids[] = $existing_gid; + foreach (group_load_multiple($existing_gids) as $existing_group) { + if (!isset($specified_gids[$existing_group->gid])) { + // The user is trying to take the node out a group. They can only do + // this if they are admin of this group. + if (!group_access('administer group', $existing_group)) { + // The user is not an administrator so keep the node in this group. + $specified_gids[] = $existing_group->gid; + } + } + } + + // The user cannot remove the node from all their groups. + $removed_group_ids = array_diff($existing_gids, $specified_gids); + + if (!empty($removed_group_ids)) { + // There has been a group removed, check that at least one remaining group + // the user is administer of. + $found_group = FALSE; + foreach (group_load_multiple($specified_gids) as $specified_group) { + if (group_access('administer group', $specified_group)) { + // The user is an administrator so the specified list is valid. + $found_group = TRUE; + break; + } + } + + if (!$found_group) { + // The user has taken the node out of all their groups so reset it back to how it was. + drupal_set_message(t('You cannot remove this content from all groups you are an administrator of.'), 'warning'); + $specified_gids = $existing_gids; } } } diff --git a/modules/gnode/tests/gnode.interface.test b/modules/gnode/tests/gnode.interface.test new file mode 100644 index 0000000..71a0718 --- /dev/null +++ b/modules/gnode/tests/gnode.interface.test @@ -0,0 +1,438 @@ +gid == $array1_group->gid) { + continue 2; + } + } + $not_in_array2[] = $array1_group; + } + + return $not_in_array2; + } + + /** + * @param Group $group + */ + abstract protected function assertNodeFormHasGroupOption($group); + + /** + * @param Group $group + */ + abstract protected function assertNodeFormNoGroupOption($group); + + /** + * @param array $edit + * @param Group $group + */ + abstract protected function selectGroupOnEditForm(&$edit, $group); + + /** + * @param array $edit + * @param Group $group + */ + abstract protected function deselectGroupOnEditForm(&$edit, $group); + + /** + * Assert the state of a node edit form given a specific user. + * + * This assertion calls abstract functions which are implemented on extended + * versions of this test class to allow different interfaces to be tested. + * + * @param stdClass $node + * The node to edit. + * @param Group[] $groups_to_select + * On the node edit form, which groups should the user choose. + * @param array $admin_groups + * Which available groups do we expect the user to be an admin of. + * @param array $non_admin_groups + * Which available groups is the user not an admin of. + * @param array $groups_after_save + * After saving the node form, which groups do we expect the node to be in. + * + * @throws \Exception + */ + public function assertUserCanEditNodeInGroup($node, $groups_to_select = array(), $admin_groups = array(), $non_admin_groups = array(), $groups_after_save = array()) { + $this->assertNodeOperationAccess($node->nid, 'edit', 200); + + $node_edit_url = '/node/' . $node->nid . '/edit'; + $this->drupalGet($node_edit_url); + + if (count($admin_groups) > 1) { + // Check user can see group selection options if there is more than 1 option. + foreach ($admin_groups as $admin_group) { + $this->assertNodeFormHasGroupOption($admin_group); + } + } + + foreach ($non_admin_groups as $non_admin_group) { + // Check user is not presented with options to set the group to one + // they are not a member of. + $this->assertNodeFormNoGroupOption($non_admin_group); + } + + $edit = array( + 'title' => 'test-title', + 'body[und][0][value]' => 'test-body', + 'body[und][0][format]' => 'filtered_html', + ); + + if (count($admin_groups) > 1) { + foreach ($groups_to_select as $group_to_select) { + // Select a group for the node to be in. + $this->selectGroupOnEditForm($edit, $group_to_select); + } + + foreach ($this->groupArrayDiff($admin_groups, $groups_to_select) as $unselected_groups) { + // Deselect a group for the node to be in. + $this->deselectGroupOnEditForm($edit, $unselected_groups); + } + } + + $this->verbose(print_r($edit, TRUE)); + $this->drupalPost($node_edit_url, $edit, t('Save')); + + $node = node_load($node->nid, NULL, TRUE); + + $expected_gids = array(); + foreach ($groups_after_save as $group_after_save) { + $expected_gids[] = $group_after_save->gid; + } + + $node_groups = empty($node->group) ? array() : $node->group; + $this->assertEqual($node_groups, $expected_gids); + } + + /** + * Test a group admins ability to edit a node when it is in several groups. + * + * There is one node, 3 groups and 3 administrators. + * User 1 is admin of group 1 + * User 2 is admin of group 2 + * User 3 is admin of groups 1 and 2. + * + * The node starts in group 1. User 1 can edit the node, user 2 cannot edit the node. + * User 3 can move the node into group 2 and out of group 1. + * User 2 can edit the node now and user 1 cannot. + * User 3 can move the node into group 1 and group 2. + * User 1 and user 2 can edit the node and it remains in both groups. + */ + public function testMultipleGroupParentEditAccess() { + $group_type = $this->createGroupType('example_group_type'); + $group_1 = $this->createGroup('group_1', 'example_group_type'); + $group_2 = $this->createGroup('group_2', 'example_group_type'); + $group_3 = $this->createGroup('group_3', 'example_group_type'); + + $node = $this->createNodeInGroup($group_1->gid); + + $admin_user_group_1 = $this->drupalCreateUser(array('access content')); + $admin_user_group_2 = $this->drupalCreateUser(array('access content')); + $admin_user_group_1_and_2 = $this->drupalCreateUser(array('access content')); + + $group_role = $this->createRole('content_admin', 'example_group_type'); + $group_role->grantPermissions(array('administer group', 'update any page node', 'create page node')); + + $role_details = array( + 'roles' => array('content_admin'), + 'added_on' => REQUEST_TIME, + 'added_by' => 1, + ); + + $group_1->addMember($admin_user_group_1->uid, $role_details); + $group_2->addMember($admin_user_group_2->uid, $role_details); + $group_1->addMember($admin_user_group_1_and_2->uid, $role_details); + $group_2->addMember($admin_user_group_1_and_2->uid, $role_details); + + // Check user 1 can edit the node as it is in group 1. + $this->drupalLogin($admin_user_group_1); + $this->assertUserCanEditNodeInGroup($node, array($group_1), array($group_1), array($group_2, $group_3), array($group_1)); + + // Check user 2 cannot edit the node in group 1. + $this->drupalLogin($admin_user_group_2); + $this->assertNodeOperationAccess($node->nid, 'edit', 403); + + // Check user 1_and_2 can move the node to group 2. + $this->drupalLogin($admin_user_group_1_and_2); + $this->assertUserCanEditNodeInGroup($node, array($group_2), array($group_1, $group_2), array($group_3), array($group_2)); + + // Now the node is only in group 2, check if user 1 can no longer edit. + $this->drupalLogin($admin_user_group_1); + $this->assertNodeOperationAccess($node->nid, 'edit', 403); + + // Now check that user 2 now can edit. + $this->drupalLogin($admin_user_group_2); + $this->assertUserCanEditNodeInGroup($node, array($group_2), array($group_2), array($group_1, $group_3), array($group_2)); + + // Now have user 1_and_2 put the node in both groups. + $this->drupalLogin($admin_user_group_1_and_2); + $this->assertUserCanEditNodeInGroup($node, array($group_1, $group_2), array($group_1, $group_2), array($group_3), array($group_1, $group_2)); + + // Check if user 1 can edit and it stays in both groups. + $this->drupalLogin($admin_user_group_1); + $this->assertUserCanEditNodeInGroup($node, array($group_1), array($group_1), array($group_2, $group_3), array($group_1, $group_2)); + + // Check if user 2 can edit and it stays in both groups. + $this->drupalLogin($admin_user_group_2); + $this->assertUserCanEditNodeInGroup($node, array($group_2), array($group_2), array($group_1, $group_3), array($group_1, $group_2)); + } + + /** + * Test that group administrators cannot take a node out of all their groups. + */ + public function testAdminsCannotRemoveNodeFromGroups() { + $group_type = $this->createGroupType('example_group_type'); + $group_1 = $this->createGroup('group_1', 'example_group_type'); + $group_2 = $this->createGroup('group_2', 'example_group_type'); + + $node = $this->createNodeInGroup($group_1->gid); + + $admin_user = $this->drupalCreateUser(array('access content')); + + $group_role = $this->createRole('content_admin', 'example_group_type'); + $group_role->grantPermissions(array('administer group', 'update any page node', 'create page node')); + + $role_details = array( + 'roles' => array('content_admin'), + 'added_on' => REQUEST_TIME, + 'added_by' => 1, + ); + + $group_1->addMember($admin_user->uid, $role_details); + $group_2->addMember($admin_user->uid, $role_details); + + // Check admin user cannot remove node from all groups. + $this->drupalLogin($admin_user); + $this->assertUserCanEditNodeInGroup($node, array(), array($group_1, $group_2), array(), array($group_1)); + } + + /** + * Test that site administrators with 'bypass group access' can remove a node from all groups. + */ + public function testAdminsWithBypassGroupAccessCanRemoveNodeFromAllGroups() { + $group_type = $this->createGroupType('example_group_type'); + $group_1 = $this->createGroup('group_1', 'example_group_type'); + $group_2 = $this->createGroup('group_2', 'example_group_type'); + + $node = $this->createNodeInGroup($group_1->gid); + $admin_user = $this->drupalCreateUser(array('access content', 'edit any page content', 'bypass group access')); + + // Check admin user can remove node from all groups. + $this->drupalLogin($admin_user); + $this->assertUserCanEditNodeInGroup($node, array(), array($group_1, $group_2), array(), array()); + } + +} + +class GNodeMultipleEditInterfaceWithCheckboxTests extends GNodeMultipleEditInterfaceTest { + + /** + * Drupal SimpleTest method: return metadata about the test. + */ + public static function getInfo() { + return array( + 'name' => t('Group gnode - node edit tests with multiple parent groups when group selection on a node uses checkboxes'), + 'description' => t('Test node edit interface for a group administrator who can set which groups a node can be in using checkboxes.'), + 'group' => t('Group'), + ); + } + + /** + * Test setup instructions. + */ + public function setUp() { + parent::setUp('node', 'entity', 'group', 'gnode'); + variable_set('group_node_mode', GROUP_NODE_SAFE_MODE); + variable_set('group_node_multiple_groups', GROUP_NODE_MULTIPLE_GROUPS_MODE); + variable_set('gnode_group_choice_standard_element', 'checkbox'); + node_access_rebuild(); + } + + /** + * {@InheritDoc} + */ + protected function assertNodeFormHasGroupOption($group) { + $this->assertFieldByName("group_settings[gid][{$group->gid}]"); + } + + /** + * {@InheritDoc} + */ + protected function assertNodeFormNoGroupOption($group) { + $this->assertNoFieldByName("group_settings[gid][{$group->gid}]"); + } + + /** + * {@InheritDoc} + */ + protected function selectGroupOnEditForm(&$edit, $group) { + $edit["group_settings[gid][{$group->gid}]"] = TRUE; + } + + /** + * {@InheritDoc} + */ + protected function deselectGroupOnEditForm(&$edit, $group) { + $edit["group_settings[gid][{$group->gid}]"] = FALSE; + } + +} + +class GNodeMultipleEditInterfaceWithSelectTests extends GNodeMultipleEditInterfaceTest { + + /** + * Drupal SimpleTest method: return metadata about the test. + */ + public static function getInfo() { + return array( + 'name' => t('Group gnode - node edit tests with multiple parent groups when group selection on a node uses select'), + 'description' => t('Test node edit interface for a group administrator who can set which groups a node can be in using select.'), + 'group' => t('Group'), + ); + } + + /** + * Test setup instructions. + */ + public function setUp() { + parent::setUp('node', 'entity', 'group', 'gnode'); + variable_set('group_node_mode', GROUP_NODE_SAFE_MODE); + variable_set('group_node_multiple_groups', GROUP_NODE_MULTIPLE_GROUPS_MODE); + variable_set('gnode_group_choice_standard_element', 'select'); + node_access_rebuild(); + } + + /** + * {@InheritDoc} + */ + protected function assertNodeFormHasGroupOption($group) { + $this->assertFieldByXPath("//select[@id='edit-group-settings-gid']/option[@value='{$group->gid}']", $group->label(), 'Should find option to add to group ' . $group->gid); + } + + /** + * {@InheritDoc} + */ + protected function assertNodeFormNoGroupOption($group) { + $this->assertNoFieldByXPath("//select[@id='edit-group-settings-gid']/option[@value='{$group->gid}']", NULL, 'Should not find option to add to group ' . $group->gid); + } + + /** + * {@InheritDoc} + */ + protected function selectGroupOnEditForm(&$edit, $group) { + if (!isset($edit['group_settings[gid][]'])) { + $edit['group_settings[gid][]'] = array(); + } + + $edit['group_settings[gid][]'][] = $group->gid; + } + + /** + * {@InheritDoc} + */ + protected function deselectGroupOnEditForm(&$edit, $group) { + if (!empty($edit['group_settings[gid][]'])) { + foreach ($edit['group_settings[gid][]'] as $key => $gid) { + if ($gid == $group->gid) { + unset($edit['group_settings[gid][]'][$key]); + break; + } + } + } + } + +} + + +class GNodeMultipleEditInterfaceWithAutoCompleteTests extends GNodeMultipleEditInterfaceTest { + + /** + * Drupal SimpleTest method: return metadata about the test. + */ + public static function getInfo() { + return array( + 'name' => t('Group gnode - node edit tests with multiple parent groups when group selection on a node uses auto complete'), + 'description' => t('Test node edit interface for a group administrator who can set which groups a node can be in using auto complete.'), + 'group' => t('Group'), + ); + } + + /** + * Test setup instructions. + */ + public function setUp() { + parent::setUp('node', 'entity', 'group', 'gnode'); + variable_set('group_node_mode', GROUP_NODE_SAFE_MODE); + variable_set('group_node_multiple_groups', GROUP_NODE_MULTIPLE_GROUPS_MODE); + variable_set('gnode_group_choice_standard_element', 'autocomplete'); + node_access_rebuild(); + } + + /** + * {@InheritDoc} + */ + protected function assertNodeFormHasGroupOption($group) { + // When working with auto complete form element there is no specific + // form element for an individual group. + } + + /** + * {@InheritDoc} + */ + protected function assertNodeFormNoGroupOption($group) { + // When working with auto complete form element there is no specific + // form element for an individual group. + } + + /** + * {@InheritDoc} + */ + protected function selectGroupOnEditForm(&$edit, $group) { + $auto_complete_string = $group->label() . ' (GID: ' . $group->gid . ')'; + if (empty($edit['group_settings[group_autocomplete]'])) { + $edit['group_settings[group_autocomplete]'] = $auto_complete_string; + } + else { + $edit['group_settings[group_autocomplete]'] .= ',' . $auto_complete_string; + } + } + + /** + * {@InheritDoc} + */ + protected function deselectGroupOnEditForm(&$edit, $group) { + if (!isset($edit['group_settings[group_autocomplete]'])) { + $edit['group_settings[group_autocomplete]'] = ''; + } + + $auto_complete_string = $group->label() . ' (GID: ' . $group->gid . ')'; + $edit['group_settings[group_autocomplete]'] = str_replace(',' . $auto_complete_string, '', $edit['group_settings[group_autocomplete]']); + $edit['group_settings[group_autocomplete]'] = str_replace($auto_complete_string, '', $edit['group_settings[group_autocomplete]']); + } +}