diff --git a/plugins/FeedsUserProcessor.inc b/plugins/FeedsUserProcessor.inc index 496be88..7117e57 100644 --- a/plugins/FeedsUserProcessor.inc +++ b/plugins/FeedsUserProcessor.inc @@ -16,6 +16,15 @@ define('FEEDS_BLOCK_NON_EXISTENT', 'block'); * Feeds processor plugin. Create users from feed items. */ class FeedsUserProcessor extends FeedsProcessor { + /** + * Search by role name. + */ + const ROLE_SEARCH_NAME = 'name'; + + /** + * Search by role id. + */ + const ROLE_SEARCH_RID = 'rid'; /** * Define entity type. @@ -161,33 +170,9 @@ class FeedsUserProcessor extends FeedsProcessor { break; case 'roles_list': - if (!empty($value)) { - // Assure roles list is an array. - $roles_list = (array) $value; - foreach ($roles_list as $role_name) { - $role_name = trim($role_name); - if (strlen($role_name) < 1) { - // No role name provided. Continue to the next role. - continue; - } - $role = user_role_load_by_name($role_name); - if (!$role && !empty($mapping['autocreate'])) { - // Create new role if role doesn't exist. - $role = new stdClass(); - $role->name = $role_name; - user_role_save($role); - $role = user_role_load_by_name($role->name); - } - if ($role) { - // Check if the role may be assigned. - if (isset($mapping['allowed_roles'][$role->rid]) && !$mapping['allowed_roles'][$role->rid]) { - // This role may *not* be assiged. - continue; - } - $target_user->roles[$role->rid] = $role->name; - } - } - } + // Ensure that the role list is an array. + $value = (array) $value; + $this->rolesListSetTarget($source, $target_user, $target_element, $value, $mapping); break; default: @@ -317,8 +302,10 @@ class FeedsUserProcessor extends FeedsProcessor { $rids = array_keys($roles); $rids = array_combine($rids, $rids); return array( + 'role_search' => self::ROLE_SEARCH_NAME, 'allowed_roles' => $rids, 'autocreate' => 0, + 'revoke_roles' => 1, ); } @@ -333,6 +320,11 @@ class FeedsUserProcessor extends FeedsProcessor { $mapping += $defaults; $mapping['allowed_roles'] += $defaults['allowed_roles']; + // Role search. + $role_search_options = $this->rolesListRoleSearchOptions(); + $options[] = t('Search roles by: @search', array('@search' => $role_search_options[$mapping['role_search']])); + + // Allowed roles. $role_names = array(); $roles = user_roles(TRUE); foreach (array_filter($mapping['allowed_roles']) as $rid => $enabled) { @@ -344,6 +336,7 @@ class FeedsUserProcessor extends FeedsProcessor { } $options[] = t('Allowed roles: %roles', array('%roles' => implode(', ', $role_names))); + // Autocreate. if ($mapping['autocreate']) { $options[] = t('Automatically create roles'); } @@ -351,6 +344,14 @@ class FeedsUserProcessor extends FeedsProcessor { $options[] = t('Only assign existing roles'); } + // Revoke roles. + if ($mapping['revoke_roles']) { + $options[] = t('Revoke roles: yes'); + } + else { + $options[] = t('Revoke roles: no'); + } + return implode('
', $options); } @@ -367,6 +368,12 @@ class FeedsUserProcessor extends FeedsProcessor { unset($allowed_roles_options[DRUPAL_AUTHENTICATED_RID]); return array( + 'role_search' => array( + '#type' => 'select', + '#title' => t('Search roles by'), + '#options' => $this->rolesListRoleSearchOptions(), + '#default_value' => $mapping['role_search'], + ), 'allowed_roles' => array( '#type' => 'checkboxes', '#title' => t('Allowed roles'), @@ -380,6 +387,76 @@ class FeedsUserProcessor extends FeedsProcessor { '#description' => t("Create the role if it doesn't exist."), '#default_value' => $mapping['autocreate'], ), + 'revoke_roles' => array( + '#type' => 'checkbox', + '#title' => t('Revoke roles'), + '#description' => t('If enabled, roles that are not provided by the feed will be revoked for the user. This affects only the "Allowed roles" as configured above.'), + '#default_value' => $mapping['revoke_roles'], + ), ); } + + /** + * Returns role list options. + */ + public function rolesListRoleSearchOptions() { + return array( + self::ROLE_SEARCH_NAME => t('Role name'), + self::ROLE_SEARCH_RID => t('Role ID'), + ); + } + + /** + * Sets role target on the user entity. + */ + public function rolesListSetTarget(FeedsSource $source, $entity, $target, array $values, array $mapping) { + // Add in defaults. + $defaults = $this->rolesListDefaults(); + $mapping += $defaults; + $mapping['allowed_roles'] += $defaults['allowed_roles']; + + // Eventually revoke roles. Do not touch roles that are not allowed to set + // by the source. + if ($mapping['revoke_roles']) { + foreach ($mapping['allowed_roles'] as $rid) { + unset($entity->roles[$rid]); + } + } + + foreach ($values as $value) { + $role = NULL; + + $value = trim($value); + if (strlen($value) < 1) { + // No role provided. Continue to the next role. + continue; + } + + switch ($mapping['role_search']) { + case self::ROLE_SEARCH_NAME: + $role = user_role_load_by_name($value); + if (!$role && !empty($mapping['autocreate'])) { + // Create new role if role doesn't exist. + $role = new stdClass(); + $role->name = $role_name; + user_role_save($role); + $role = user_role_load_by_name($role->name); + } + break; + + case self::ROLE_SEARCH_RID: + $role = user_role_load($value); + break; + } + + if ($role) { + // Check if the role may be assigned. + if (isset($mapping['allowed_roles'][$role->rid]) && !$mapping['allowed_roles'][$role->rid]) { + // This role may *not* be assiged. + continue; + } + $entity->roles[$role->rid] = $role->name; + } + } + } } diff --git a/tests/feeds/users_roles.csv b/tests/feeds/users_roles.csv index 7ccfc28..414cfef 100644 --- a/tests/feeds/users_roles.csv +++ b/tests/feeds/users_roles.csv @@ -1,5 +1,5 @@ -name,mail,since,password,roles -Morticia,morticia@example.com,1244347500,mort,editor -Fester,fester@example.com,1241865600,fest,manager -Gomez,gomez@example.com,1228572000,gome,tester| |manager -Pugsley,pugsley@example.com,1228260225,pugs, \ No newline at end of file +name,mail,since,password,roles,rids +Morticia,morticia@example.com,1244347500,mort,editor,6 +Fester,fester@example.com,1241865600,fest,manager,4 +Gomez,gomez@example.com,1228572000,gome,tester| |manager,5| |4 +Pugsley,pugsley@example.com,1228260225,pugs,, \ No newline at end of file diff --git a/tests/feeds_processor_user.test b/tests/feeds_processor_user.test index 2082484..9b7398a 100644 --- a/tests/feeds_processor_user.test +++ b/tests/feeds_processor_user.test @@ -170,7 +170,7 @@ class FeedsCSVtoUsersTest extends FeedsWebTestCase { $this->assertTrue(isset($account->roles[$manager_rid]), 'Fester has the manager role.'); $this->assertEqual(2, count($account->roles), 'Fester has two roles.'); - // Assert that Gomez got the manager role but not the editor role, since + // Assert that Gomez got the manager role but not the tester role, since // that role doesn't exist on the system. $account = user_load_by_name('Gomez'); $this->assertTrue(isset($account->roles[$manager_rid]), 'Gomez has the manager role.'); @@ -240,6 +240,59 @@ class FeedsCSVtoUsersTest extends FeedsWebTestCase { } /** + * Tests mapping to role using role ID's. + */ + public function testRoleTargetRids() { + // Add mapping to role. + $this->addMappings('user_import', array( + 4 => array( + 'source' => 'rids', + 'target' => 'roles_list', + 'role_search' => FeedsUserProcessor::ROLE_SEARCH_RID, + ), + )); + + // Create manager and tester roles. + $manager_rid = $this->drupalCreateRole(array('access content'), 'manager'); + $tester_rid = $this->drupalCreateRole(array('access content'), 'tester'); + // Ensure expected ID's of these roles. + $this->assertEqual(4, $manager_rid); + $this->assertEqual(5, $tester_rid); + + // Import CSV file. + $this->importFile('user_import', $this->absolutePath() . '/tests/feeds/users_roles.csv'); + + // Assert that Mortica did not get the editor role and has one role in + // total. + $account = user_load_by_name('Morticia'); + $this->assertFalse(in_array('editor', $account->roles), 'Morticia does not have the editor role.'); + $this->assertEqual(1, count($account->roles), 'Morticia has one role.'); + + // Assert that Fester got the manager role and two roles in total. + $account = user_load_by_name('Fester'); + $this->assertTrue(isset($account->roles[$manager_rid]), 'Fester has the manager role.'); + $this->assertEqual(2, count($account->roles), 'Fester has two roles.'); + + // Assert that Gomez got the manager and tester roles. + $account = user_load_by_name('Gomez'); + $this->assertTrue(isset($account->roles[$manager_rid]), 'Gomez has the manager role.'); + $this->assertTrue(isset($account->roles[$manager_rid]), 'Gomez has the tester role.'); + $this->assertEqual(3, count($account->roles), 'Gomez has two roles.'); + + // Assert that Pugsley only has one role. + $account = user_load_by_name('Pugsley'); + $this->assertEqual(1, count($account->roles), 'Pugsley has one role.'); + + // Assert that four roles exist: + // - authenticated user + // - role from the admin user + // - manager + // - tester + $roles = user_roles(TRUE); + $this->assertEqual(4, count($roles), 'Four roles exist.'); + } + + /** * Tests mapping to role using only allowed roles. */ public function testRoleTargetWithAllowedRoles() { @@ -282,4 +335,95 @@ class FeedsCSVtoUsersTest extends FeedsWebTestCase { $this->assertTrue(in_array('tester', $account->roles), 'Gomez has the tester role.'); $this->assertEqual(2, count($account->roles), 'Gomez has two roles.'); } + + /** + * Tests that roles can be revoked and that only allowed roles are revoked. + */ + public function testRevokeRoles() { + // Create manager role. + $manager_rid = $this->drupalCreateRole(array('access content'), 'manager'); + // Create editor role. + $editor_rid = $this->drupalCreateRole(array('access content'), 'editor'); + // Create tester role. + $tester_rid = $this->drupalCreateRole(array('access content'), 'tester'); + + // Set to update existing users. + $this->setSettings('user_import', 'FeedsUserProcessor', array('update_existing' => 2)); + + // Add mapping to role. + // The manager role may not be revoked, but the editor role may. + $this->addMappings('user_import', array( + 4 => array( + 'source' => 'roles', + 'target' => 'roles_list', + 'allowed_roles' => array( + $manager_rid => FALSE, + $editor_rid => $editor_rid, + $tester_rid => $tester_rid, + ), + 'autocreate' => TRUE, + ), + )); + + // Create account for Mortica with roles "manager" and "editor". In the + // source only "editor" is specified. Mortica should keep both roles. + user_save(drupal_anonymous_user(), array( + 'name' => 'Mortica', + 'mail' => 'morticia@example.com', + 'pass' => 'mort', + 'status' => 1, + 'roles' => array( + $manager_rid => $manager_rid, + $editor_rid => $editor_rid, + ), + )); + // Create account for Pugsley with roles "manager", "editor" and "tester". + // Pugsley has no roles in the source so should only keep the "manager" + // role. + user_save(drupal_anonymous_user(), array( + 'name' => 'Pugsley', + 'mail' => 'pugsley@example.com', + 'pass' => 'pugs', + 'status' => 1, + 'roles' => array( + $manager_rid => $manager_rid, + $editor_rid => $editor_rid, + $tester_rid => $tester_rid, + ), + )); + // Create account for Gomez and give it the "editor" role. Gomez has roles + // "tester" and "manager" in the source, so it should lose the "editor" role + // and gain the "tester" role. + user_save(drupal_anonymous_user(), array( + 'name' => 'Gomez', + 'mail' => 'gomez@example.com', + 'pass' => 'gome', + 'status' => 1, + 'roles' => array( + $editor_rid => $editor_rid, + ), + )); + + // Import CSV file. + $this->importFile('user_import', $this->absolutePath() . '/tests/feeds/users_roles.csv'); + + // Assert that Mortica kept the manager and editor roles. + $account = user_load_by_name('Morticia'); + $this->assertTrue(isset($account->roles[$manager_rid]), 'Morticia still has the manager role.'); + $this->assertTrue(isset($account->roles[$editor_rid]), 'Morticia has the editor role.'); + $this->assertEqual(3, count($account->roles), 'Morticia has three roles.'); + + // Assert that Pugsley only kept the manager role. + $account = user_load_by_name('Pugsley'); + $this->assertTrue(isset($account->roles[$manager_rid]), 'Pugsley still has the manager role.'); + $this->assertFalse(isset($account->roles[$editor_rid]), 'Pugsley no longer has the editor role.'); + $this->assertFalse(isset($account->roles[$tester_rid]), 'Pugsley no longer has the tester role.'); + $this->assertEqual(2, count($account->roles), 'Pugsley has two roles.'); + + // Assert that Gomez lost the editor role, and gained the tester role. + $account = user_load_by_name('Gomez'); + $this->assertFalse(isset($account->roles[$editor_rid]), 'Gomez no longer has the editor role.'); + $this->assertTrue(isset($account->roles[$tester_rid]), 'Gomez has the tester role.'); + $this->assertEqual(2, count($account->roles), 'Gomez has two roles.'); + } } diff --git a/tests/feeds_tests.module b/tests/feeds_tests.module index 6816b57..5688b66 100644 --- a/tests/feeds_tests.module +++ b/tests/feeds_tests.module @@ -352,26 +352,19 @@ function feeds_tests_mapper_unique(FeedsSource $source, $entity_type, $bundle, $ /** * Implements hook_feeds_after_parse(). - * - * Empties the list of items to import in case the test says that there are - * items in there with encoding issues. These items can not be processed during - * tests without having a test failure because in < PHP 5.4 that would produce - * the following warning: - * htmlspecialchars(): Invalid multibyte sequence in argument - * - * @see FeedsCSVParserTestCase::testMbstringExtensionDisabled() */ function feeds_tests_feeds_after_parse(FeedsSource $source, FeedsParserResult $result) { + // Empties the list of items to import in case the test says that there are + // items in there with encoding issues. These items can not be processed + // during tests without having a test failure because in < PHP 5.4 that would + // produce the following warning: + // htmlspecialchars(): Invalid multibyte sequence in argument + // @see FeedsCSVParserTestCase::testMbstringExtensionDisabled() if (variable_get('feeds_tests_feeds_after_parse_empty_items', FALSE)) { // Remove all items. No items will be processed. $result->items = array(); } -} -/** - * Implements hook_feeds_after_parse(). - */ -function feeds_tests_feeds_after_parse(FeedsSource $source, FeedsParserResult $result) { if ($source->id == 'user_import') { foreach ($result->items as &$item) { if (!empty($item['roles']) && strpos($item['roles'], '|')) { @@ -381,6 +374,11 @@ function feeds_tests_feeds_after_parse(FeedsSource $source, FeedsParserResult $r // @see FeedsCSVtoUsersTest::testRoleTargetWithAllowedRoles() $item['roles'] = explode('|', $item['roles']); } + if (!empty($item['rids']) && strpos($item['rids'], '|')) { + // Convert roles value to multiple values. + // @see FeedsCSVtoUsersTest::testRoleTargetRids() + $item['rids'] = explode('|', $item['rids']); + } } } }