Commit b571dd05 authored by Kristiaan Van den Eynde's avatar Kristiaan Van den Eynde
Browse files

Issue #3204083 by kristiaanvandeneynde: Rework group roles into a scoped...

Issue #3204083 by kristiaanvandeneynde: Rework group roles into a scoped system and adjust permission calculation
parent 6775dcc9
Loading
Loading
Loading
Loading
+5 −8
Changes for config/schema/group.schema.yml: 5 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -66,18 +66,15 @@ group.role.*:
    admin:
      type: 'boolean'
      label: 'Group role is an admin role'
    internal:
      type: 'boolean'
      label: 'Group role is used internally'
    audience:
    scope:
      type: 'string'
      label: 'The scope of the group role'
    global_role:
      type: 'string'
      label: 'Audience'
      label: 'The global role the group role synchronizes with. Only applies if scope is outsider or insider'
    group_type:
      type: 'string'
      label: 'Group type ID'
    permissions_ui:
      type: 'boolean'
      label: 'Show this role in the default permissions UI'
    permissions:
      type: 'sequence'
      label: 'Permissions'
+122 −0
Changes for group.install: 122 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -5,7 +5,9 @@
 * Install, update and uninstall functions for the group module.
 */

use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\group\Entity\GroupTypeInterface;

/**
 * Implements hook_update_last_removed().
@@ -102,3 +104,123 @@ function group_update_9203() {
    $role->save(TRUE);
  }
}

/**
 * Convert synchronized group roles to new scope and target_role structure.
 */
function group_update_9204() {
  $config_factory = \Drupal::configFactory();

  $synchronized_role_ids = [];
  foreach ($config_factory->listAll('user.role.') as $role_config_name) {
    $role_id = $config_factory->get($role_config_name)->get('id');

    // We never synced any roles to anonymous or authenticated.
    if ($role_id === 'anonymous' || $role_id === 'authenticated') {
      continue;
    }

    $synchronized_role_ids[] = $role_id;
  }

  // Nothing to update if we had no user defined global roles.
  if (empty($synchronized_role_ids)) {
    return;
  }

  foreach ($config_factory->listAll('group.type.') as $group_type_config_name) {
    $group_type_id = $config_factory->get($group_type_config_name)->get('id');

    foreach ($synchronized_role_ids as $role_id) {
      // Copied the group role ID logic directly from the GroupRoleSynchronizer
      // class because that class has been removed in favor of the new roles.
      $machine_name_max_length = EntityTypeInterface::ID_MAX_LENGTH - GroupTypeInterface::ID_MAX_LENGTH - 1;
      $machine_name = substr(md5('group_role_sync.' . $role_id), 0, $machine_name_max_length);
      $group_role_id = "$group_type_id-$machine_name";

      // Delete the synchronized role if they had no rights.
      $group_role = $config_factory->getEditable('group.role.' . $group_role_id);
      if (!$group_role->get('admin') && empty($group_role->get('permissions'))) {
        $group_role->delete();
      }
      // Otherwise update the group role to use the new keys.
      else {
        $group_role->set('scope', 'outsider');
        $group_role->set('global_role', $role_id);

        // Clear the old keys and save.
        $group_role->clear('permissions_ui');
        $group_role->clear('internal');
        $group_role->clear('audience');
        $group_role->save(TRUE);
      }
    }
  }
}

/**
 * Convert default group roles to new scope and target_role structure.
 */
function group_update_9205() {
  $config_factory = \Drupal::configFactory();

  $group_role_info = [
    'anonymous' => [
      'scope' => 'outsider',
      'global_role' => 'anonymous',
    ],
    'outsider' => [
      'scope' => 'outsider',
      'global_role' => 'authenticated',
    ],
    'member' => [
      'scope' => 'insider',
      'global_role' => 'authenticated',
    ],
  ];

  // Update anonymous, outsider and member.
  foreach ($config_factory->listAll('group.type.') as $group_type_config_name) {
    $group_type_id = $config_factory->get($group_type_config_name)->get('id');

    foreach ($group_role_info as $group_role_suffix => $info) {
      $group_role_id = "$group_type_id-$group_role_suffix";

      // We do not delete the default roles, even if they have no rights.
      $group_role = $config_factory->getEditable('group.role.' . $group_role_id);
      $group_role->set('scope', $info['scope']);
      $group_role->set('global_role', $info['global_role']);

      // Clear the old keys and save.
      $group_role->clear('permissions_ui');
      $group_role->clear('internal');
      $group_role->clear('audience');
      $group_role->save(TRUE);
    }
  }
}

/**
 * Convert user created group roles to new scope and target_role structure.
 */
function group_update_9206() {
  $config_factory = \Drupal::configFactory();

  foreach ($config_factory->listAll('group.role.') as $config_name) {
    // Now that we've updated synchronized and default roles, any role that
    // still has the old properties is a user generated one. So skip the rest.
    if (empty($config_factory->get($config_name)->get('audience'))) {
      continue;
    }

    // We do not delete the user created roles, even if they have no rights.
    $group_role = $config_factory->getEditable($config_name);
    $group_role->set('scope', 'individual');

    // Clear the old keys and save.
    $group_role->clear('permissions_ui');
    $group_role->clear('internal');
    $group_role->clear('audience');
    $group_role->save(TRUE);
  }
}
+0 −14
Changes for group.links.task.yml: 0 added lines, 14 removed lines.
Original line number Diff line number Diff line
@@ -59,20 +59,6 @@ group_type.permissions_form:
  base_route: 'entity.group_type.edit_form'
  weight: 15

group_type.permissions_form.basic:
  title: 'Basic permissions'
  route_name: 'entity.group_type.permissions_form'
  base_route: 'entity.group_type.edit_form'
  parent_id: 'group_type.permissions_form'
  weight: 0

group_type.permissions_form.outsider:
  title: 'Advanced outsider permissions'
  route_name: 'entity.group_type.outsider_permissions_form'
  base_route: 'entity.group_type.edit_form'
  parent_id: 'group_type.permissions_form'
  weight: 5

group_type.roles_list:
  title: 'Roles'
  route_name: 'entity.group_role.collection'
+28 −51
Changes for group.module: 28 added lines, 51 removed lines.
Original line number Diff line number Diff line
@@ -19,7 +19,8 @@ use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\group\Entity\GroupContent;
use Drupal\group\QueryAccess\EntityQueryAlter;
use Drupal\user\RoleInterface;
use Drupal\group\QueryAccess\GroupContentQueryAlter;
use Drupal\group\QueryAccess\GroupQueryAlter;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\Plugin\views\query\Sql;
use Drupal\views\ViewExecutable;
@@ -184,11 +185,6 @@ function template_preprocess_group_content(&$variables) {
 */
function group_rebuild() {
  _group_relation_type_manager()->installEnforced();

  /** @var \Drupal\group\Entity\Storage\GroupRoleStorageInterface $storage */
  $storage = \Drupal::entityTypeManager()->getStorage('group_role');
  $storage->createInternal();
  $storage->createSynchronized();
}

/**
@@ -198,11 +194,6 @@ function group_modules_installed($modules) {
  // Only create config objects while config import is not in progress.
  if (!\Drupal::isConfigSyncing()) {
    _group_relation_type_manager()->installEnforced();

    /** @var \Drupal\group\Entity\Storage\GroupRoleStorageInterface $storage */
    $storage = \Drupal::entityTypeManager()->getStorage('group_role');
    $storage->createInternal();
    $storage->createSynchronized();
  }
}

@@ -245,8 +236,33 @@ function group_query_entity_query_alter(SelectInterface $query) {
    $entity_type_manager = \Drupal::entityTypeManager();
    $entity_type = $entity_type_manager->getDefinition($entity_type_id);

    // Add specific access checks based on the entity type. Please note that we
    // first check for the group and group content entity types because we have
    // full control over those and can therefore optimize the query more. If the
    // query is for a different entity type, we fall back to the default query
    // alter.
    //
    // PLEASE KEEP IN MIND that this means that a plugin that allows you to nest
    // groups will have no query access added for nested groups whatsoever. This
    // is by design as those plugins should opt to use the permission calculator
    // system, as it's way faster and automatically included in GroupQueryAlter.
    //
    // @todo Enforce that plugins may not define entity_access for "group".
    switch ($entity_type_id) {
      case 'group':
        $class_name = GroupQueryAlter::class;
        break;

      case 'group_content':
        $class_name = GroupContentQueryAlter::class;
        break;

      default:
        $class_name = EntityQueryAlter::class;
    }

    \Drupal::service('class_resolver')
      ->getInstanceFromDefinition(EntityQueryAlter::class)
      ->getInstanceFromDefinition($class_name)
      ->alter($query, $entity_type);
  }
}
@@ -501,33 +517,6 @@ function group_user_delete(EntityInterface $account) {
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function group_user_role_insert(RoleInterface $role) {
  // Only create config objects while config import is not in progress.
  if (!\Drupal::isConfigSyncing()) {
    /** @var \Drupal\group\Entity\Storage\GroupRoleStorageInterface $storage */
    $storage = \Drupal::entityTypeManager()->getStorage('group_role');
    $storage->createSynchronized(NULL, [$role->id()]);
  }
}

/**
 * Implements hook_ENTITY_TYPE_update().
 */
function group_user_role_update(RoleInterface $role) {
  /** @var \Drupal\User\RoleInterface $original */
  $original = $role->original;

  // Update the group roles if the user role label changed.
  if ($role->label() != $original->label()) {
    /** @var \Drupal\group\Entity\Storage\GroupRoleStorageInterface $storage */
    $storage = \Drupal::entityTypeManager()->getStorage('group_role');
    $storage->updateSynchronizedLabels($role);
  }
}

/**
 * Implements hook_form_alter().
 */
@@ -706,18 +695,6 @@ function _group_relation_type_manager() {
  return \Drupal::service('group_relation_type.manager');
}

/**
 * Gets the group role synchronizer service.
 *
 * @return \Drupal\group\GroupRoleSynchronizerInterface
 *   The group role synchronizer service.
 *
 * @internal Try to properly inject the service when possible.
 */
function _group_role_synchronizer() {
  return \Drupal::service('group_role.synchronizer');
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 *
+0 −8
Changes for group.routing.yml: 0 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -54,14 +54,6 @@ entity.group_type.permissions_form:
  requirements:
    _permission: 'administer group'

entity.group_type.outsider_permissions_form:
  path: '/admin/group/types/manage/{group_type}/permissions/outsider'
  defaults:
    _form: '\Drupal\group\Form\GroupPermissionsOutsiderForm'
    _title: 'Edit advanced outsider permissions'
  requirements:
    _permission: 'administer group'

entity.group_type.content_plugins:
  path: '/admin/group/types/manage/{group_type}/content'
  defaults:
Loading