diff --git a/og.services.yml b/og.services.yml index 92db7870a..d8f3b38f0 100644 --- a/og.services.yml +++ b/og.services.yml @@ -4,7 +4,7 @@ services: arguments: ['@config.factory', '@current_user', '@module_handler'] og.event_subscriber: class: Drupal\og\EventSubscriber\OgEventSubscriber - arguments: ['@og.permission_manager'] + arguments: ['@og.permission_manager', '@entity_type.manager'] tags: - { name: 'event_subscriber' } og.group.manager: diff --git a/src/Entity/OgRole.php b/src/Entity/OgRole.php index 112a324d7..b03c50947 100644 --- a/src/Entity/OgRole.php +++ b/src/Entity/OgRole.php @@ -149,7 +149,7 @@ public function setGroupBundle($group_bundle) { * OgRoleInterface::ROLE_TYPE_STANDARD. */ public function getRoleType() { - return $this->get('role_type'); + return $this->get('role_type') ?: OgRoleInterface::ROLE_TYPE_STANDARD; } /** @@ -174,11 +174,46 @@ public function setRoleType($role_type) { return $this->set('role_type', $role_type); } + /** + * {@inheritdoc} + */ + public function getName() { + // If the name is not set yet, try to derive it from the ID. + if (empty($this->name) && !empty($this->id()) && !empty($this->getGroupType()) && !empty($this->getGroupBundle())) { + // Check if the ID matches the pattern '{entity type}-{bundle}-{name}'. + $pattern = preg_quote("{$this->getGroupType()}-{$this->getGroupBundle()}-"); + preg_match("/$pattern(.+)/", $this->id(), $matches); + if (!empty($matches[1])) { + $this->setName($matches[1]); + } + } + return $this->get('name'); + } + + /** + * {@inheritdoc} + */ + public function setName($name) { + $this->name = $name; + return $this; + } + /** * {@inheritdoc} */ public function save() { - if ($this->isNew()) { + // The ID of a new OgRole has to consist of the entity type ID, bundle ID + // and role name, separated by dashes. + if ($this->isNew() && !empty($this->id())) { + list($entity_type_id, $bundle_id, $name) = explode('-', $this->id()); + if ($entity_type_id !== $this->getGroupType() || $bundle_id !== $this->getGroupBundle() || $name !== $this->getName()) { + throw new ConfigValueException('The ID should consist of the group entity type ID, group bundle ID and role name, separated by dashes.'); + } + } + + // If a new OgRole is saved and the ID is not set, construct the ID from + // the entity type ID, bundle ID and role name. + if ($this->isNew() && empty($this->id())) { if (empty($this->getGroupType())) { throw new ConfigValueException('The group type can not be empty.'); } @@ -187,6 +222,10 @@ public function save() { throw new ConfigValueException('The group bundle can not be empty.'); } + if (empty($this->getName())) { + throw new ConfigValueException('The role name can not be empty.'); + } + // When assigning a role to group we need to add a prefix to the ID in // order to prevent duplicate IDs. $prefix = $this->getGroupType() . '-' . $this->getGroupBundle() . '-'; @@ -195,7 +234,7 @@ public function save() { $prefix .= $this->getGroupId() . '-'; } - $this->id = $prefix . $this->id(); + $this->id = $prefix . $this->getName(); } parent::save(); @@ -233,33 +272,6 @@ public function delete() { parent::delete(); } - /** - * Returns default properties for the default OG roles. - * - * These are the two roles that are required by every group: the 'member' and - * 'non-member' roles. - * - * All other default roles are provided by DefaultRoleEvent. - * - * @return array - * An array of properties, keyed by OG role. - * - * @see \Drupal\og\Event\DefaultRoleEventInterface - * @see \Drupal\og\GroupManager::getDefaultRoles() - */ - public static function getDefaultRoles() { - return [ - self::ANONYMOUS => [ - 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, - 'label' => 'Non-member', - ], - self::AUTHENTICATED => [ - 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, - 'label' => 'Member', - ], - ]; - } - /** * Maps role names to role types. * diff --git a/src/Event/DefaultRoleEvent.php b/src/Event/DefaultRoleEvent.php index 82d8d7bf2..3e6679ce5 100644 --- a/src/Event/DefaultRoleEvent.php +++ b/src/Event/DefaultRoleEvent.php @@ -2,7 +2,7 @@ namespace Drupal\og\Event; -use Drupal\og\OgRoleInterface; +use Drupal\og\Entity\OgRole; use Symfony\Component\EventDispatcher\Event; /** @@ -41,35 +41,32 @@ public function getRoles() { /** * {@inheritdoc} */ - public function addRole($name, array $properties) { - if (array_key_exists($name, $this->roles)) { - throw new \InvalidArgumentException("The '$name' role already exists."); - } - $this->validate($name, $properties); + public function addRole(OgRole $role) { + $this->validate($role); - // Provide default value for the role type. - if (empty($properties['role_type'])) { - $properties['role_type'] = OgRoleInterface::ROLE_TYPE_STANDARD; + if (array_key_exists($role->getName(), $this->roles)) { + throw new \InvalidArgumentException("The '{$role->getName()}' role already exists."); } - $this->roles[$name] = $properties; + $this->roles[$role->getName()] = $role; } /** * {@inheritdoc} */ public function addRoles(array $roles) { - foreach ($roles as $role => $properties) { - $this->addRole($role, $properties); + foreach ($roles as $role) { + $this->addRole($role); } } /** * {@inheritdoc} */ - public function setRole($name, array $properties) { - $this->deleteRole($name); - $this->addRole($name, $properties); + public function setRole(OgRole $role) { + $this->validate($role); + $this->deleteRole($role->getName()); + $this->addRole($role); } /** @@ -77,7 +74,7 @@ public function setRole($name, array $properties) { */ public function setRoles(array $roles) { foreach ($roles as $name => $properties) { - $this->setRole($name, $properties); + $this->setRole($properties); } } @@ -105,8 +102,12 @@ public function offsetGet($key) { /** * {@inheritdoc} */ - public function offsetSet($key, $value) { - $this->setRole($key, $value); + public function offsetSet($key, $role) { + $this->validate($role); + if ($role->getName() !== $key) { + throw new \InvalidArgumentException('The key and the "name" property of the role should be identical.'); + } + $this->setRole($role); } /** @@ -131,33 +132,20 @@ public function getIterator() { } /** - * Validates a role that is about to be set or added. + * Validates that a role that is about to be set or added has a name. * - * @param string $name - * The name of the role to add or set. - * @param array $properties - * The role properties to validate. + * The roles are stored locally keyed by role name. + * + * @param \Drupal\og\Entity\OgRole $role + * The role to validate. * * @throws \InvalidArgumentException - * Thrown when the role name is empty, the 'label' property is missing, or - * the 'role_type' property is invalid. + * Thrown when the role name is empty. */ - protected function validate($name, $properties) { - if (empty($name)) { + protected function validate(OgRole $role) { + if (empty($role->getName())) { throw new \InvalidArgumentException('Role name is required.'); } - - if (empty($properties['label'])) { - throw new \InvalidArgumentException('The label property is required.'); - } - - $valid_role_types = [ - OgRoleInterface::ROLE_TYPE_STANDARD, - OgRoleInterface::ROLE_TYPE_REQUIRED, - ]; - if (!empty($properties['role_type']) && !in_array($properties['role_type'], $valid_role_types)) { - throw new \InvalidArgumentException('The role type is invalid.'); - } } } diff --git a/src/Event/DefaultRoleEventInterface.php b/src/Event/DefaultRoleEventInterface.php index 89023bfaa..4c4be6b60 100644 --- a/src/Event/DefaultRoleEventInterface.php +++ b/src/Event/DefaultRoleEventInterface.php @@ -2,6 +2,8 @@ namespace Drupal\og\Event; +use Drupal\og\Entity\OgRole; + /** * Interface for DefaultRoleEvent classes. * @@ -21,6 +23,10 @@ interface DefaultRoleEventInterface extends \ArrayAccess, \IteratorAggregate { * @param $name * The name of the role to return. * + * @return \Drupal\og\Entity\OgRole + * The OgRole entity. Note that we cannot specify OgRoleInterface here + * because of limitations in interface inheritance in PHP 5. + * * @throws \InvalidArgumentException * Thrown when the role with the given name does not exist. */ @@ -37,49 +43,43 @@ public function getRoles(); /** * Adds a default role. * - * @param string $name - * The name of the role to add. - * @param array $properties - * An associative array of role properties, keyed by the following: - * - 'label': The human readable label. - * - 'role_type': Either OgRoleInterface::ROLE_TYPE_STANDARD or - * OgRoleInterface::ROLE_TYPE_REQUIRED. Defaults to - * OgRoleInterface::ROLE_TYPE_STANDARD. + * @param \Drupal\og\Entity\OgRole $role + * The OgRole entity to add. This should be an unsaved entity that doesn't + * have the group entity type and bundle IDs set. * * @throws \InvalidArgumentException - * Thrown when the role that is added already exists, when the role name is - * empty, or when the 'label' property is missing. + * Thrown when the role that is added already exists. */ - public function addRole($name, array $properties); + public function addRole(OgRole $role); /** * Adds multiple default roles. * - * @param array $roles - * An associative array of default role properties, keyed by role name. + * @param \Drupal\og\Entity\OgRole[] $roles + * An array of OgRole entities to add. These should be unsaved entities that + * don't have the group entity type and bundle IDs set. */ public function addRoles(array $roles); /** * Sets a default roles. * - * @param string $name - * The name of the role to set. - * @param array $properties - * An associative array of role properties to set, keyed by the following: - * - 'label': The human readable label. + * @param \Drupal\og\Entity\OgRole $role + * The OgRole entity to set. This should be an unsaved entity that doesn't + * have the group entity type and bundle IDs set. * * @throws \InvalidArgumentException * Thrown when the role name is empty, or when the 'label' property is * missing. */ - public function setRole($name, array $properties); + public function setRole(OgRole $role); /** * Sets multiple default roles. * - * @param array $roles - * An associative array of default role properties, keyed by role name. + * @param \Drupal\og\Entity\OgRole[] $roles + * An array of OgRole entities to set. These should be unsaved entities that + * don't have the group entity type and bundle IDs set. */ public function setRoles(array $roles); @@ -95,7 +95,7 @@ public function deleteRole($name); * Returns whether or not the given role exists. * * @param string $name - * The name of the role for which to verify the existance. + * The name of the role for which to verify the existence. * * @return bool * TRUE if the role exists, FALSE otherwise. diff --git a/src/EventSubscriber/OgEventSubscriber.php b/src/EventSubscriber/OgEventSubscriber.php index ef6ef3269..7d9444b11 100644 --- a/src/EventSubscriber/OgEventSubscriber.php +++ b/src/EventSubscriber/OgEventSubscriber.php @@ -2,6 +2,7 @@ namespace Drupal\og\EventSubscriber; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\og\Event\DefaultRoleEventInterface; use Drupal\og\Event\PermissionEventInterface; use Drupal\og\OgRoleInterface; @@ -20,14 +21,24 @@ class OgEventSubscriber implements EventSubscriberInterface { */ protected $permissionManager; + /** + * The storage handler for OgRole entities. + * + * @var \Drupal\core\Entity\EntityStorageInterface + */ + protected $ogRoleStorage; + /** * Constructs an OgEventSubscriber object. * * @param \Drupal\og\PermissionManagerInterface $permission_manager * The OG permission manager. + * @param \Drupal\core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. */ - public function __construct(PermissionManagerInterface $permission_manager) { + public function __construct(PermissionManagerInterface $permission_manager, EntityTypeManagerInterface $entity_type_manager) { $this->permissionManager = $permission_manager; + $this->ogRoleStorage = $entity_type_manager->getStorage('og_role'); } /** @@ -69,7 +80,12 @@ public function provideDefaultOgPermissions(PermissionEventInterface $event) { * The default role event. */ public function provideDefaultRoles(DefaultRoleEventInterface $event) { - $event->addRole(OgRoleInterface::ADMINISTRATOR, ['label' => 'Administrator']); + $role = $this->ogRoleStorage->create([ + 'name' => OgRoleInterface::ADMINISTRATOR, + 'label' => 'Administrator', + 'is_admin' => TRUE, + ]); + $event->addRole($role); } } diff --git a/src/GroupManager.php b/src/GroupManager.php index c707a6c7e..291bf12f3 100644 --- a/src/GroupManager.php +++ b/src/GroupManager.php @@ -290,21 +290,17 @@ public function removeGroup($entity_type_id, $bundle_id) { * @todo: Would a dedicated RoleManager service be a better place for this? */ protected function createPerBundleRoles($entity_type_id, $bundle_id) { - foreach ($this->getDefaultRoles() as $role_name => $default_properties) { - $properties = [ - 'group_type' => $entity_type_id, - 'group_bundle' => $bundle_id, - 'id' => $role_name, - 'role_type' => OgRole::getRoleTypeByName($role_name), - ]; + foreach ($this->getDefaultRoles() as $role) { + $role->setGroupType($entity_type_id); + $role->setGroupBundle($bundle_id); // Populate the default permissions. $event = new PermissionEvent($entity_type_id, $bundle_id); /** @var \Drupal\og\Event\PermissionEventInterface $permissions */ $permissions = $this->eventDispatcher->dispatch(PermissionEventInterface::EVENT_NAME, $event); - $properties['permissions'] = array_keys($permissions->filterByDefaultRole($role_name)); - - $role = $this->ogRoleStorage->create($properties + $default_properties); + foreach (array_keys($permissions->filterByDefaultRole($role->getName())) as $permission) { + $role->grantPermission($permission); + } $role->save(); } } @@ -312,21 +308,59 @@ protected function createPerBundleRoles($entity_type_id, $bundle_id) { /** * Returns the default roles. * - * @return array - * An associative array of default role properties, keyed by role name. Each - * role property is an associative array with the following keys: - * - 'label': The human readable label. - * - 'role_type': Either OgRoleInterface::ROLE_TYPE_STANDARD or - * OgRoleInterface::ROLE_TYPE_REQUIRED. + * @return \Drupal\og\Entity\OgRole[] + * An associative array of (unsaved) OgRole entities, keyed by role name. * * @todo: Would a dedicated RoleManager service be a better place for this? */ public function getDefaultRoles() { - /** @var \Drupal\og\Event\DefaultRoleEvent $default_role_event */ + // Provide the required default roles: 'member' and 'non-member'. + $roles = $this->getRequiredDefaultRoles(); + $event = new DefaultRoleEvent(); - $default_role_event = $this->eventDispatcher->dispatch(DefaultRoleEventInterface::EVENT_NAME, $event); + $this->eventDispatcher->dispatch(DefaultRoleEventInterface::EVENT_NAME, $event); + + // Use the array union operator '+=' to ensure the default roles cannot be + // altered by event subscribers. + $roles += $event->getRoles(); + + return $roles; + } + + /** + * Returns the roles which every group type requires. + * + * This provides the 'member' and 'non-member' roles. These are hard coded + * because they are strictly required and should not be altered. + * + * @return \Drupal\og\Entity\OgRole[] + * An associative array of (unsaved) required OgRole entities, keyed by role + * name. These are populated with the basic properties: name, label and + * role_type. + * + * @todo: Would a dedicated RoleManager service be a better place for this? + */ + protected function getRequiredDefaultRoles() { + $roles = []; + + $role_properties = [ + [ + 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, + 'label' => 'Non-member', + 'name' => OgRoleInterface::ANONYMOUS, + ], + [ + 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, + 'label' => 'Member', + 'name' => OgRoleInterface::AUTHENTICATED, + ], + ]; + + foreach ($role_properties as $properties) { + $roles[$properties['name']] = $this->ogRoleStorage->create($properties); + } - return OgRole::getDefaultRoles() + $default_role_event->getRoles(); + return $roles; } /** diff --git a/src/OgRoleInterface.php b/src/OgRoleInterface.php index 33da49184..928c5177f 100644 --- a/src/OgRoleInterface.php +++ b/src/OgRoleInterface.php @@ -51,4 +51,22 @@ interface OgRoleInterface { */ public function setId($id); + /** + * Returns the role name. + * + * @return string + * The role name. + */ + public function getName(); + + /** + * Sets the role name. + * + * @param string $name + * The role name + * + * @return $this + */ + public function setName($name); + } diff --git a/tests/src/Kernel/Access/OgEntityAccessTest.php b/tests/src/Kernel/Access/OgEntityAccessTest.php index 1b4d22f93..52417a9f1 100644 --- a/tests/src/Kernel/Access/OgEntityAccessTest.php +++ b/tests/src/Kernel/Access/OgEntityAccessTest.php @@ -149,7 +149,7 @@ protected function setUp() { /** @var OgRole ogRoleWithPermission */ $this->ogRoleWithPermission = OgRole::create(); $this->ogRoleWithPermission - ->setId($this->randomMachineName()) + ->setName($this->randomMachineName()) ->setLabel($this->randomString()) ->setGroupType($this->group1->getEntityTypeId()) ->setGroupBundle($this->groupBundle) @@ -159,7 +159,7 @@ protected function setUp() { $this->ogRoleWithPermission2 = OgRole::create(); $this->ogRoleWithPermission2 - ->setId($this->randomMachineName()) + ->setName($this->randomMachineName()) ->setLabel($this->randomString()) ->setGroupType($this->group1->getEntityTypeId()) ->setGroupBundle($this->groupBundle) @@ -170,7 +170,7 @@ protected function setUp() { /** @var OgRole ogRoleWithoutPermission */ $this->ogRoleWithoutPermission = OgRole::create(); $this->ogRoleWithoutPermission - ->setId($this->randomMachineName()) + ->setName($this->randomMachineName()) ->setLabel($this->randomString()) ->setGroupType($this->group1->getEntityTypeId()) ->setGroupBundle($this->groupBundle) @@ -179,7 +179,7 @@ protected function setUp() { $this->ogAdminRole = OgRole::create(); $this->ogAdminRole - ->setId($this->randomMachineName()) + ->setName($this->randomMachineName()) ->setLabel($this->randomString()) ->setGroupType($this->group1->getEntityTypeId()) ->setGroupBundle($this->groupBundle) diff --git a/tests/src/Kernel/DefaultPermissionEventIntegrationTest.php b/tests/src/Kernel/DefaultRoleEventIntegrationTest.php similarity index 80% rename from tests/src/Kernel/DefaultPermissionEventIntegrationTest.php rename to tests/src/Kernel/DefaultRoleEventIntegrationTest.php index eecaeff16..f199b6b90 100644 --- a/tests/src/Kernel/DefaultPermissionEventIntegrationTest.php +++ b/tests/src/Kernel/DefaultRoleEventIntegrationTest.php @@ -62,17 +62,20 @@ public function setUp() { * Tests that OG correctly provides the group administrator default role. */ public function testPermissionEventIntegration() { + /** @var DefaultRoleEvent $event */ + $event = new DefaultRoleEvent(); + // Query the event listener directly to see if the administrator role is // present. - /** @var DefaultRoleEvent $event */ - $event = $this->eventDispatcher->dispatch(DefaultRoleEventInterface::EVENT_NAME, new DefaultRoleEvent()); - $expected_roles = [ - OgRoleInterface::ADMINISTRATOR => [ - 'label' => 'Administrator', - 'role_type' => OgRoleInterface::ROLE_TYPE_STANDARD, - ], - ]; - $this->assertEquals($event->getRoles(), $expected_roles); + $this->eventDispatcher->dispatch(DefaultRoleEventInterface::EVENT_NAME, $event); + $this->assertEquals([OgRoleInterface::ADMINISTRATOR], array_keys($event->getRoles())); + + // Check that the role was created with the correct values. + $role = $event->getRole(OgRoleInterface::ADMINISTRATOR); + $this->assertEquals(OgRoleInterface::ADMINISTRATOR, $role->getName()); + $this->assertEquals('Administrator', $role->getLabel()); + $this->assertEquals(OgRoleInterface::ROLE_TYPE_STANDARD, $role->getRoleType()); + $this->assertTrue($role->isAdmin()); // Check that the per-group-type default roles are populated. $expected_roles = [ diff --git a/tests/src/Kernel/Entity/OgMembershipRoleReferenceTest.php b/tests/src/Kernel/Entity/OgMembershipRoleReferenceTest.php index 3fbecdedc..2c9486c57 100644 --- a/tests/src/Kernel/Entity/OgMembershipRoleReferenceTest.php +++ b/tests/src/Kernel/Entity/OgMembershipRoleReferenceTest.php @@ -77,7 +77,7 @@ public function testRoleCreate() { $content_editor ->setGroupType('node') ->setGroupBundle('group') - ->setId('content_editor') + ->setName('content_editor') ->setLabel('Content editor') ->grantPermission('administer group'); $content_editor->save(); @@ -87,7 +87,7 @@ public function testRoleCreate() { $group_member ->setGroupType('node') ->setGroupBundle('group') - ->setId('group_member') + ->setName('group_member') ->setLabel('Group member'); $group_member->save(); diff --git a/tests/src/Kernel/Entity/OgRoleTest.php b/tests/src/Kernel/Entity/OgRoleTest.php index 9971764c4..df944531f 100644 --- a/tests/src/Kernel/Entity/OgRoleTest.php +++ b/tests/src/Kernel/Entity/OgRoleTest.php @@ -40,7 +40,7 @@ protected function setUp() { public function testRoleCreate() { $og_role = OgRole::create(); $og_role - ->setId('content_editor') + ->setName('content_editor') ->setLabel('Content editor') ->grantPermission('administer group'); @@ -66,7 +66,7 @@ public function testRoleCreate() { try { $og_role = OgRole::create(); $og_role - ->setId('content_editor') + ->setName('content_editor') ->setLabel('Content editor') ->setGroupType('node') ->setGroupBundle('group') @@ -82,7 +82,7 @@ public function testRoleCreate() { // Create a role assigned to a group type. $og_role = OgRole::create(); $og_role - ->setId('content_editor') + ->setName('content_editor') ->setLabel('Content editor') ->setGroupType('entity_test') ->setGroupBundle('group') @@ -95,7 +95,7 @@ public function testRoleCreate() { try { $og_role = OgRole::create(); $og_role - ->setId('content_editor') + ->setName('content_editor') ->setLabel('Content editor') ->setGroupType('entity_test') ->setGroupBundle('group') @@ -107,6 +107,40 @@ public function testRoleCreate() { catch (EntityStorageException $e) { $this->assertTrue(TRUE, "OG role with the same ID on the same group can not be saved."); } + + // Try to save a role with an ID instead of a name. This is how the Config + // system will create a role from data stored in a YAML file. + $og_role = OgRole::create([ + 'id' => 'entity_test-group-configurator', + 'label' => 'Configurator', + 'group_type' => 'entity_test', + 'group_bundle' => 'group', + ]); + $og_role->save(); + + $this->assertNotEmpty(OgRole::load('entity_test-group-configurator')); + + // Check that we can retrieve the role name correctly. This was not + // explicitly saved but it should be possible to derive this from the ID. + $this->assertEquals('configurator', $og_role->getName()); + + // When a role is saved with an ID that does not matches the pattern + // 'entity type-bundle-role name' then an exception should be thrown. + try { + $og_role = OgRole::create(); + $og_role + ->setId('entity_test-group-wrong_id') + ->setName('content_editor') + ->setLabel('Content editor') + ->setGroupType('entity_test') + ->setGroupBundle('group') + ->save(); + + $this->fail('OG role with a non-matching ID can be saved.'); + } + catch (ConfigValueException $e) { + $this->assertTrue(TRUE, "OG role with a non-matching ID can not be saved."); + } } } diff --git a/tests/src/Unit/DefaultRoleEventTest.php b/tests/src/Unit/DefaultRoleEventTest.php index 3ce72d441..a207dd21e 100644 --- a/tests/src/Unit/DefaultRoleEventTest.php +++ b/tests/src/Unit/DefaultRoleEventTest.php @@ -2,6 +2,9 @@ namespace Drupal\Tests\og\Unit; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\og\Entity\OgRole; use Drupal\og\Event\DefaultRoleEvent; use Drupal\og\OgRoleInterface; use Drupal\Tests\UnitTestCase; @@ -12,6 +15,32 @@ */ class DefaultRoleEventTest extends UnitTestCase { + /** + * The DefaultRoleEvent class, which is the system under test. + * + * @var \Drupal\og\Event\DefaultRoleEvent + */ + protected $defaultRoleEvent; + + /** + * The mocked OgRole entity storage. + * + * @var \Drupal\core\Entity\EntityStorageInterface|\Prophecy\Prophecy\ObjectProphecy + */ + protected $ogRoleStorage; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); + $this->ogRoleStorage = $this->prophesize(EntityStorageInterface::class); + $entity_type_manager->getStorage('og_role')->willReturn($this->ogRoleStorage->reveal()); + $this->defaultRoleEvent = new DefaultRoleEvent($entity_type_manager->reveal()); + } + /** * @param array $roles * An array of test default roles. @@ -21,11 +50,12 @@ class DefaultRoleEventTest extends UnitTestCase { * @dataProvider defaultRoleProvider */ public function testGetRole($roles) { - $event = new DefaultRoleEvent(); - $event->setRoles($roles); + $this->expectOgRoleCreation($roles); + + $this->defaultRoleEvent->setRoles($roles); foreach ($roles as $name => $role) { - $this->assertRoleEquals($role, $event->getRole($name)); + $this->assertRoleEquals($role, $this->defaultRoleEvent->getRole($name)); } } @@ -39,10 +69,11 @@ public function testGetRole($roles) { * @dataProvider defaultRoleProvider */ public function testGetRoles($roles) { - $event = new DefaultRoleEvent(); - $event->setRoles($roles); + $this->expectOgRoleCreation($roles); - $actual_roles = $event->getRoles(); + $this->defaultRoleEvent->setRoles($roles); + + $actual_roles = $this->defaultRoleEvent->getRoles(); foreach ($roles as $name => $role) { $this->assertRoleEquals($role, $actual_roles[$name]); } @@ -59,15 +90,16 @@ public function testGetRoles($roles) { * @dataProvider defaultRoleProvider */ public function testAddRole($roles) { - $event = new DefaultRoleEvent(); + $this->expectOgRoleCreation($roles); + foreach ($roles as $name => $role) { - $this->assertFalse($event->hasRole($name)); - $event->addRole($name, $role); - $this->assertRoleEquals($role, $event->getRole($name)); + $this->assertFalse($this->defaultRoleEvent->hasRole($name)); + $this->defaultRoleEvent->addRole($role); + $this->assertRoleEquals($role, $this->defaultRoleEvent->getRole($name)); // Adding a role a second time should throw an exception. try { - $event->addRole($name, $role); + $this->defaultRoleEvent->addRole($role); $this->fail('It should not be possible to add a role with the same name a second time.'); } catch (\InvalidArgumentException $e) { @@ -85,10 +117,11 @@ public function testAddRole($roles) { * @dataProvider defaultRoleProvider */ public function testAddRoles($roles) { - $event = new DefaultRoleEvent(); - $event->addRoles($roles); + $this->expectOgRoleCreation($roles); + + $this->defaultRoleEvent->addRoles($roles); - $actual_roles = $event->getRoles(); + $actual_roles = $this->defaultRoleEvent->getRoles(); foreach ($roles as $name => $role) { $this->assertRoleEquals($role, $actual_roles[$name]); } @@ -105,15 +138,16 @@ public function testAddRoles($roles) { * @dataProvider defaultRoleProvider */ public function testSetRole($roles) { - $event = new DefaultRoleEvent(); + $this->expectOgRoleCreation($roles); + foreach ($roles as $name => $role) { - $this->assertFalse($event->hasRole($name)); - $event->setRole($name, $role); - $this->assertRoleEquals($role, $event->getRole($name)); + $this->assertFalse($this->defaultRoleEvent->hasRole($name)); + $this->defaultRoleEvent->setRole($role); + $this->assertRoleEquals($role, $this->defaultRoleEvent->getRole($name)); // Setting a role a second time should be possible. No exception should be // thrown. - $event->setRole($name, $role); + $this->defaultRoleEvent->setRole($role); } } @@ -126,13 +160,14 @@ public function testSetRole($roles) { * @dataProvider defaultRoleProvider */ public function testDeleteRole($roles) { - $event = new DefaultRoleEvent(); - $event->setRoles($roles); + $this->expectOgRoleCreation($roles); + + $this->defaultRoleEvent->setRoles($roles); foreach ($roles as $name => $role) { - $this->assertTrue($event->hasRole($name)); - $event->deleteRole($name); - $this->assertFalse($event->hasRole($name)); + $this->assertTrue($this->defaultRoleEvent->hasRole($name)); + $this->defaultRoleEvent->deleteRole($name); + $this->assertFalse($this->defaultRoleEvent->hasRole($name)); } } @@ -145,11 +180,12 @@ public function testDeleteRole($roles) { * @dataProvider defaultRoleProvider */ public function testHasRole($roles) { - $event = new DefaultRoleEvent(); + $this->expectOgRoleCreation($roles); + foreach ($roles as $name => $role) { - $this->assertFalse($event->hasRole($name)); - $event->addRole($name, $role); - $this->assertTrue($event->hasRole($name)); + $this->assertFalse($this->defaultRoleEvent->hasRole($name)); + $this->defaultRoleEvent->addRole($role); + $this->assertTrue($this->defaultRoleEvent->hasRole($name)); } } @@ -162,11 +198,12 @@ public function testHasRole($roles) { * @dataProvider defaultRoleProvider */ public function testOffsetGet($roles) { - $event = new DefaultRoleEvent(); - $event->setRoles($roles); + $this->expectOgRoleCreation($roles); + + $this->defaultRoleEvent->setRoles($roles); foreach ($roles as $name => $role) { - $this->assertRoleEquals($role, $event[$name]); + $this->assertRoleEquals($role, $this->defaultRoleEvent[$name]); } } @@ -179,12 +216,12 @@ public function testOffsetGet($roles) { * @dataProvider defaultRoleProvider */ public function testOffsetSet($roles) { - $event = new DefaultRoleEvent(); + $this->expectOgRoleCreation($roles); foreach ($roles as $name => $role) { - $this->assertFalse($event->hasRole($name)); - $event[$name] = $role; - $this->assertRoleEquals($role, $event->getRole($name)); + $this->assertFalse($this->defaultRoleEvent->hasRole($name)); + $this->defaultRoleEvent[$name] = $role; + $this->assertRoleEquals($role, $this->defaultRoleEvent->getRole($name)); } } @@ -197,13 +234,14 @@ public function testOffsetSet($roles) { * @dataProvider defaultRoleProvider */ public function testOffsetUnset($roles) { - $event = new DefaultRoleEvent(); - $event->setRoles($roles); + $this->expectOgRoleCreation($roles); + + $this->defaultRoleEvent->setRoles($roles); foreach ($roles as $name => $role) { - $this->assertTrue($event->hasRole($name)); - unset($event[$name]); - $this->assertFalse($event->hasRole($name)); + $this->assertTrue($this->defaultRoleEvent->hasRole($name)); + unset($this->defaultRoleEvent[$name]); + $this->assertFalse($this->defaultRoleEvent->hasRole($name)); } } @@ -216,11 +254,12 @@ public function testOffsetUnset($roles) { * @dataProvider defaultRoleProvider */ public function testOffsetExists($roles) { - $event = new DefaultRoleEvent(); + $this->expectOgRoleCreation($roles); + foreach ($roles as $name => $role) { - $this->assertFalse(isset($event[$name])); - $event->addRole($name, $role); - $this->assertTrue(isset($event[$name])); + $this->assertFalse(isset($this->defaultRoleEvent[$name])); + $this->defaultRoleEvent->addRole($role); + $this->assertTrue(isset($this->defaultRoleEvent[$name])); } } @@ -233,10 +272,11 @@ public function testOffsetExists($roles) { * @dataProvider defaultRoleProvider */ public function testIteratorAggregate($roles) { - $event = new DefaultRoleEvent(); - $event->setRoles($roles); + $this->expectOgRoleCreation($roles); + + $this->defaultRoleEvent->setRoles($roles); - foreach ($event as $name => $role) { + foreach ($this->defaultRoleEvent as $name => $role) { $this->assertRoleEquals($roles[$name], $role); unset($roles[$name]); } @@ -254,10 +294,11 @@ public function testIteratorAggregate($roles) { * @dataProvider invalidDefaultRoleProvider */ public function testAddInvalidRole($invalid_roles) { - $event = new DefaultRoleEvent(); + $this->expectOgRoleCreation($invalid_roles); + try { foreach ($invalid_roles as $name => $invalid_role) { - $event->addRole($name, $invalid_role); + $this->defaultRoleEvent->addRole($invalid_role); } $this->fail('An invalid role cannot be added.'); } @@ -275,18 +316,13 @@ public function testAddInvalidRole($invalid_roles) { * @covers ::addRoles * * @dataProvider invalidDefaultRoleProvider + * @expectedException \InvalidArgumentException */ public function testAddInvalidRoles($invalid_roles) { - $event = new DefaultRoleEvent(); - try { - $event->addRoles($invalid_roles); - $this->fail('An array of invalid roles cannot be added.'); - } - catch (\InvalidArgumentException $e) { - // Expected result. Do an arbitrary assertion so the test is not marked as - // risky. - $this->assertTrue(TRUE); - } + $this->expectOgRoleCreation($invalid_roles); + + $this->defaultRoleEvent->addRoles($invalid_roles); + $this->fail('An array of invalid roles cannot be added.'); } /** @@ -296,19 +332,13 @@ public function testAddInvalidRoles($invalid_roles) { * @covers ::setRole * * @dataProvider invalidDefaultRoleProvider + * @expectedException \InvalidArgumentException */ public function testSetInvalidRole($invalid_roles) { - $event = new DefaultRoleEvent(); - try { - foreach ($invalid_roles as $name => $invalid_role) { - $event->setRole($name, $invalid_role); - } - $this->fail('An invalid role cannot be set.'); - } - catch (\InvalidArgumentException $e) { - // Expected result. Do an arbitrary assertion so the test is not marked as - // risky. - $this->assertTrue(TRUE); + $this->expectOgRoleCreation($invalid_roles); + + foreach ($invalid_roles as $name => $invalid_role) { + $this->defaultRoleEvent->setRole($invalid_role); } } @@ -319,18 +349,12 @@ public function testSetInvalidRole($invalid_roles) { * @covers ::setRoles * * @dataProvider invalidDefaultRoleProvider + * @expectedException \InvalidArgumentException */ public function testSetInvalidRoles($invalid_roles) { - $event = new DefaultRoleEvent(); - try { - $event->setRoles($invalid_roles); - $this->fail('An array of invalid roles cannot be set.'); - } - catch (\InvalidArgumentException $e) { - // Expected result. Do an arbitrary assertion so the test is not marked as - // risky. - $this->assertTrue(TRUE); - } + $this->expectOgRoleCreation($invalid_roles); + + $this->defaultRoleEvent->setRoles($invalid_roles); } /** @@ -340,19 +364,13 @@ public function testSetInvalidRoles($invalid_roles) { * @covers ::offsetSet * * @dataProvider invalidDefaultRoleProvider + * @expectedException \InvalidArgumentException */ public function testInvalidOffsetSet($invalid_roles) { - $event = new DefaultRoleEvent(); - try { - foreach ($invalid_roles as $name => $invalid_role) { - $event[$name] = $invalid_role; - } - $this->fail('An invalid role cannot be set through ArrayAccess.'); - } - catch (\InvalidArgumentException $e) { - // Expected result. Do an arbitrary assertion so the test is not marked as - // risky. - $this->assertTrue(TRUE); + $this->expectOgRoleCreation($invalid_roles); + + foreach ($invalid_roles as $name => $invalid_role) { + $this->defaultRoleEvent[$name] = $invalid_role; } } @@ -365,16 +383,21 @@ public function testInvalidOffsetSet($invalid_roles) { */ public function defaultRoleProvider() { return [ - // Test adding a single administrator role with only a label. + // Test adding a single administrator role with only the required + // properties. [ [ - OgRoleInterface::ADMINISTRATOR => ['label' => $this->t('Administrator')], + OgRoleInterface::ADMINISTRATOR => [ + 'name' => OgRoleInterface::ADMINISTRATOR, + 'label' => $this->t('Administrator'), + ], ], ], // Test adding a single administrator role with a label and role type. [ [ OgRoleInterface::ADMINISTRATOR => [ + 'name' => OgRoleInterface::ADMINISTRATOR, 'label' => $this->t('Administrator'), 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, ], @@ -384,14 +407,17 @@ public function defaultRoleProvider() { [ [ OgRoleInterface::ADMINISTRATOR => [ + 'name' => OgRoleInterface::ADMINISTRATOR, 'label' => $this->t('Administrator'), 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, ], 'moderator' => [ + 'name' => 'moderator', 'label' => $this->t('Moderator'), 'role_type' => OgRoleInterface::ROLE_TYPE_STANDARD, ], 'contributor' => [ + 'name' => 'contributor', 'label' => $this->t('Contributor'), ], ], @@ -414,38 +440,22 @@ public function invalidDefaultRoleProvider() { '' => ['label' => $this->t('Administrator')], ], ], - // A role without a label. - [ - [ - OgRoleInterface::ADMINISTRATOR => [ - 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, - ], - ], - ], - // A role with an invalid role type. - [ - [ - OgRoleInterface::ADMINISTRATOR => [ - 'label' => $this->t('Administrator'), - 'role_type' => 'Some non-existing role type', - ], - ], - ], // An array of multiple correct roles, with one invalid role type sneaked // in. [ [ OgRoleInterface::ADMINISTRATOR => [ + 'name' => OgRoleInterface::ADMINISTRATOR, 'label' => $this->t('Administrator'), 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, ], 'moderator' => [ + 'name' => 'moderator', 'label' => $this->t('Moderator'), 'role_type' => OgRoleInterface::ROLE_TYPE_STANDARD, ], - 'contributor' => [ - 'label' => $this->t('Contributor'), - 'role_type' => 'Some non-existing role type', + 'role with missing name' => [ + 'label' => $this->t('Invalid role'), ], ], ], @@ -469,17 +479,32 @@ protected function t($string) { /** * Asserts that the given role properties matches the expected result. * - * @param array $expected - * An array of expected role properties. - * @param array $actual - * An array of actual role properties. + * @param \Drupal\og\Entity\OgRole $expected + * The expected role. + * @param \Drupal\og\Entity\OgRole $actual + * The actual OgRole entity to check. + * + * Note that we are not specifying the OgRoleInterface type because of a PHP 5 + * class inheritance limitation. + */ + protected function assertRoleEquals(OgRole $expected, OgRole $actual) { + foreach (['name', 'label', 'role_type', 'is_admin'] as $property) { + $this->assertEquals($expected->get($property), $actual->get($property)); + } + } + + /** + * Adds an expectation that roles with the given properties should be created. + * + * @param \Drupal\og\Entity\OgRole[] $roles + * An array of role properties that are expected to be passed to the roles + * that should be created. */ - protected function assertRoleEquals(array $expected, array $actual) { - // Provide default value for the role type. - if (empty($expected['role_type'])) { - $expected['role_type'] = OgRoleInterface::ROLE_TYPE_STANDARD; + protected function expectOgRoleCreation(array &$roles) { + foreach ($roles as &$properties) { + $role = new OgRole($properties, 'og_role'); + $properties = $role; } - $this->assertEquals($expected, $actual); } } diff --git a/tests/src/Unit/GroupManagerTest.php b/tests/src/Unit/GroupManagerTest.php index 30a6ae2d5..7fe029b04 100644 --- a/tests/src/Unit/GroupManagerTest.php +++ b/tests/src/Unit/GroupManagerTest.php @@ -74,11 +74,6 @@ class GroupManagerTest extends UnitTestCase { */ protected $stateProphecy; - /** - * @var \Drupal\og\Event\DefaultRoleEventInterface|\Prophecy\Prophecy\ObjectProphecy - */ - protected $defaultRoleEventProphecy; - /** * {@inheritdoc} */ @@ -92,7 +87,6 @@ public function setUp() { $this->eventDispatcherProphecy = $this->prophesize(EventDispatcherInterface::class); $this->permissionEventProphecy = $this->prophesize(PermissionEventInterface::class); $this->stateProphecy = $this->prophesize(StateInterface::class); - $this->defaultRoleEventProphecy = $this->prophesize(DefaultRoleEvent::class); } /** @@ -164,6 +158,7 @@ public function testGetGroupsForEntityType() { /** * @covers ::addGroup + * @expectedException \InvalidArgumentException */ public function testAddGroupExisting() { // It is expected that the group map will be retrieved from config. @@ -179,13 +174,7 @@ public function testAddGroupExisting() { $manager = $this->createGroupManager(); // Add to existing. - try { - $manager->addGroup('test_entity', 'c'); - $this->fail('An entity type that was already declared as a group, was redeclared as a group'); - } catch (\InvalidArgumentException $e) { - // Expected result. An exception should be thrown when an entity type is - // redeclared as a group. - } + $manager->addGroup('test_entity', 'c'); } /** @@ -308,10 +297,6 @@ protected function expectDefaultRoleCreation($entity_type, $bundle) { // expected that the list of default roles to populate will be retrieved // from the event listener. $this->eventDispatcherProphecy->dispatch(DefaultRoleEventInterface::EVENT_NAME, Argument::type(DefaultRoleEvent::class)) - ->willReturn($this->defaultRoleEventProphecy->reveal()) - ->shouldBeCalled(); - $this->defaultRoleEventProphecy->getRoles() - ->willReturn([]) ->shouldBeCalled(); foreach ([OgRoleInterface::ANONYMOUS, OgRoleInterface::AUTHENTICATED] as $role_name) { @@ -330,25 +315,44 @@ protected function expectDefaultRoleCreation($entity_type, $bundle) { * The name of the role being created. */ protected function addNewDefaultRole($entity_type, $bundle, $role_name) { + // Make references of class properties in the local scope so that we can + // pass them to anonymous functions which are used as Prophecy promises. + $permission_event = $this->permissionEventProphecy; + $og_role = $this->ogRoleProphecy; + // It is expected that the OG permissions that need to be populated on the - // new role will be requested from the PermissionEvent listener. + // new role will be requested from the PermissionEvent listener. In order to + // get the role name, this will be requested from the OgRole object. $this->eventDispatcherProphecy->dispatch(PermissionEventInterface::EVENT_NAME, Argument::type('\Drupal\og\Event\PermissionEvent')) ->willReturn($this->permissionEventProphecy->reveal()) ->shouldBeCalled(); - $this->permissionEventProphecy->filterByDefaultRole($role_name) - ->willReturn([]) - ->shouldBeCalled(); // It is expected that the role will be created with default properties. - $properties = [ - 'group_type' => $entity_type, - 'group_bundle' => $bundle, - 'role_type' => OgRole::getRoleTypeByName($role_name), - 'id' => $role_name, - 'permissions' => [], - ]; - $this->entityStorageProphecy->create($properties + OgRole::getDefaultRoles()[$role_name]) - ->willReturn($this->ogRoleProphecy->reveal()) + $this->entityStorageProphecy->create($this->getDefaultRoleProperties($role_name)) + ->will(function () use ($entity_type, $bundle, $role_name, $permission_event, $og_role) { + // For each role that is created it is expected that the role name will + // be retrieved, so that the role name can be used to filter the + // permissions. + // This type of behavior is mocked in Prophecy using a 'promise' - the + // call to getName() returns a different result depending on the last + // call that was made to EntityStorageInterface::create(), and by itself + // it changes the argument that is used for filterByDefaultRole(). + // @see https://github.com/phpspec/prophecy#arguments-wildcarding + $og_role->getName() + ->will(function () use ($role_name, $permission_event) { + $permission_event->filterByDefaultRole($role_name) + ->willReturn([]) + ->shouldBeCalled(); + return $role_name; + }) + ->shouldBeCalled(); + + // The group type, bundle and permissions will have to be set on the new + // role. + $og_role->setGroupType($entity_type)->shouldBeCalled(); + $og_role->setGroupBundle($bundle)->shouldBeCalled(); + return $og_role->reveal(); + }) ->shouldBeCalled(); // The role is expected to be saved. @@ -357,6 +361,32 @@ protected function addNewDefaultRole($entity_type, $bundle, $role_name) { ->shouldBeCalled(); } + /** + * Returns the expected properties of the default role with the given name. + * + * @param string $role_name + * The name of the default role for which to return the properties. + * + * @return array + * The default properties. + */ + protected function getDefaultRoleProperties($role_name) { + $role_properties = [ + OgRoleInterface::ANONYMOUS => [ + 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, + 'label' => 'Non-member', + 'name' => OgRoleInterface::ANONYMOUS, + ], + OgRoleInterface::AUTHENTICATED => [ + 'role_type' => OgRoleInterface::ROLE_TYPE_REQUIRED, + 'label' => 'Member', + 'name' => OgRoleInterface::AUTHENTICATED, + ], + ]; + + return $role_properties[$role_name]; + } + /** * Expected method calls when deleting roles after a group is deleted. *