Skip to content

Commit

Permalink
PE-672 Bulk group membership management (#636)
Browse files Browse the repository at this point in the history
* PE-672 Bulk group membership management

* PE-672 Modify items selected list for group_content-based actions to provide better context.

* PE-672 Give error if multiple group types are selected
  • Loading branch information
RichardDavies authored Jan 9, 2025
1 parent 6d0b955 commit 2b0bdbc
Show file tree
Hide file tree
Showing 16 changed files with 1,369 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace Drupal\employees\Plugin\Action;

use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Action\Attribute\Action;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\user\UserInterface;
use Drupal\group\Entity\Group;


/**
* Views bulk operations action that allows adding mulitple users to a group in bulk.
*
* @Action(
* id = "employees_add_users_to_group",
* label = @Translation("Add users to a group (custom action)"),
* type = "user",
* confirm = FALSE,
* )
*/
final class AddGroupMembershipAction extends ViewsBulkOperationsActionBase implements PluginFormInterface {

use StringTranslationTrait;

/**
* {@inheritdoc}
*/
public function execute(UserInterface $user = NULL) {
if ($user === NULL || $this->configuration['group_id'] === 0) {
return $this->t('Invalid entity or configuration.');
}

$group_id = $this->configuration['group_id'];
$role_ids = $this->configuration['role_ids'];
$group = \Drupal::entityTypeManager()->getStorage('group')->load($group_id);
$membership = $group->getMember($user);
if ($membership === FALSE) {
// Add user to the group with the assigned role(s)
$role_ids = array_diff($role_ids, [0]); // Remove array elements with the value of "0"
$group->addMember($user, ['group_roles' => array_keys($role_ids)]);
} else {
// User is already a member of the group so just update the role(s)
foreach ($role_ids as $role_id => $assign_role) {
if ($assign_role) {
$membership->addRole($role_id);
} else {
$membership->removeRole($role_id);
}
}
}

return $this->t('User has been added to the group with the assigned roles.');
}


/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
// Step tracking
$current_step = $form_state->get('current_step') ?? 1;

// Build the form based on the current step
switch ($current_step) {
case 1:
$form['step_desc'] = [
'#type' => 'markup',
'#markup' => '<h4>' . t('Step 1 of 2: Select the group') . '</h4>',
];
$form['group_id'] = [
'#type' => 'entity_autocomplete',
'#title' => $this->t('Group'),
'#placeholder' => $this->t('Enter group name'),
'#target_type' => 'group',
'#selection_settings' => ['filter' => ['access' => 'administer members']],
'#required' => TRUE,
'#tags' => FALSE,
];

$form['actions']['submit']['#value'] = t('Next');
break;

case 2:
$form['step_desc'] = [
'#type' => 'markup',
'#markup' => '<h4>' . t('Step 2 of 2: Assign group role(s)') . '</h4>',
];

$form['group'] = [
'#type' => 'markup',
'#markup' => '<p><strong>Group:</strong> ' . $form_state->getUserInput()['group_id'] . '</p>',
];

$group_id = $form_state->getValue('group_id');
$form['group_id'] = [
'#type' => 'hidden',
'#value' => $group_id,
];

// Get the group roles associated with the selected group
$roles = \Drupal::entityTypeManager()->getStorage('group')->load($group_id)->getGroupType()->getRoles(FALSE);
$role_options = array();
foreach ($roles as $role) {
$role_options[$role->id()] = $role->label();
}
$form['role_ids'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Roles'),
'#options' => $role_options,
'#required' => TRUE,
'#description' => $this->t('IMPORTANT: If a user already belongs to the selected group, this will remove their current roles and assign the roles selected here.'),
];
break;
}

// Store the current step
$form_state->set('current_step', $current_step);

return $form;
}


/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$button = $form_state->getTriggeringElement()['#value']->getUntranslatedString();

if ($button === 'Next') {
$current_step = $form_state->get('current_step');
$form_state->set('current_step', ++$current_step);
$form_state->setRebuild();
}
}


/**
* {@inheritdoc}
*/
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
// This checks for edit access on the selected users' accounts. We can't really check if the operator
// has the necessary group permissions because the group hasn't been chosen yet, but since this action is
// only available on the /admin/people view which requires sitewide admin permissions, we can assume the
// operator has the necessary permissions.
return $object->access('edit', $account, $return_as_object);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

namespace Drupal\employees\Plugin\Action;

use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Action\Attribute\Action;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\group\Entity\Group;
use Drupal\Core\Access\AccessResult;

/**
* Views bulk operations action that allows adding users to multiple groups in bulk.
*
* @Action(
* id = "employees_add_user_to_groups",
* label = @Translation("Add or update members (custom action)"),
* type = "group",
* confirm = FALSE,
* )
*/
final class AddGroupMembershipsAction extends ViewsBulkOperationsActionBase implements PluginFormInterface {

use StringTranslationTrait;

/**
* {@inheritdoc}
*/
public function execute(Group $group = NULL) {
if ($group === NULL || \count($this->configuration['user_id']) === 0) {
return $this->t('Invalid entity or configuration.');
}

$user_ids = [];
foreach ($this->configuration['user_id'] as $item) {
$user_ids[$item['target_id']] = $item['target_id'];
}

$role_ids = $this->configuration['role_ids'];
foreach (\Drupal::entityTypeManager()->getStorage('user')->loadMultiple($user_ids) as $user) {
$membership = $group->getMember($user);
if ($membership === FALSE) {
// Add user to the group with the assigned role(s)
$role_ids = array_diff($role_ids, [0]); // Remove array elements with the value of "0"
$group->addMember($user, ['group_roles' => array_keys($role_ids)]);
} else {
// User is already a member of the group so just update the role(s)
foreach ($role_ids as $role_id => $assign_role) {
if ($assign_role) {
$membership->addRole($role_id);
} else {
$membership->removeRole($role_id);
}
}
}
}

return $this->t('User has been added to the group with the assigned roles.');
}


/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
// Make sure all selected groups are of the same group type
$groups = $form_state->getStorage()['views_bulk_operations']['list'];
$group_types = array();
foreach ($groups as $group) {
$group_id = $group[0];
$group_type = \Drupal::entityTypeManager()->getStorage('group')->load($group_id)->getGroupType();
$group_types[$group_type->id()] = $group_type->label();

if (count($group_types) > 1) {
$group_type_labels = implode(', ', $group_types);
$form['error'] = [
'#type' => 'markup',
'#markup' => '<p>' . t("Error: All selected groups must be of the same group type. Current selection includes groups of type $group_type_labels.") . '</p>',
];
$form['actions']['submit']['#disabled'] = TRUE;
return $form;
}
}

$form['user_id'] = [
'#type' => 'entity_autocomplete',
'#title' => $this->t('User'),
'#placeholder' => $this->t('Enter user name'),
'#target_type' => 'user',
'#selection_settings' => ['filter' => ['access' => 'administer members']],
'#required' => TRUE,
'#tags' => TRUE,
'#description' => $this->t('Use a comma to select multiple users.'),
];

// Get the group roles associated with the first selected group
$groups = $form_state->getStorage()['views_bulk_operations']['list'];
$first_key = array_keys($groups)[0];
$group_id = $groups[$first_key][0];
$roles = \Drupal::entityTypeManager()->getStorage('group')->load($group_id)->getGroupType()->getRoles(FALSE);
$role_options = array();
foreach ($roles as $role) {
$role_options[$role->id()] = $role->label();
}
$form['role_ids'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Roles'),
'#options' => $role_options,
'#required' => TRUE,
'#description' => $this->t('IMPORTANT: If a user already belongs to a selected group, this will remove their current roles and assign the roles selected here.'),
];

return $form;
}


/**
* {@inheritdoc}
*/
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
$has_access = $object->hasPermission('administer members', $account, $return_as_object);
if ($return_as_object) {
return $has_access ? AccessResult::allowed() : AccessResult::forbidden();
} else {
return $has_access;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace Drupal\employees\Plugin\Action;

use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Action\Attribute\Action;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\group\Entity\GroupMembership;
use Drupal\Core\Access\AccessResult;

/**
* Views bulk operations action that allows editing multiple users group memberships in bulk.
*
* @Action(
* id = "employees_edit_users_membership",
* label = @Translation("Edit group roles (custom action)"),
* type = "group_content",
* confirm = FALSE,
* )
*/
final class EditGroupMembershipAction extends ViewsBulkOperationsActionBase implements PluginFormInterface {

use StringTranslationTrait;

/**
* {@inheritdoc}
*/
public function execute(GroupMembership $membership = NULL) {
if ($membership === NULL || \count($this->configuration['role_ids']) === 0) {
return $this->t('Invalid entity or configuration.');
}

$role_ids = $this->configuration['role_ids'];
foreach ($role_ids as $role_id => $assign_role) {
if ($assign_role) {
$membership->addRole($role_id);
} else {
$membership->removeRole($role_id);
}
}

return $this->t('The user\'s group roles have been updated.');
}


/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
// Get the group roles associated with the group
$group_contents = $form_state->getStorage()['views_bulk_operations']['list'];
$first_key = array_keys($group_contents)[0];
$group_content_id = $group_contents[$first_key][0];
$roles = \Drupal::entityTypeManager()->getStorage('group_content')->load($group_content_id)->getGroup()->getGroupType()->getRoles(FALSE);
$role_options = array();
foreach ($roles as $role) {
$role_options[$role->id()] = $role->label();
}
$form['role_ids'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Roles'),
'#options' => $role_options,
'#required' => TRUE,
'#description' => $this->t('IMPORTANT: This will remove the users\' current roles and assign the roles selected here.'),
];

// The "Items selected" list on group_content-based views has the user’s name. Include the
// selected groups' name too.
$list = $form_state->getStorage()['views_bulk_operations']['list'];
$count = 0;
foreach ($list as $item) {
$entity_id = $item[0];
$username = $form['list']['#items'][$count];
$group = \Drupal::entityTypeManager()->getStorage('group_content')->load($entity_id)->getGroup();
$form['list']['#items'][$count++] = "$username in " . $group->label();
}

return $form;
}


/**
* {@inheritdoc}
*/
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
$has_access = $object->getGroup()->hasPermission('administer members', $account, $return_as_object);
if ($return_as_object) {
return $has_access ? AccessResult::allowed() : AccessResult::forbidden();
} else {
return $has_access;
}
}
}
Loading

0 comments on commit 2b0bdbc

Please sign in to comment.