og-8.x-1.x-dev/tests/src/Kernel/Entity/OgSelectionTest.php
tests/src/Kernel/Entity/OgSelectionTest.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\og\Kernel\Entity;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\og\Entity\OgRole;
use Drupal\og\Og;
use Drupal\og\OgGroupAudienceHelperInterface;
use Drupal\og\OgRoleInterface;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
/**
* Tests entity reference selection plugins.
*
* @group og
*/
class OgSelectionTest extends KernelTestBase {
/**
* The selection handler.
*/
protected SelectionInterface $selectionHandler;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'user',
'field',
'node',
'og',
'options',
];
/**
* A site-wide group administrator.
*/
protected UserInterface $groupAdmin;
/**
* A group manager.
*/
protected UserInterface $groupManager;
/**
* A regular group member.
*/
protected UserInterface $groupMember;
/**
* A user with 'bypass node access' permission.
*/
protected UserInterface $nodeBypassUser;
/**
* The machine name of the group node type.
*/
protected string $groupBundle;
/**
* The machine name of the group content node type.
*/
protected string $groupContentBundle;
/**
* The field definition used in this test.
*/
protected FieldDefinitionInterface $fieldDefinition;
/**
* Selection plugin manager.
*/
protected SelectionPluginManagerInterface $selectionPluginManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Add membership and config schema.
$this->installConfig(['og']);
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installSchema('system', 'sequences');
$this->installSchema('node', 'node_access');
// Setting up variables.
$this->groupBundle = mb_strtolower($this->randomMachineName());
$this->groupContentBundle = mb_strtolower($this->randomMachineName());
$this->selectionPluginManager = $this->container->get('plugin.manager.entity_reference_selection');
// Create a group.
NodeType::create([
'type' => $this->groupBundle,
'name' => $this->randomString(),
])->save();
// Create a group content type.
NodeType::create([
'type' => $this->groupContentBundle,
'name' => $this->randomString(),
])->save();
// Define bundle as group.
Og::groupTypeManager()->addGroup('node', $this->groupBundle);
// Add og audience field to group content.
$this->fieldDefinition = Og::createField(OgGroupAudienceHelperInterface::DEFAULT_FIELD, 'node', $this->groupContentBundle);
// The selection handler for the field.
// Get the storage of the field.
$options = [
'target_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type'),
'handler' => $this->fieldDefinition->getSetting('handler'),
];
$this->selectionPluginManager->getInstance($options);
$this->selectionHandler = $this->selectionPluginManager->getSelectionHandler($this->fieldDefinition);
// Create users.
$this->groupAdmin = User::create(['name' => $this->randomString()]);
$this->groupAdmin->save();
$this->groupManager = User::create(['name' => $this->randomString()]);
$this->groupManager->save();
$this->groupMember = User::create(['name' => $this->randomString()]);
$this->groupMember->save();
$this->nodeBypassUser = User::create(['name' => $this->randomString()]);
$this->nodeBypassUser->save();
// Assign administer-group permission to admin.
$role = Role::create([
'id' => $this->randomMachineName(),
'label' => $this->randomMachineName(),
]);
$role
->grantPermission('administer organic groups')
->save();
$this
->groupAdmin
->addRole($role->id());
$node_bypass_role = Role::create([
'id' => $this->randomMachineName(),
'label' => $this->randomMachineName(),
]);
$node_bypass_role
->grantPermission('bypass node access')
->save();
$this->nodeBypassUser->addRole($node_bypass_role->id());
// Set the group content entity stub on the selection handler's config.
$group_content = Node::create([
'type' => $this->groupContentBundle,
'title' => $this->randomString(),
]);
$this->selectionHandler->setConfiguration([
'target_type' => 'node',
'entity' => $group_content,
]);
}
/**
* Testing the OG manager selection handler.
*
* We need to verify that the manager selection handler will use the default
* selection manager of the entity which the audience field referencing to.
*
* i.e: When the field referencing to node, we need verify we got the default
* node selection handler.
*/
public function testSelectionHandler(): void {
$this->assertEquals(get_class($this->selectionHandler->getSelectionHandler()), 'Drupal\node\Plugin\EntityReferenceSelection\NodeSelection');
}
/**
* Testing OG selection handler results.
*
* We need to verify that each user gets the groups they own in the normal
* widget and the other users' groups in the other groups widget and vice
* versa.
*/
public function testSelectionHandlerResults(): void {
$user1_groups = $this->createGroups(5, $this->groupAdmin);
$user2_groups = $this->createGroups(5, $this->groupManager);
$all_groups_ids = array_merge($user1_groups, $user2_groups);
// Admin user can create content on all groups.
$this->setCurrentAccount($this->groupAdmin);
$groups = $this->selectionHandler->getReferenceableEntities();
$this->assertEquals($all_groups_ids, array_keys($groups[$this->groupBundle]));
// Group manager can create content in their groups.
$this->setCurrentAccount($this->groupManager);
$groups = $this->selectionHandler->getReferenceableEntities();
$this->assertEquals($user2_groups, array_keys($groups[$this->groupBundle]));
// Non-group member.
$this->setCurrentAccount($this->groupMember);
$groups = $this->selectionHandler->getReferenceableEntities();
$this->assertArrayNotHasKey($this->groupBundle, $groups);
// Add group member to first group from group admin's groups to
// create content.
$group_id = $user1_groups[0];
$group = Node::load($group_id);
$membership = Og::createMembership($group, $this->groupMember);
$membership->save();
// Group member cannot create content in their groups when they don't have
// access to.
$groups = $this->selectionHandler->getReferenceableEntities();
$this->assertArrayNotHasKey($this->groupBundle, $groups);
// Grant OG permission.
$og_role = OgRole::getRole('node', $this->groupBundle, OgRoleInterface::AUTHENTICATED);
$og_role
->grantPermission("create {$this->groupContentBundle} content")
->save();
$groups = $this->selectionHandler->getReferenceableEntities();
$this->assertEquals([$group_id], array_keys($groups[$this->groupBundle]));
}
/**
* Tests saving multi-group content with limited visibility.
*/
public function testMultiGroupContentSaveWithHiddenGroup(): void {
// Create 3 groups owned by the OG group admin.
$groups = [];
for ($i = 0; $i < 3; $i++) {
$group = Node::create([
'title' => $this->randomString(),
'uid' => $this->groupAdmin->id(),
'type' => $this->groupBundle,
]);
$group->save();
$groups[$group->id()] = $group;
}
$expected_group_ids = array_keys($groups);
$limited_group_ids = array_slice($expected_group_ids, 0, 2);
// Create authors: one limited user and reuse the OG group admin.
$limited_user = User::create(['name' => $this->randomString()]);
$limited_user->save();
$permissions = [
"create {$this->groupContentBundle} content",
"edit own {$this->groupContentBundle} content",
];
// Grant memberships so the limited user can post in only two groups.
$limited_role = OgRole::create()
->setName($this->randomMachineName())
->setLabel($this->randomString())
->setGroupType('node')
->setGroupBundle($this->groupBundle);
foreach ($permissions as $permission) {
$limited_role->grantPermission($permission);
}
$limited_role->save();
// Only grant membership to 2 of the 3 groups.
$limited_membership_one = Og::createMembership($groups[$limited_group_ids[0]], $limited_user);
$limited_membership_one->addRole($limited_role)->save();
$limited_membership_two = Og::createMembership($groups[$limited_group_ids[1]], $limited_user);
$limited_membership_two->addRole($limited_role)->save();
// Seed group content node that references all three groups.
$audience_values = [];
foreach ($groups as $group) {
$audience_values[] = ['target_id' => $group->id()];
}
// Create and save the group content an OG group admin user.
$this->setCurrentAccount($this->groupAdmin);
$group_content = Node::create([
'type' => $this->groupContentBundle,
'title' => $this->randomString(),
OgGroupAudienceHelperInterface::DEFAULT_FIELD => $audience_values,
'uid' => $this->groupAdmin->id(),
]);
$group_content->save();
$group_content_id = (int) $group_content->id();
$group_content = Node::load($group_content_id);
// With the limited visibility user, we should only see permitted groups
// yet still be able to save the content as members of the other groups.
$this->setCurrentAccount($limited_user);
$initial_save_status = $group_content->save();
$this->assertSame(SAVED_UPDATED, $initial_save_status);
$post_save_groups = $group_content->get(OgGroupAudienceHelperInterface::DEFAULT_FIELD)->referencedEntities();
$post_save_group_ids = [];
foreach ($post_save_groups as $group_entity) {
$post_save_group_ids[] = (int) $group_entity->id();
}
$this->assertSame($limited_group_ids, $post_save_group_ids, 'Limited editor only sees groups they can post in.');
// Users bypassing node access should always see every referenced group even
// if they are not OG administrators.
$this->setCurrentAccount($this->nodeBypassUser);
$node_bypass_groups = $group_content->get(OgGroupAudienceHelperInterface::DEFAULT_FIELD)->referencedEntities();
$node_bypass_group_ids = [];
foreach ($node_bypass_groups as $group_entity) {
$node_bypass_group_ids[] = (int) $group_entity->id();
}
$this->assertSame($expected_group_ids, $node_bypass_group_ids);
// Switch back to the limited account for the remaining assertions.
$this->setCurrentAccount($limited_user);
// Administrators still see every referenced group, confirming hidden
// memberships remain attached to the entity.
$this->setCurrentAccount($this->groupAdmin);
$admin_visible_groups = $group_content->get(OgGroupAudienceHelperInterface::DEFAULT_FIELD)->referencedEntities();
$admin_group_ids = [];
foreach ($admin_visible_groups as $group_entity) {
$admin_group_ids[] = (int) $group_entity->id();
}
$this->assertSame($expected_group_ids, $admin_group_ids);
// Switch back to the limited account for the remaining assertions.
$this->setCurrentAccount($limited_user);
// Check the selection handler only shows 2.
$limited_selection_handler = $this->selectionPluginManager->getSelectionHandler($this->fieldDefinition);
$limited_configuration = $limited_selection_handler->getConfiguration();
$limited_configuration['entity'] = $group_content;
$limited_selection_handler->setConfiguration($limited_configuration);
$limited_referenceable = $limited_selection_handler->getReferenceableEntities();
$this->assertArrayHasKey($this->groupBundle, $limited_referenceable);
$visible_group_ids = [];
foreach ($limited_referenceable[$this->groupBundle] as $group_id => $label) {
$visible_group_ids[] = (int) $group_id;
}
$this->assertSame($limited_group_ids, $visible_group_ids);
$group_content->setTitle($this->randomString());
$this->assertSame(SAVED_UPDATED, $group_content->save());
// Reload via entity storage to ensure all group references persisted. Use
// the administrator account so hidden groups remain visible.
$this->setCurrentAccount($this->groupAdmin);
/** @var \Drupal\node\NodeInterface $reloaded */
$reloaded = Node::load($group_content_id);
$this->assertNotNull($reloaded);
$actual_group_ids = [];
foreach ($reloaded->get(OgGroupAudienceHelperInterface::DEFAULT_FIELD)->referencedEntities() as $group_entity) {
$actual_group_ids[] = (int) $group_entity->id();
}
$this->assertSame($expected_group_ids, $actual_group_ids);
// Full-access administrator should see every group when editing.
$full_selection_handler = $this->selectionPluginManager->getSelectionHandler($this->fieldDefinition);
$full_configuration = $full_selection_handler->getConfiguration();
$full_configuration['entity'] = $reloaded;
$full_selection_handler->setConfiguration($full_configuration);
$full_referenceable = $full_selection_handler->getReferenceableEntities();
$this->assertArrayHasKey($this->groupBundle, $full_referenceable);
$full_visible_group_ids = [];
foreach ($full_referenceable[$this->groupBundle] as $group_id => $label) {
$full_visible_group_ids[] = (int) $group_id;
}
$this->assertSame($expected_group_ids, $full_visible_group_ids);
$reloaded->setTitle($this->randomString());
$this->assertSame(SAVED_UPDATED, $reloaded->save());
// Final save should still reference each original group.
/** @var \Drupal\node\NodeInterface $final */
$final = Node::load($group_content_id);
$this->assertNotNull($final);
$final_group_ids = [];
foreach ($final->get(OgGroupAudienceHelperInterface::DEFAULT_FIELD)->referencedEntities() as $group_entity) {
$final_group_ids[] = (int) $group_entity->id();
}
$this->assertSame($expected_group_ids, $final_group_ids);
}
/**
* Creating groups for a given user.
*
* @param int $amount
* The number of groups to create.
* @param \Drupal\user\Entity\User $user
* The user object which owns the groups.
*
* @return \Drupal\Core\Entity\ContentEntityBase[]
* An array of group entities.
*/
protected function createGroups(int $amount, User $user): array {
$groups = [];
for ($i = 0; $i <= $amount; $i++) {
$group = Node::create([
'title' => $this->randomString(),
'uid' => $user->id(),
'type' => $this->groupBundle,
]);
$group->save();
$groups[] = $group->id();
}
return $groups;
}
/**
* Sets the current account.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account to switch to.
*/
protected function setCurrentAccount(AccountInterface $account): void {
$this->container->get('account_switcher')->switchTo($account);
}
}
