diff --git a/.travis.yml b/.travis.yml index f121c28ab..a54414a7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: php sudo: true php: - - 5.6 - 7.0 - 7.1 - 7.2 @@ -13,11 +12,18 @@ env: matrix: - TEST_SUITE=8.5.x - TEST_SUITE=8.6.x + - TEST_SUITE=8.7.x - TEST_SUITE=PHP_CodeSniffer # Only run the coding standards check once. matrix: exclude: + - php: 5.6 + env: TEST_SUITE=8.7.x + - php: 7.0 + env: TEST_SUITE=8.7.x + - php: 7.1 + env: TEST_SUITE=8.7.x - php: 5.6 env: TEST_SUITE=PHP_CodeSniffer - php: 7.0 @@ -25,7 +31,7 @@ matrix: - php: 7.1 env: TEST_SUITE=PHP_CodeSniffer allow_failures: - - env: TEST_SUITE=8.6.x + - env: TEST_SUITE=8.7.x mysql: database: og @@ -44,9 +50,6 @@ before_script: # Remember the current directory for later use in the Drupal installation. - MODULE_DIR=$(pwd) - # Install Composer dependencies for OG. - - composer install - # Navigate out of module directory to prevent blown stack by recursive module # lookup. - cd .. diff --git a/config/install/core.entity_form_display.og_membership.default.default.yml b/config/install/core.entity_form_display.og_membership.default.default.yml index 08b77f3bf..0332b58f8 100644 --- a/config/install/core.entity_form_display.og_membership.default.default.yml +++ b/config/install/core.entity_form_display.og_membership.default.default.yml @@ -18,4 +18,17 @@ content: rows: 2 placeholder: '' third_party_settings: { } + roles: + type: options_buttons + weight: 0 + settings: { } + third_party_settings: { } + uid: + type: og_autocomplete + weight: -1 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + third_party_settings: { } hidden: { } diff --git a/config/install/core.entity_view_display.og_membership.default.default.yml b/config/install/core.entity_view_display.og_membership.default.default.yml index 4d19f8b4e..468f4dd18 100644 --- a/config/install/core.entity_view_display.og_membership.default.default.yml +++ b/config/install/core.entity_view_display.og_membership.default.default.yml @@ -14,7 +14,21 @@ content: og_membership_request: label: above type: basic_string - weight: 0 + weight: 2 settings: { } third_party_settings: { } + roles: + type: entity_reference_label + weight: 1 + label: above + settings: + link: false + third_party_settings: { } + uid: + type: entity_reference_label + weight: 0 + label: above + settings: + link: true + third_party_settings: { } hidden: { } diff --git a/config/optional/views.view.og_members_overview.yml b/config/optional/views.view.og_members_overview.yml index ad531e3ab..93c434969 100644 --- a/config/optional/views.view.og_members_overview.yml +++ b/config/optional/views.view.og_members_overview.yml @@ -421,6 +421,57 @@ display: entity_type: og_membership entity_field: roles plugin_id: field + operations: + id: operations + table: og_membership + field: operations + relationship: none + group_type: group + admin_label: '' + label: 'Operations links' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + destination: true + entity_type: og_membership + plugin_id: entity_operations filters: { } sorts: { } header: { } diff --git a/config/schema/og.schema.yml b/config/schema/og.schema.yml index 4f08e369b..aa919cd2b 100644 --- a/config/schema/og.schema.yml +++ b/config/schema/og.schema.yml @@ -193,3 +193,18 @@ condition.plugin.og_group_type: type: sequence sequence: type: string + +# Copied and adapted from core.entity.schema.yml +field.widget.settings.og_autocomplete: + type: mapping + label: 'OG context based entity reference autocomplete with display format settings' + mapping: + match_operator: + type: string + label: 'Autocomplete matching' + size: + type: integer + label: 'Size of textfield' + placeholder: + type: label + label: 'Placeholder' diff --git a/og.links.action.yml b/og.links.action.yml new file mode 100644 index 000000000..276999d49 --- /dev/null +++ b/og.links.action.yml @@ -0,0 +1,11 @@ +og_membership.type_add: + route_name: og_membership.type_add + title: 'Add membership type' + appears_on: + - entity.og_membership_type.collection + +og_admin.members.add: + route_name: og_admin.members.add + title: 'Add member' + appears_on: + - og_admin.members diff --git a/og.links.menu.yml b/og.links.menu.yml new file mode 100644 index 000000000..b91d6607f --- /dev/null +++ b/og.links.menu.yml @@ -0,0 +1,5 @@ +entity.og_membership_type.collection: + title: 'Membership types' + parent: system.admin_structure + description: 'Create and manage fields, forms, and display settings for OG memberships.' + route_name: entity.og_membership_type.collection diff --git a/og.links.task.yml b/og.links.task.yml index 854c7df83..bedb52d58 100644 --- a/og.links.task.yml +++ b/og.links.task.yml @@ -1,2 +1,15 @@ -og.og_admin_routes: +entity.og_membership.canonical: + route_name: entity.og_membership.canonical + base_route: entity.og_membership.canonical + title: View +entity.og_membership.edit_form: + route_name: entity.og_membership.edit_form + base_route: entity.og_membership.canonical + title: Edit +entity.og_membership.delete_form: + route_name: entity.og_membership.delete_form + base_route: entity.og_membership.canonical + title: Delete + weight: 10 +og_admin.local_tasks: deriver: \Drupal\og\Plugin\Derivative\OgLocalTask diff --git a/og.module b/og.module index d4bf89737..61e8f2ab8 100755 --- a/og.module +++ b/og.module @@ -17,6 +17,7 @@ use Drupal\og\Entity\OgRole; use Drupal\og\Og; use Drupal\og\OgGroupAudienceHelperInterface; use Drupal\og\OgMembershipInterface; +use Drupal\og\OgMembershipTypeInterface; use Drupal\og\OgRoleInterface; use Drupal\system\Entity\Action; use Drupal\user\EntityOwnerInterface; @@ -177,6 +178,47 @@ function og_entity_access(EntityInterface $entity, $operation, AccountInterface return AccessResult::forbidden(); } +/** + * Implements hook_ENTITY_TYPE_access(). + */ +function og_og_membership_type_access(OgMembershipTypeInterface $entity, $operation, AccountInterface $account) { + // Do not allow deleting the default membership type. + if ($operation === 'delete' && $entity->id() === OgMembershipInterface::TYPE_DEFAULT) { + return AccessResult::forbidden(); + } + + // If the user has permission to administer all groups, allow access. + if ($account->hasPermission('administer group')) { + return AccessResult::allowed(); + } + + return AccessResult::forbidden(); +} + +/** + * Implements hook_ENTITY_TYPE_access(). + */ +function og_og_membership_access(OgMembershipInterface $entity, $operation, AccountInterface $account) { + $group = $entity->getGroup(); + + // If there's a group owner, don't let them leave. + if ( + isset($group_fields['uid']) + && $operation === 'delete' + && $group_fields['uid']->entity->id() === $entity->getOwner()->id() + ) { + return AccessResult::forbidden(); + } + + // Ensure that there's at least one member in the group. + if ($operation === 'delete' && \Drupal::service('og.membership_manager')->getGroupMembershipCount($group) === 1) { + return AccessResult::forbidden(); + } + + return \Drupal::service('og.access') + ->userAccess($entity->getGroup(), 'manage members'); +} + /** * Implements hook_entity_create_access(). */ @@ -242,7 +284,7 @@ function og_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, $fields = []; $fields['og_group'] = BaseFieldDefinition::create('og_group') - ->setLabel(t('OG Group')) + ->setLabel('OG Group') ->setComputed(TRUE) ->setTranslatable(FALSE) ->setDefaultValue(TRUE) diff --git a/og.routing.yml b/og.routing.yml index 06a78558e..4fdeb8a54 100644 --- a/og.routing.yml +++ b/og.routing.yml @@ -1,11 +1,11 @@ # Routes for Organic groups. og.subscribe: - path: 'group/{entity_type_id}/{group}/subscribe/{membership_type}' + path: 'group/{entity_type_id}/{group}/subscribe/{og_membership_type}' defaults: _controller: '\Drupal\og\Controller\SubscriptionController::subscribe' _title: 'Join Group' - membership_type: default + og_membership_type: default requirements: # Only authenticated users can subscribe to group, but we do allow anonymous # users to reach this route. They will be redirect to login page or be given @@ -15,8 +15,6 @@ og.subscribe: parameters: group: type: entity:{entity_type_id} - membership_type: - type: entity:og_membership_type og.unsubscribe: path: 'group/{entity_type_id}/{group}/unsubscribe' @@ -43,3 +41,125 @@ og.remove_multiple_roles_confirm: _form: '\Drupal\og\Form\OgRemoveMultipleRolesForm' requirements: _custom_access: '\Drupal\og\Form\OgRemoveMultipleRolesForm::access' + +og.entity_autocomplete: + path: '/group/{entity_type_id}/{group}/autocomplete/{target_type}/{selection_handler}/{selection_settings_key}' + defaults: + _controller: '\Drupal\og\Controller\OgAutocompleteController:handleAutocomplete' + requirements: + _access: 'TRUE' + options: + parameters: + group: + type: entity:{entity_type_id} + +# OG Membership entity routes +entity.og_membership.add_form: + path: 'group/{entity_type_id}/{group}/admin/members/add/{og_membership_type}' + defaults: + _controller: '\Drupal\og\Controller\OgAdminMembersController::addForm' + _title: 'Add member' + requirements: + _og_membership_add_access: 'TRUE' + options: + _admin_route: 'TRUE' + parameters: + group: + type: entity:{entity_type_id} + +# The canonical route is the same as the edit-form route because we need a +# canonical route for various functionality to work properly, but a standard +# entity view for OG memberships tends to feel quite stub-like. +entity.og_membership.canonical: + path: 'group/{entity_type_id}/{group}/admin/members/{og_membership}/edit' + defaults: + _entity_form: 'og_membership.edit' + options: + _admin_route: 'TRUE' + parameters: + group: + type: entity:{entity_type_id} + requirements: + _entity_access: 'og_membership.edit' + +entity.og_membership.edit_form: + path: 'group/{entity_type_id}/{group}/admin/members/{og_membership}/edit' + defaults: + _entity_form: 'og_membership.edit' + options: + _admin_route: 'TRUE' + parameters: + group: + type: entity:{entity_type_id} + requirements: + _entity_access: 'og_membership.edit' + +entity.og_membership.delete_form: + path: 'group/{entity_type_id}/{group}/admin/members/{og_membership}/delete' + defaults: + _entity_form: 'og_membership.delete' + options: + _admin_route: 'TRUE' + parameters: + group: + type: entity:{entity_type_id} + requirements: + _entity_access: 'og_membership.delete' + +# OG Membership type entity routes +entity.og_membership_type.collection: + path: '/admin/structure/membership-types' + defaults: + _entity_list: 'og_membership_type' + _title: 'Membership types' + requirements: + _permission: 'administer group' + +entity.og_membership_type.edit_form: + path: '/admin/structure/membership-types/manage/{og_membership_type}' + defaults: + _entity_form: 'og_membership_type.edit' + requirements: + _permission: 'administer group' + +entity.og_membership_type.delete_form: + path: '/admin/structure/membership-types/manage/{og_membership_type}/delete' + defaults: + _entity_form: 'og_membership_type.delete' + _title: 'Delete' + requirements: + _permission: 'og_membership_type.delete' + +og_membership.type_add: + path: '/admin/structure/membership-types/add' + defaults: + _entity_form: 'og_membership_type.add' + _title: 'Add membership type' + requirements: + _permission: 'administer group' + +og_admin.members: + path: '/group/{entity_type_id}/{group}/admin/members' + defaults: + _controller: '\Drupal\og\Controller\OgAdminMembersController::membersList' + _title: 'Members' + requirements: + _permission: 'administer group' + options: + _admin_route: 'TRUE' + parameters: + group: + type: entity:{entity_type_id} + +og_admin.members.add: + path: '/group/{entity_type_id}/{group}/admin/members/add' + defaults: + _controller: '\Drupal\og\Controller\OgAdminMembersController::addPage' + _title: 'Add member' + requirements: + _permission: 'administer group' + options: + _admin_route: 'TRUE' + parameters: + group: + type: entity:{entity_type_id} diff --git a/og.services.yml b/og.services.yml index 34a31b286..c7f5a1736 100644 --- a/og.services.yml +++ b/og.services.yml @@ -4,6 +4,11 @@ services: arguments: ['@entity_type.manager', '@og.access'] tags: - { name: access_check, applies_to: _og_user_access_group } + access_check.og.membership.add: + class: Drupal\og\Access\OgMembershipAddAccessCheck + arguments: ['@entity_type.manager', '@og.access'] + tags: + - { name: access_check, applies_to: _og_membership_add_access } cache_context.og_group_context: class: 'Drupal\og\Cache\Context\OgGroupContextCacheContext' arguments: ['@og.context'] diff --git a/og_ui/og_ui.module b/og_ui/og_ui.module index f319c4ed0..f09da9234 100644 --- a/og_ui/og_ui.module +++ b/og_ui/og_ui.module @@ -19,10 +19,16 @@ use Drupal\og_ui\BundleFormAlter; * Implements hook_form_alter(). */ function og_ui_form_alter(array &$form, FormStateInterface $form_state, $form_id) { - if ($form_state->getFormObject() instanceof BundleEntityFormBase) { - (new BundleFormAlter($form_state->getFormObject()->getEntity())) - ->formAlter($form, $form_state); + if (!$form_state->getFormObject() instanceof BundleEntityFormBase) { + return; } + + $entity_type = $form_state->getFormObject()->getEntity(); + if ($entity_type->getEntityTypeId() === 'og_membership_type') { + return; + } + + (new BundleFormAlter($entity_type))->formAlter($form, $form_state); } /** diff --git a/og_ui/tests/src/Functional/BundleFormAlterTest.php b/og_ui/tests/src/Functional/BundleFormAlterTest.php index 8f326fcdf..1427f0cbf 100644 --- a/og_ui/tests/src/Functional/BundleFormAlterTest.php +++ b/og_ui/tests/src/Functional/BundleFormAlterTest.php @@ -18,7 +18,16 @@ class BundleFormAlterTest extends BrowserTestBase { /** * {@inheritdoc} */ - public static $modules = ['block_content', 'entity_test', 'node', 'og_ui']; + public static $modules = [ + 'block_content', + 'entity_test', + 'node', + 'og_ui', + 'system', + 'og', + 'options', + 'field', + ]; /** * An administrator user. diff --git a/scripts/travis-ci/run-test.sh b/scripts/travis-ci/run-test.sh index aafaab4a7..07797a6a9 100755 --- a/scripts/travis-ci/run-test.sh +++ b/scripts/travis-ci/run-test.sh @@ -11,7 +11,7 @@ mysql_to_ramdisk() { sudo service mysql start } -TEST_DIRS=($MODULE_DIR/tests $MODULE_DIR/og_ui/tests) +TEST_DIRS=($DRUPAL_DIR/modules/og/tests $DRUPAL_DIR/modules/og/og_ui/tests) case "$1" in PHP_CodeSniffer) diff --git a/src/Access/GroupCheck.php b/src/Access/GroupCheck.php index 002797b84..5bb177bac 100644 --- a/src/Access/GroupCheck.php +++ b/src/Access/GroupCheck.php @@ -73,18 +73,18 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, OgA public function access(AccountInterface $user, Route $route, RouteMatchInterface $route_match, $entity_type_id = NULL, $entity_id = NULL) { $group = NULL; if (!$entity_type_id) { - $parameter_name = $route_match->getRouteObject()->getOption('_og_entity_type_id'); + $entity_type_id = $route_match->getRouteObject()->getOption('_og_entity_type_id'); - if (!$parameter_name) { + if (!$entity_type_id) { throw new \BadMethodCallException('Group definition is missing from the router. Did you define $route->setOption(\'_og_entity_type_id\', $entity_type_id) in your route declaration?'); } /** @var \Drupal\Core\Entity\EntityInterface $group */ - if (!$group = $route_match->getParameter($parameter_name)) { + $group = $route_match->getParameter($entity_type_id); + if (!$group) { return AccessResult::forbidden(); } - $entity_type_id = $group->getEntityTypeId(); } // No access if the entity type doesn't exist. @@ -93,7 +93,7 @@ public function access(AccountInterface $user, Route $route, RouteMatchInterface } $entity_storage = $this->entityTypeManager->getStorage($entity_type_id); - $group = $group ?: $entity_storage->load($entity_id); + $group = is_numeric($group) && !is_object($group) && is_null($entity_id) ? $entity_storage->load($group) : $entity_storage->load($entity_id); // No access if no entity was loaded or it's not a group. if (!$group || !Og::isGroup($entity_type_id, $group->bundle())) { diff --git a/src/Access/OgMembershipAddAccessCheck.php b/src/Access/OgMembershipAddAccessCheck.php new file mode 100644 index 000000000..922543e5b --- /dev/null +++ b/src/Access/OgMembershipAddAccessCheck.php @@ -0,0 +1,77 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * Checks access to create the entity type and bundle for the given route. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The parametrized route. + * @param \Drupal\Core\Session\AccountInterface $account + * The currently logged in account. + * @param \Drupal\Core\Entity\EntityInterface $group + * The group entity. + * @param \Drupal\og\OgMembershipTypeInterface $og_membership_type + * The membership type entity. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public function access(RouteMatchInterface $route_match, AccountInterface $account, EntityInterface $group = NULL, OgMembershipTypeInterface $og_membership_type = NULL) { + // The $group param will be null if it is from the + // Drupal\og\Event\OgAdminRoutesEvent rather than the routing.yml version. + if (is_null($group)) { + $entity_type_id = $route_match->getRouteObject() + ->getOption('_og_entity_type_id'); + $group = $route_match->getParameter($entity_type_id); + } + + if (!Og::isGroup($group->getEntityTypeId(), $group->bundle())) { + return AccessResult::forbidden(); + } + + $membership_type_id = OgMembershipInterface::TYPE_DEFAULT; + if (!is_null($og_membership_type)) { + $membership_type_id = $og_membership_type->id(); + } + + $context = ['group' => $group]; + + return $this->entityTypeManager + ->getAccessControlHandler('og_membership') + ->createAccess($membership_type_id, $account, $context, TRUE); + } + +} diff --git a/src/Controller/OgAdminMembersController.php b/src/Controller/OgAdminMembersController.php index 9eb723aea..6018f3834 100644 --- a/src/Controller/OgAdminMembersController.php +++ b/src/Controller/OgAdminMembersController.php @@ -3,14 +3,47 @@ namespace Drupal\og\Controller; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Link; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\og\Entity\OgMembership; +use Drupal\og\OgMembershipInterface; +use Drupal\og\OgMembershipTypeInterface; use Drupal\views\Views; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * OgAdminMembersController class. */ class OgAdminMembersController extends ControllerBase { + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Constructs a new EntityController. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager) { + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager') + ); + } + /** * Display list of members that belong to the group. * @@ -21,13 +54,78 @@ class OgAdminMembersController extends ControllerBase { * The members overview View. */ public function membersList(RouteMatchInterface $route_match) { - $parameter_name = $route_match->getRouteObject()->getOption('_og_entity_type_id'); - - /** @var \Drupal\Core\Entity\EntityInterface $group */ - $group = $route_match->getParameter($parameter_name); - + $group = $route_match->getParameter('group'); $arguments = [$group->getEntityTypeId(), $group->id()]; return Views::getView('og_members_overview')->executeDisplay('default', $arguments); } + /** + * Displays add membership links for available membership types. + * + * Returns default membership type if that's all that exists. + * + * @return array + * A render array for a list of the og membership types that can be added; + * however, if there is only one og membership type defined for the site, + * the function will return the default add member form. + */ + public function addPage(RouteMatchInterface $route_match) { + $group = $route_match->getParameter('group'); + + $membership_types = $this->entityTypeManager + ->getStorage('og_membership_type') + ->loadMultiple(); + + if ($membership_types && count($membership_types) == 1) { + return $this->addForm($group, $membership_types[OgMembershipInterface::TYPE_DEFAULT]); + } + + $build = [ + '#theme' => 'entity_add_list', + '#bundles' => [], + ]; + + $build['#cache']['tags'] = $this->entityTypeManager + ->getDefinition('og_membership_type') + ->getListCacheTags(); + + $add_link_params = [ + 'group' => $group->id(), + 'entity_type_id' => $group->getEntityType()->id(), + ]; + + foreach ($membership_types as $membership_type_id => $og_membership_type) { + $add_link_params['og_membership_type'] = $membership_type_id; + $build['#bundles'][$membership_type_id] = [ + 'label' => $og_membership_type->label(), + 'description' => NULL, + 'add_link' => Link::createFromRoute($og_membership_type->label(), 'entity.og_membership.add_form', $add_link_params), + ]; + } + + return $build; + } + + /** + * Provides the add member submission form. + * + * @param \Drupal\Core\Entity\EntityInterface $group + * The group entity. + * @param \Drupal\og\OgMembershipTypeInterface $og_membership_type + * The membership type entity. + * + * @return array + * The member add form. + */ + public function addForm(EntityInterface $group, OgMembershipTypeInterface $og_membership_type) { + /** @var \Drupal\og\Entity\OgMembership $og_membership */ + $og_membership = OgMembership::create([ + 'type' => $og_membership_type->id(), + 'entity_type' => $group->getEntityType()->id(), + 'entity_id' => $group->id(), + ]); + + return $this->entityFormBuilder()->getForm($og_membership, 'add'); + } + } diff --git a/src/Controller/OgAdminRoutesController.php b/src/Controller/OgAdminRoutesController.php index 071b53f35..4da0f73cf 100644 --- a/src/Controller/OgAdminRoutesController.php +++ b/src/Controller/OgAdminRoutesController.php @@ -2,13 +2,10 @@ namespace Drupal\og\Controller; -use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher; use Drupal\Core\Access\AccessManagerInterface; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; -use Drupal\og\Event\OgAdminRoutesEvent; -use Drupal\og\Event\OgAdminRoutesEventInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -16,13 +13,6 @@ */ class OgAdminRoutesController extends ControllerBase { - /** - * The event dispatcher service. - * - * @var \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher - */ - protected $eventDispatcher; - /** * The access manager service. * @@ -33,13 +23,10 @@ class OgAdminRoutesController extends ControllerBase { /** * Constructs an OgAdminController object. * - * @param \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher $event_dispatcher - * The event dispatcher service. * @param \Drupal\Core\Access\AccessManagerInterface $access_manager * The access manager service. */ - public function __construct(ContainerAwareEventDispatcher $event_dispatcher, AccessManagerInterface $access_manager) { - $this->eventDispatcher = $event_dispatcher; + public function __construct(AccessManagerInterface $access_manager) { $this->accessManager = $access_manager; } @@ -48,7 +35,6 @@ public function __construct(ContainerAwareEventDispatcher $event_dispatcher, Acc */ public static function create(ContainerInterface $container) { return new static( - $container->get('event_dispatcher'), $container->get('access_manager') ); } @@ -63,39 +49,32 @@ public static function create(ContainerInterface $container) { * List of available admin routes for the current group. */ public function overview(RouteMatchInterface $route_match) { - $parameter_name = $route_match->getRouteObject()->getOption('_og_entity_type_id'); - /** @var \Drupal\Core\Entity\EntityInterface $group */ - $group = $route_match->getParameter($parameter_name); - - $entity_type_id = $group->getEntityTypeId(); - + $group = $route_match->getParameter('group'); + $entity_type_id = NULL; + if (!is_object($group)) { + $entity_type_id = $route_match->getRouteObject()->getOption('_og_entity_type_id'); + $group = $route_match->getParameter($entity_type_id); + } // Get list from routes. $content = []; - $event = new OgAdminRoutesEvent(); - $event = $this->eventDispatcher->dispatch(OgAdminRoutesEventInterface::EVENT_NAME, $event); - - foreach ($event->getRoutes($entity_type_id) as $name => $info) { - $route_name = "entity.$entity_type_id.og_admin_routes.$name"; - $parameters = [$entity_type_id => $group->id()]; + $route_name = "og_admin.members"; + $parameters = ['entity_type_id' => $group->getEntityTypeId(), 'group' => $group->id()]; - // We don't use Url::fromRoute() here for the access check, as it will - // prevent us from unit testing this method. - if (!$this->accessManager->checkNamedRoute($route_name, $parameters)) { - // User doesn't have access to the route. - continue; - } - - $content[$name]['title'] = $info['title']; - $content[$name]['description'] = $info['description']; - $content[$name]['url'] = Url::fromRoute($route_name, $parameters); - } - - if (!$content) { + // We don't use Url::fromRoute() here for the access check, as it will + // prevent us from unit testing this method. + if (!$this->accessManager->checkNamedRoute($route_name, $parameters)) { + // User doesn't have access to the route. return ['#markup' => $this->t('You do not have any administrative items.')]; } + $content[] = [ + 'title' => 'Members', + 'description' => 'Manage members', + 'url' => Url::fromRoute($route_name, $parameters), + ]; + return [ 'og_admin_routes' => [ '#theme' => 'admin_block_content', diff --git a/src/Controller/OgAutocompleteController.php b/src/Controller/OgAutocompleteController.php new file mode 100644 index 000000000..7f976976e --- /dev/null +++ b/src/Controller/OgAutocompleteController.php @@ -0,0 +1,113 @@ +matcher = $matcher; + $this->keyValue = $key_value; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.autocomplete_matcher'), + $container->get('keyvalue')->get('entity_autocomplete') + ); + } + + /** + * Autocomplete the label of an entity. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object that contains the typed tags. + * @param \Drupal\Core\Entity\EntityInterface $group + * The group context for this autocomplete. + * @param string $target_type + * The ID of the target entity type. + * @param string $selection_handler + * The plugin ID of the entity reference selection handler. + * @param string $selection_settings_key + * The hashed key of the key/value entry that holds the selection handler + * settings. + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + * The matched entity labels as a JSON response. + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * Thrown if the selection settings key is not found in the key/value store + * or if it does not match the stored data. + */ + public function handleAutocomplete(Request $request, EntityInterface $group, $target_type, $selection_handler, $selection_settings_key) { + $matches = []; + // Get the typed string from the URL, if it exists. + if ($input = $request->query->get('q')) { + $typed_string = Tags::explode($input); + $typed_string = Unicode::strtolower(array_pop($typed_string)); + + // Selection settings are passed in as a hashed key of a serialized array + // stored in the key/value store. + $selection_settings = $this->keyValue->get($selection_settings_key, FALSE); + if ($selection_settings !== FALSE) { + $selection_settings_hash = Crypt::hmacBase64(serialize($selection_settings) . $target_type . $selection_handler, Settings::getHashSalt()); + if ($selection_settings_hash !== $selection_settings_key) { + // Disallow access when the selection settings hash does not match the + // passed-in key. + throw new AccessDeniedHttpException('Invalid selection settings key.'); + } + } + else { + // Disallow access when the selection settings key is not found in the + // key/value store. + throw new AccessDeniedHttpException(); + } + + $selection_settings['group'] = $group; + $matches = $this->matcher->getMatches($target_type, $selection_handler, $selection_settings, $typed_string); + } + + return new JsonResponse($matches); + } + +} diff --git a/src/Controller/SubscriptionController.php b/src/Controller/SubscriptionController.php index a320562d9..8c5b904fc 100644 --- a/src/Controller/SubscriptionController.php +++ b/src/Controller/SubscriptionController.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Drupal\og\Og; +use Drupal\Core\Messenger\MessengerInterface; /** * Controller for OG subscription routes. @@ -29,14 +30,24 @@ class SubscriptionController extends ControllerBase { */ protected $ogAccess; + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + /** * Constructs a SubscriptionController object. * * @param \Drupal\og\OgAccessInterface $og_access * The OG access service. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. */ - public function __construct(OgAccessInterface $og_access) { + public function __construct(OgAccessInterface $og_access, MessengerInterface $messenger) { $this->ogAccess = $og_access; + $this->messenger = $messenger; } /** @@ -44,7 +55,8 @@ public function __construct(OgAccessInterface $og_access) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('og.access') + $container->get('og.access'), + $container->get('messenger') ); } @@ -55,14 +67,14 @@ public static function create(ContainerInterface $container) { * The entity type of the group entity. * @param \Drupal\Core\Entity\EntityInterface $group * The entity ID of the group entity. - * @param \Drupal\og\OgMembershipTypeInterface $membership_type + * @param \Drupal\og\OgMembershipTypeInterface $og_membership_type * The membership type to be used for creating the membership. * * @return mixed * Redirect user or show access denied if they are not allowed to subscribe, * otherwise provide a subscribe confirmation form. */ - public function subscribe($entity_type_id, EntityInterface $group, OgMembershipTypeInterface $membership_type) { + public function subscribe($entity_type_id, EntityInterface $group, OgMembershipTypeInterface $og_membership_type) { if (!$group instanceof ContentEntityInterface) { // Not a valid entity. throw new AccessDeniedHttpException(); @@ -83,12 +95,12 @@ public function subscribe($entity_type_id, EntityInterface $group, OgMembershipT if ($this->config('user.settings')->get('register') === USER_REGISTER_ADMINISTRATORS_ONLY) { $params = [':login' => $user_login_url]; - drupal_set_message($this->t('In order to join any group, you must login. After you have successfully done so, you will need to request membership again.', $params)); + $this->messenger->addMessage($this->t('In order to join any group, you must login. After you have successfully done so, you will need to request membership again.', $params)); } else { $user_register_url = Url::fromRoute('user.register', [], $destination)->toString(); $params = [':register' => $user_register_url, ':login' => $user_login_url]; - drupal_set_message($this->t('In order to join any group, you must login or register a new account. After you have successfully done so, you will need to request membership again.', $params)); + $this->messenger->addMessage($this->t('In order to join any group, you must login or register a new account. After you have successfully done so, you will need to request membership again.', $params)); } return new RedirectResponse(Url::fromRoute('user.page')->setAbsolute(TRUE)->toString()); @@ -119,7 +131,7 @@ public function subscribe($entity_type_id, EntityInterface $group, OgMembershipT } if ($redirect) { - drupal_set_message($message, 'warning'); + $this->messenger->addWarning($message); return new RedirectResponse($group->toUrl()->setAbsolute(TRUE)->toString()); } @@ -127,7 +139,7 @@ public function subscribe($entity_type_id, EntityInterface $group, OgMembershipT throw new AccessDeniedHttpException(); } - $membership = Og::createMembership($group, $user, $membership_type->id()); + $membership = Og::createMembership($group, $user, $og_membership_type->id()); $form = $this->entityFormBuilder()->getForm($membership, 'subscribe'); return $form; @@ -164,7 +176,7 @@ public function unsubscribe(ContentEntityInterface $group) { if ($group instanceof EntityOwnerInterface && $group->getOwnerId() == $user->id()) { // The user is the manager of the group. - drupal_set_message($this->t('As the manager of %group, you can not leave the group.', ['%group' => $group->label()])); + $this->messenger->addMessage($this->t('As the manager of %group, you can not leave the group.', ['%group' => $group->label()])); return new RedirectResponse($group->toUrl() ->setAbsolute() diff --git a/src/Element/OgAutocomplete.php b/src/Element/OgAutocomplete.php new file mode 100644 index 000000000..9047564b8 --- /dev/null +++ b/src/Element/OgAutocomplete.php @@ -0,0 +1,61 @@ +id(); + } + + // Store the selection settings in the key/value store and pass a hashed key + // in the route parameters. + $selection_settings = isset($element['#selection_settings']) ? $element['#selection_settings'] : []; + $data = serialize($selection_settings) . $element['#target_type'] . $element['#selection_handler']; + $selection_settings_key = Crypt::hmacBase64($data, Settings::getHashSalt()); + + $key_value_storage = \Drupal::keyValue('entity_autocomplete'); + if (!$key_value_storage->has($selection_settings_key)) { + $key_value_storage->set($selection_settings_key, $selection_settings); + } + + $element['#autocomplete_route_name'] = 'og.entity_autocomplete'; + $element['#autocomplete_route_parameters'] = [ + 'target_type' => $element['#target_type'], + 'selection_handler' => $element['#selection_handler'], + 'selection_settings_key' => $selection_settings_key, + 'entity_type_id' => $element['#og_group']->getEntityTypeId(), + 'group' => $element['#og_group']->id(), + ]; + + return $element; + } + +} diff --git a/src/Entity/OgMembership.php b/src/Entity/OgMembership.php index c9ccf9788..4d11b2897 100644 --- a/src/Entity/OgMembership.php +++ b/src/Entity/OgMembership.php @@ -59,6 +59,7 @@ * fieldable = TRUE, * bundle_entity_type = "og_membership_type", * entity_keys = { + * "uuid" = "uuid", * "id" = "id", * "bundle" = "type", * }, @@ -66,16 +67,38 @@ * "bundle" = "type", * }, * handlers = { + * "access" = "Drupal\og\OgMembershipAccessControlHandler", * "views_data" = "Drupal\og\OgMembershipViewsData", + * "list_builder" = "Drupal\Core\Entity\EntityListBuilder", + * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", * "form" = { * "subscribe" = "Drupal\og\Form\GroupSubscribeForm", * "unsubscribe" = "Drupal\og\Form\GroupUnsubscribeConfirmForm", + * "add" = "Drupal\og\Form\OgMembershipForm", + * "edit" = "Drupal\og\Form\OgMembershipForm", + * "delete" = "Drupal\og\Form\OgMembershipDeleteForm", * }, - * } + * }, + * links = { + * "edit-form" = "/group/{entity_type_id}/{group}/admin/members/{og_membership}/edit", + * "delete-form" = "/group/{entity_type_id}/{group}/admin/members/{og_membership}/delete", + * "canonical" = "/group/{entity_type_id}/{group}/admin/members/{og_membership}/edit" + * }, + * field_ui_base_route = "entity.og_membership_type.edit_form" * ) */ class OgMembership extends ContentEntityBase implements OgMembershipInterface { + /** + * {@inheritdoc} + */ + protected function urlRouteParameters($rel) { + $uri_route_parameters = parent::urlRouteParameters($rel); + $uri_route_parameters['entity_type_id'] = $this->getGroupEntityType(); + $uri_route_parameters['group'] = $this->getGroupId(); + return $uri_route_parameters; + } + /** * {@inheritdoc} */ @@ -356,9 +379,23 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setSetting('target_type', 'og_membership_type'); $fields['uid'] = BaseFieldDefinition::create('entity_reference') - ->setLabel(t('Member User ID')) + ->setLabel(t('Username')) ->setDescription(t('The user ID of the member.')) - ->setSetting('target_type', 'user'); + ->setSetting('target_type', 'user') + ->setSetting('handler', 'og:user') + ->setConstraints(['UniqueOgMembership' => []]) + ->setDisplayOptions('form', [ + 'type' => 'og_autocomplete', + 'weight' => -1, + 'settings' => [ + 'match_operator' => 'CONTAINS', + 'size' => 60, + 'placeholder' => '', + ], + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE) + ->setRequired(TRUE); $fields['entity_type'] = BaseFieldDefinition::create('string') ->setLabel(t('Group entity type')) @@ -372,21 +409,38 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setLabel(t('Group entity ID')) ->setDescription(t('The entity ID of the group.')); - $fields['state'] = BaseFieldDefinition::create('string') + $fields['state'] = BaseFieldDefinition::create('list_string') ->setLabel(t('State')) ->setDescription(t('The user membership state: active, pending, or blocked.')) - ->setDefaultValue(OgMembershipInterface::STATE_ACTIVE); + ->setDefaultValue(OgMembershipInterface::STATE_ACTIVE) + ->setSettings([ + 'allowed_values' => [ + OgMembershipInterface::STATE_ACTIVE => t('Active'), + OgMembershipInterface::STATE_PENDING => t('Pending'), + OgMembershipInterface::STATE_BLOCKED => t('Blocked'), + ], + ]) + ->setDisplayOptions('form', [ + 'type' => 'options_buttons', + 'weight' => 0, + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE) + ->setRequired(TRUE); $fields['roles'] = BaseFieldDefinition::create('entity_reference') ->setLabel(t('Roles')) ->setDescription(t('The OG roles related to an OG membership entity.')) ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) - ->setDisplayOptions('view', [ - 'label' => 'hidden', - 'type' => 'entity_reference_label', + ->setSetting('target_type', 'og_role') + ->setSetting('handler', 'og:og_role') + ->setConstraints(['ValidOgRole' => []]) + ->setDisplayOptions('form', [ + 'type' => 'options_buttons', 'weight' => 0, ]) - ->setSetting('target_type', 'og_role'); + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); $fields['created'] = BaseFieldDefinition::create('created') ->setLabel(t('Create')) diff --git a/src/Entity/OgMembershipType.php b/src/Entity/OgMembershipType.php index 821b29966..acac6fd54 100644 --- a/src/Entity/OgMembershipType.php +++ b/src/Entity/OgMembershipType.php @@ -3,6 +3,8 @@ namespace Drupal\og\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\field\Entity\FieldConfig; +use Drupal\og\OgMembershipInterface; use Drupal\og\OgMembershipTypeInterface; /** @@ -20,11 +22,26 @@ * @ConfigEntityType( * id = "og_membership_type", * label = @Translation("OG membership type"), + * handlers = { + * "access" = "Drupal\Core\Entity\EntityAccessControlHandler", + * "form" = { + * "add" = "Drupal\og\Form\OgMembershipTypeForm", + * "edit" = "Drupal\og\Form\OgMembershipTypeForm", + * "delete" = "Drupal\Core\Entity\EntityDeleteForm" + * }, + * "list_builder" = "Drupal\og\OgMembershipTypeListBuilder" + * }, + * admin_permission = "administer group", * config_prefix = "og_membership_type", * bundle_of = "og_membership", * entity_keys = { * "id" = "type", * "label" = "name" + * }, + * links = { + * "edit-form" = "/admin/structure/membership-types/manage/{membership_type}", + * "delete-form" = "/admin/structure/membership-types/manage/{membership_type}/delete", + * "collection" = "/admin/structure/membership-types", * } * ) */ @@ -42,4 +59,35 @@ public function id() { return $this->type; } + /** + * {@inheritdoc} + */ + public function save() { + $status = parent::save(); + + if ($status === SAVED_NEW) { + FieldConfig::create([ + 'field_name' => 'og_membership_request', + 'entity_type' => 'og_membership', + 'bundle' => $this->id(), + 'label' => 'Request Membership', + 'description' => 'Explain the motivation for your request to join this group.', + 'translatable' => TRUE, + 'settings' => [], + ])->save(); + } + + return $status; + } + + /** + * {@inheritdoc} + */ + public function delete() { + if ($this->id() === OgMembershipInterface::TYPE_DEFAULT) { + throw \Exception("The default OG membership type cannot be deleted."); + } + parent::delete(); + } + } diff --git a/src/Event/OgAdminRoutesEvent.php b/src/Event/OgAdminRoutesEvent.php deleted file mode 100644 index 575ccf90c..000000000 --- a/src/Event/OgAdminRoutesEvent.php +++ /dev/null @@ -1,72 +0,0 @@ -routesInfo = $routes_info; - } - - /** - * {@inheritdoc} - */ - public function getRoutesInfo() { - return $this->routesInfo; - } - - /** - * {@inheritdoc} - */ - public function getRoutes($entity_type_id) { - $routes_info = []; - - foreach ($this->routesInfo as $name => $route_info) { - - $routes_info[$name] = $route_info; - - // Add default values. - $routes_info[$name] += [ - 'description' => '', - - 'requirements' => [ - '_og_user_access_group' => 'administer group', - ], - - 'options' => [ - 'parameters' => [ - $entity_type_id => ['type' => 'entity:' . $entity_type_id], - ], - // The above parameters doesn't send the entity, - // so we will have to use the Route matcher to extract it. - '_og_entity_type_id' => $entity_type_id, - '_admin_route' => TRUE, - ], - - // Move the title and controller under the "defaults" key. - 'defaults' => [ - '_controller' => $route_info['controller'], - '_title' => $route_info['title'], - ], - ]; - } - - return $routes_info; - } - -} diff --git a/src/Event/OgAdminRoutesEventInterface.php b/src/Event/OgAdminRoutesEventInterface.php deleted file mode 100644 index 5ad4bd851..000000000 --- a/src/Event/OgAdminRoutesEventInterface.php +++ /dev/null @@ -1,49 +0,0 @@ - [['provideDefaultRoles']], - OgAdminRoutesEventInterface::EVENT_NAME => [['provideOgAdminRoutes']], ]; } @@ -342,28 +340,4 @@ protected function generateEntityOperationPermissionList($group_content_entity_t return $permissions; } - /** - * Provide OG admin routes. - * - * @param \Drupal\og\Event\OgAdminRoutesEventInterface $event - * The OG admin routes event object. - */ - public function provideOgAdminRoutes(OgAdminRoutesEventInterface $event) { - $routes_info = $event->getRoutesInfo(); - - $routes_info['members'] = [ - 'controller' => '\Drupal\og\Controller\OgAdminMembersController::membersList', - 'title' => 'Members', - 'description' => 'Manage members', - 'path' => 'members', - 'requirements' => [ - '_og_user_access_group' => 'administer group', - // Views module must be enabled. - '_module_dependencies' => 'views', - ], - ]; - - $event->setRoutesInfo($routes_info); - } - } diff --git a/src/Form/GroupSubscribeForm.php b/src/Form/GroupSubscribeForm.php index d77fc69a4..39a715e4c 100644 --- a/src/Form/GroupSubscribeForm.php +++ b/src/Form/GroupSubscribeForm.php @@ -11,6 +11,7 @@ use Drupal\og\OgAccessInterface; use Drupal\og\OgMembershipInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Messenger\MessengerInterface; /** * Provides a form for subscribing to a group. @@ -30,6 +31,13 @@ class GroupSubscribeForm extends ContentEntityForm { */ protected $ogAccess; + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + /** * Constructs a GroupSubscribeForm. * @@ -41,6 +49,8 @@ class GroupSubscribeForm extends ContentEntityForm { * The entity type bundle service. * @param \Drupal\Component\Datetime\TimeInterface $time * The time service. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. * * @todo Set the `EntityRepositoryInterface` type hint on the second argument * once Drupal 8.6.0 is released. It is currently omitted to preserve @@ -48,9 +58,16 @@ class GroupSubscribeForm extends ContentEntityForm { * * @see https://github.com/Gizra/og/issues/397 */ - public function __construct(OgAccessInterface $og_access, $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) { + public function __construct( + OgAccessInterface $og_access, + EntityRepositoryInterface $entity_repository, + EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, + TimeInterface $time = NULL, + MessengerInterface $messenger + ) { parent::__construct($entity_repository, $entity_type_bundle_info, $time); $this->ogAccess = $og_access; + $this->messenger = $messenger; } /** @@ -72,7 +89,8 @@ public static function create(ContainerInterface $container) { $container->get('og.access'), $container->get($entity_repository_service), $container->get('entity_type.bundle.info'), - $container->get('datetime.time') + $container->get('datetime.time'), + $container->get('messenger') ); } @@ -235,7 +253,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $group = $membership->getGroup(); $message = $membership->isActive() ? $this->t('Your are now subscribed to the group.') : $this->t('Your subscription request was sent.'); - drupal_set_message($message); + $this->messenger->addMessage($message); // User doesn't have access to the group entity, so redirect to front page, // otherwise back to the group entity. diff --git a/src/Form/GroupUnsubscribeConfirmForm.php b/src/Form/GroupUnsubscribeConfirmForm.php index 08f4fa0aa..d224ae42e 100644 --- a/src/Form/GroupUnsubscribeConfirmForm.php +++ b/src/Form/GroupUnsubscribeConfirmForm.php @@ -5,12 +5,39 @@ use Drupal\Core\Entity\ContentEntityDeleteForm; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; +use Drupal\Core\Messenger\MessengerInterface; /** * Provides a confirmation form for unsubscribing form a group. */ class GroupUnsubscribeConfirmForm extends ContentEntityDeleteForm { + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** + * Constructs the GroupUnsubscribeConfirmForm object. + * + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. + */ + public function __construct(MessengerInterface $messenger) { + $this->messenger = $messenger; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('messenger') + ); + } + /** * {@inheritdoc} */ @@ -62,7 +89,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $form_state->setRedirectUrl($redirect); $membership->delete(); - drupal_set_message($this->t('You have unsubscribed from the group.')); + $this->messenger->addMessage($this->t('You have unsubscribed from the group.')); } } diff --git a/src/Form/OgMembershipDeleteForm.php b/src/Form/OgMembershipDeleteForm.php new file mode 100644 index 000000000..eaa049d8c --- /dev/null +++ b/src/Form/OgMembershipDeleteForm.php @@ -0,0 +1,53 @@ +getEntity(); + + return $this->t("%user has been unsubscribed from %group.", [ + '%user' => $membership->getOwner()->getDisplayName(), + '%group' => $membership->getGroup()->label(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function logDeletionMessage() { + /** @var \Drupal\og\Entity\OgMembership $entity */ + $membership = $this->getEntity(); + + $this->logger('og')->notice("OG Membership: deleted the @membership_type membership for the user uid: @uid to the group of the entity-type @group_type and ID: @gid", [ + '@membership_type' => $membership->getType(), + '@uid' => $membership->getOwner()->id(), + '@group_type' => $membership->getGroupEntityType(), + '@gid' => $membership->getGroupId(), + ]); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + /** @var \Drupal\og\Entity\OgMembership $entity */ + $membership = $this->getEntity(); + + return $this->t("Are you sure you want to unsubscribe %user from %group?", [ + '%user' => $membership->getOwner()->getDisplayName(), + '%group' => $membership->getGroup()->label(), + ]); + } + +} diff --git a/src/Form/OgMembershipForm.php b/src/Form/OgMembershipForm.php new file mode 100644 index 000000000..73dcd959c --- /dev/null +++ b/src/Form/OgMembershipForm.php @@ -0,0 +1,150 @@ +ogAccess = $og_access; + $this->messenger = $messenger; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager'), + $container->get('entity_type.bundle.info'), + $container->get('datetime.time'), + $container->get('og.access'), + $container->get('messenger') + ); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + /** @var \Drupal\og\Entity\OgMembership $entity */ + $entity = $this->getEntity(); + /** @var \Drupal\Core\Entity\ContentEntityInterface $group */ + $group = $entity->getGroup(); + + $form = parent::form($form, $form_state); + $form['#title'] = $this->t('Add member to %group', ['%group' => $group->label()]); + $form['entity_type'] = ['#value' => $entity->getEntityType()->id()]; + $form['entity_id'] = ['#value' => $group->id()]; + + if ($entity->getType() != OgMembershipInterface::TYPE_DEFAULT) { + $form['membership_type'] = [ + '#title' => $this->t('Membership type'), + '#type' => 'item', + '#plain_text' => $entity->type->entity->label(), + '#weight' => -2, + ]; + } + + if ($this->operation == 'edit') { + $form['#title'] = $this->t('Edit membership in %group', ['%group' => $group->label()]); + $form['uid']['#access'] = FALSE; + $form['member'] = [ + '#title' => $this->t('Member name'), + '#type' => 'item', + '#markup' => $entity->getOwner()->getDisplayName(), + '#weight' => -10, + ]; + } + + // Require the 'manage members' permission to be able to edit roles. + $form['roles']['#access'] = $this->ogAccess + ->userAccess($group, 'manage members') + ->isAllowed(); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $membership = $this->entity; + $insert = $membership->isNew(); + $membership->save(); + + $membership_link = $membership->link($this->t('View')); + + $context = [ + '@membership_type' => $membership->getType(), + '@uid' => $membership->getOwner()->id(), + '@group_type' => $membership->getGroupEntityType(), + '@gid' => $membership->getGroupId(), + 'link' => $membership_link, + ]; + + $t_args = [ + '%user' => $membership->getOwner()->link(), + '%group' => $membership->getGroup()->link(), + ]; + + if ($insert) { + $this->logger('og')->notice('OG Membership: added the @membership_type membership for the use uid @uid to the group of the entity-type @group_type and ID @gid.', $context); + $this->messenger->addMessage($this->t('Added %user to %group.', $t_args)); + return; + } + + $this->logger('og')->notice('OG Membership: updated the @membership_type membership for the use uid @uid to the group of the entity-type @group_type and ID @gid.', $context); + $this->messenger->addMessage($this->t('Updated the membership for %user to %group.', $t_args)); + } + +} diff --git a/src/Form/OgMembershipTypeForm.php b/src/Form/OgMembershipTypeForm.php new file mode 100644 index 000000000..540970ec6 --- /dev/null +++ b/src/Form/OgMembershipTypeForm.php @@ -0,0 +1,138 @@ +entityTypeManager = $entity_manager; + $this->messenger = $messenger; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('messenger') + ); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $type = $this->entity; + if ($this->operation == 'add') { + $form['#title'] = $this->t('Add membership type'); + } + else { + $form['#title'] = $this->t('Edit %label membership type', ['%label' => $type->label()]); + } + + $form['name'] = [ + '#title' => $this->t('Name'), + '#type' => 'textfield', + '#default_value' => $type->label(), + '#description' => $this->t('The human-readable name of this membership type.'), + '#required' => TRUE, + '#size' => 30, + ]; + + $form['type'] = [ + '#type' => 'machine_name', + '#default_value' => $type->id(), + '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH, + '#machine_name' => [ + 'exists' => ['Drupal\og\Entity\OgMembershipType', 'load'], + 'source' => ['name'], + ], + '#description' => $this->t('A unique machine-readable name for this membership type. It must only contain lowercase letters, numbers, and underscores.'), + ]; + return $this->protectBundleIdElement($form); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions = parent::actions($form, $form_state); + $actions['submit']['#value'] = $this->t('Save membership type'); + $actions['delete']['#value'] = $this->t('Delete membership type'); + return $actions; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); + + $id = trim($form_state->getValue('type')); + // '0' is invalid, since elsewhere we check it using empty(). + if ($id == '0') { + $form_state->setErrorByName('type', $this->t("Invalid machine-readable name. Enter a name other than %invalid.", ['%invalid' => $id])); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $type = $this->entity; + $type->set('type', trim($type->id())); + $type->set('name', trim($type->label())); + + $status = $type->save(); + + $t_args = ['%name' => $type->label()]; + + if ($status == SAVED_UPDATED) { + $this->messenger->addMessage($this->t('The membership type %name has been updated.', $t_args)); + } + elseif ($status == SAVED_NEW) { + $this->messenger->addMessage($this->t('The membership type %name has been added.', $t_args)); + $context = array_merge($t_args, ['link' => $type->link($this->t('View'), 'collection')]); + $this->logger('og')->notice('Added membership type %name.', $context); + } + + $this->entityTypeManager->clearCachedFieldDefinitions(); + $form_state->setRedirectUrl($type->urlInfo('collection')); + } + +} diff --git a/src/MembershipManager.php b/src/MembershipManager.php index 7677d35f4..27a9d2e42 100644 --- a/src/MembershipManager.php +++ b/src/MembershipManager.php @@ -131,6 +131,23 @@ public function getMembership(EntityInterface $group, AccountInterface $user, ar return NULL; } + /** + * {@inheritdoc} + */ + public function getGroupMembershipCount(EntityInterface $group, array $states = [OgMembershipInterface::STATE_ACTIVE]) { + $query = $this->entityTypeManager + ->getStorage('og_membership') + ->getQuery() + ->condition('entity_id', $group->id()); + + if ($states) { + $query->condition('state', $states, 'IN'); + } + + $query->count(); + return $query->execute(); + } + /** * {@inheritdoc} */ diff --git a/src/MembershipManagerInterface.php b/src/MembershipManagerInterface.php index d117645bb..bbe6db710 100644 --- a/src/MembershipManagerInterface.php +++ b/src/MembershipManagerInterface.php @@ -68,6 +68,19 @@ public function getUserGroups(AccountInterface $user, array $states = [OgMembers */ public function getMemberships(AccountInterface $user, array $states = [OgMembershipInterface::STATE_ACTIVE]); + /** + * Returns the number of group memberships for a given group. + * + * @param \Drupal\Core\Entity\EntityInterface $group + * The group to get the membership for. + * @param array $states + * (optional) Array with the state to return. Defaults to active. + * + * @return int + * The number of memberships for the group. + */ + public function getGroupMembershipCount(EntityInterface $group, array $states = [OgMembershipInterface::STATE_ACTIVE]); + /** * Returns the group membership for a given user and group. * diff --git a/src/Og.php b/src/Og.php index 1aa2361e5..0ae939c1f 100644 --- a/src/Og.php +++ b/src/Og.php @@ -164,6 +164,23 @@ public static function getMembership(EntityInterface $group, AccountInterface $u return $membership_manager->getMembership($group, $user, $states); } + /** + * Returns the group memberships for a given group. + * + * @param \Drupal\Core\Entity\EntityInterface $group + * The group to get the membership for. + * @param array $states + * (optional) Array with the state to return. Defaults to active. + * + * @return \Drupal\og\OgMembershipInterface[] + * An array of OgMembership entities, keyed by ID. + */ + public static function getGroupMemberships(EntityInterface $group, array $states = [OgMembershipInterface::STATE_ACTIVE]) { + /** @var \Drupal\og\MembershipManagerInterface $membership_manager */ + $membership_manager = \Drupal::service('og.membership_manager'); + return $membership_manager->getGroupMemberships($group, $states); + } + /** * Creates an OG membership. * diff --git a/src/OgMembershipAccessControlHandler.php b/src/OgMembershipAccessControlHandler.php new file mode 100644 index 000000000..d9b352dc1 --- /dev/null +++ b/src/OgMembershipAccessControlHandler.php @@ -0,0 +1,155 @@ +entityTypeId = $entity_type->id(); + $this->entityType = $entity_type; + $this->ogAccess = $og_access; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('og.access') + ); + } + + /** + * {@inheritdoc} + */ + protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { + $group = $entity->getGroup(); + + // Do not allow deleting the group owner's membership. + if (($operation === 'delete') && ($group instanceof EntityOwnerInterface) && ($group->getOwnerId() == $entity->getOwner()->id())) { + return AccessResult::forbidden(); + } + + // If the user has permission to administer all groups, allow access. + if ($account->hasPermission('administer group')) { + return AccessResult::allowed(); + } + + $permissions = [OgAccess::ADMINISTER_GROUP_PERMISSION, 'manager members']; + foreach ($permissions as $permission) { + $result = $this->ogAccess->userAccess($group, $permission, $account); + if ($result->isAllowed()) { + return $result; + } + } + + return AccessResult::neutral(); + + } + + /** + * {@inheritdoc} + */ + public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = [], $return_as_object = FALSE) { + $account = $this->prepareUser($account); + $context += [ + 'entity_type_id' => $this->entityTypeId, + 'langcode' => LanguageInterface::LANGCODE_DEFAULT, + ]; + + $cid = 'create:' . $context['group']->getEntityTypeId() . ':' . $context['group']->id(); + if ($entity_bundle) { + $cid .= ':' . $entity_bundle; + } + + if (($access = $this->getCache($cid, 'create', $context['langcode'], $account)) !== NULL) { + // Cache hit, no work necessary. + return $return_as_object ? $access : $access->isAllowed(); + } + + // Invoke hook_entity_create_access() and hook_ENTITY_TYPE_create_access(). + // Hook results take precedence over overridden implementations of + // EntityAccessControlHandler::checkCreateAccess(). Entities that have + // checks that need to be done before the hook is invoked should do so by + // overriding this method. + // We grant access to the entity if both of these conditions are met: + // - No modules say to deny access. + // - At least one module says to grant access. + $args = [$account, $context, $entity_bundle]; + $access = array_merge( + $this->moduleHandler()->invokeAll('entity_create_access', $args), + $this->moduleHandler()->invokeAll($this->entityTypeId . '_create_access', $args) + ); + + $return = $this->processAccessHookResults($access); + + // Also execute the default access check except when the access result is + // already forbidden, as in that case, it can not be anything else. + if (!$return->isForbidden()) { + $return = $return->orIf($this->checkCreateAccess($account, $context, $entity_bundle)); + } + $result = $this->setCache($return, $cid, 'create', $context['langcode'], $account); + return $return_as_object ? $result : $result->isAllowed(); + } + + /** + * {@inheritdoc} + */ + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + // If the user has permission to administer all groups, allow access. + if ($account->hasPermission('administer group')) { + return AccessResult::allowed(); + } + + $group = $context['group']; + + // If we don't have a group, we can't really determine access other than + // checking global account permissions. + if ($group === NULL) { + return AccessResult::neutral(); + } + + $permissions = [ + OgAccess::ADMINISTER_GROUP_PERMISSION, + 'add user', + 'manager members', + ]; + foreach ($permissions as $permission) { + $result = $this->ogAccess->userAccess($group, $permission, $account); + if ($result->isAllowed()) { + return $result; + } + } + + return AccessResult::neutral(); + } + +} diff --git a/src/OgMembershipTypeListBuilder.php b/src/OgMembershipTypeListBuilder.php new file mode 100644 index 000000000..47f992ab4 --- /dev/null +++ b/src/OgMembershipTypeListBuilder.php @@ -0,0 +1,47 @@ + $entity->label(), + 'class' => ['menu-label'], + ]; + return $row + parent::buildRow($entity); + } + + /** + * {@inheritdoc} + */ + public function getDefaultOperations(EntityInterface $entity) { + $operations = parent::getDefaultOperations($entity); + // Place the edit operation after the operations added by field_ui.module + // which have the weights 15, 20, 25. + if (isset($operations['edit'])) { + $operations['edit']['weight'] = 30; + } + return $operations; + } + +} diff --git a/src/Plugin/Derivative/OgLocalTask.php b/src/Plugin/Derivative/OgLocalTask.php index c861065c4..65cf6f769 100644 --- a/src/Plugin/Derivative/OgLocalTask.php +++ b/src/Plugin/Derivative/OgLocalTask.php @@ -28,7 +28,7 @@ class OgLocalTask extends DeriverBase implements ContainerDeriverInterface { * * @var \Drupal\Core\Routing\RouteProvider */ - protected $routProvider; + protected $routeProvider; /** * Creates an OgLocalTask object. @@ -40,7 +40,7 @@ class OgLocalTask extends DeriverBase implements ContainerDeriverInterface { */ public function __construct(GroupTypeManagerInterface $group_type_manager, RouteProvider $route_provider) { $this->groupTypeManager = $group_type_manager; - $this->routProvider = $route_provider; + $this->routeProvider = $route_provider; } /** @@ -60,14 +60,14 @@ public function getDerivativeDefinitions($base_plugin_definition) { $derivatives = []; foreach (array_keys($this->groupTypeManager->getGroupMap()) as $entity_type_id) { - $route_name = "entity.$entity_type_id.og_admin_routes"; + $route_name = "entity.$entity_type_id.og_admin"; - if (!$this->routProvider->getRoutesByNames([$route_name])) { + if (!$this->routeProvider->getRoutesByNames([$route_name])) { // Route not found. continue; } - $derivatives[$entity_type_id . '.og_admin_routes'] = [ + $derivatives[$route_name] = [ 'route_name' => $route_name, 'title' => $this->t('Group'), 'base_route' => 'entity.' . $entity_type_id . '.canonical', diff --git a/src/Plugin/EntityReferenceSelection/OgRoleSelection.php b/src/Plugin/EntityReferenceSelection/OgRoleSelection.php new file mode 100644 index 000000000..c6f2a52f8 --- /dev/null +++ b/src/Plugin/EntityReferenceSelection/OgRoleSelection.php @@ -0,0 +1,64 @@ + 'og_role', + ]; + return \Drupal::service('plugin.manager.entity_reference_selection')->getInstance($options); + } + + /** + * {@inheritdoc} + */ + protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') { + $query = parent::buildEntityQuery($match, $match_operator); + + // @todo implement an easier, more consistent way to get the group type. At + // the moment, this works either for checkboxes or OG Autocomplete widget + // types on entities that have a getGroup() method. It also does not work + // properly every time; for example during validation. + $group = NULL; + if (isset($this->configuration['entity'])) { + $entity = $this->configuration['entity']; + $group = is_callable([$entity, 'getGroup']) ? $entity->getGroup() : NULL; + } + + if (isset($this->configuration['handler_settings']['group'])) { + $group = $this->configuration['handler_settings']['group']; + } + + if ($group === NULL) { + return $query; + } + + $query->condition('group_type', $group->getEntityTypeId(), '='); + $query->condition('group_bundle', $group->bundle(), '='); + $query->condition($query->orConditionGroup() + ->condition('role_type', NULL, 'IS NULL') + ->condition('role_type', 'required', '<>')); + return $query; + } + +} diff --git a/src/Plugin/EntityReferenceSelection/OgUserSelection.php b/src/Plugin/EntityReferenceSelection/OgUserSelection.php new file mode 100644 index 000000000..e66edd5d6 --- /dev/null +++ b/src/Plugin/EntityReferenceSelection/OgUserSelection.php @@ -0,0 +1,194 @@ +connection = $connection; + $this->userStorage = $entity_manager->getStorage('user'); + $this->membershipManager = $membership_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity.manager'), + $container->get('module_handler'), + $container->get('current_user'), + $container->get('database'), + $container->get('og.membership_manager') + ); + } + + /** + * Get the selection handler of the field. + * + * @return Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection + * Returns the selection handler. + */ + public function getSelectionHandler() { + $options = [ + 'target_type' => 'user', + ]; + return \Drupal::service('plugin.manager.entity_reference_selection')->getInstance($options); + } + + /** + * {@inheritdoc} + */ + protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') { + $query = parent::buildEntityQuery($match, $match_operator); + + // Anon can't be a group member. + $query->condition('uid', 0, '<>'); + + // The user entity doesn't have a label column. + if (isset($match)) { + $query->condition('name', $match, $match_operator); + } + + // Adding the permission check is sadly insufficient for users: core + // requires us to also know about the concept of 'blocked' and 'active'. + if (!$this->currentUser->hasPermission('administer users')) { + $query->condition('status', 1); + } + + return $query; + } + + /** + * {@inheritdoc} + */ + public function entityQueryAlter(SelectInterface $query) { + // Exclude users who are already in the current group. + // This has to be done on the SQL query rather than the entity query, + // because a reverse relationship to the OG membership entity is needed. + // @todo implement an easier, more consistent way to get the group type. At + // the moment, this works either for checkboxes or OG Autocomplete widget + // types on entities that have a getGroup() method. It also does not work + // properly every time; for example during validation. + $group = NULL; + if (isset($this->configuration['entity'])) { + $entity = $this->configuration['entity']; + $group = is_callable([$entity, 'getGroup']) ? $entity->getGroup() : NULL; + } + + if (isset($this->configuration['handler_settings']['group'])) { + $group = $this->configuration['handler_settings']['group']; + } + + if ($group === NULL) { + return $query; + } + + // Left join to the OG membership base table. + $query->leftJoin('og_membership', 'ogm', "base_table.uid = ogm.uid AND ogm.entity_type = :entity_type AND ogm.entity_id = :entity_id", [ + ':entity_type' => $group->getEntityTypeId(), + ':entity_id' => $group->id(), + ]); + + // Exclude any users who are in the current group. + $query->isNull('ogm.id'); + } + + /** + * {@inheritdoc} + */ + public function createNewEntity($entity_type_id, $bundle, $label, $uid) { + $user = parent::createNewEntity($entity_type_id, $bundle, $label, $uid); + + // In order to create a referenceable user, it needs to be active. + if (!$this->currentUser->hasPermission('administer users')) { + /** @var \Drupal\user\UserInterface $user */ + $user->activate(); + } + + return $user; + } + + /** + * {@inheritdoc} + */ + public function validateReferenceableNewEntities(array $entities) { + $entities = parent::validateReferenceableNewEntities($entities); + // Mirror the conditions checked in buildEntityQuery(). + if (!$this->currentUser->hasPermission('administer users')) { + $entities = array_filter($entities, function ($user) { + /** @var \Drupal\user\UserInterface $user */ + return $user->isActive(); + }); + } + return $entities; + } + +} diff --git a/src/Plugin/Field/FieldFormatter/GroupSubscribeFormatter.php b/src/Plugin/Field/FieldFormatter/GroupSubscribeFormatter.php index ab3431c3d..ca19e00d4 100644 --- a/src/Plugin/Field/FieldFormatter/GroupSubscribeFormatter.php +++ b/src/Plugin/Field/FieldFormatter/GroupSubscribeFormatter.php @@ -141,6 +141,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) { $parameters = [ 'entity_type_id' => $group->getEntityTypeId(), 'group' => $group->id(), + 'og_membership_type' => OgMembershipInterface::TYPE_DEFAULT, ]; $url = Url::fromRoute('og.subscribe', $parameters); diff --git a/src/Plugin/Field/FieldWidget/OgAutocomplete.php b/src/Plugin/Field/FieldWidget/OgAutocomplete.php new file mode 100644 index 000000000..0a2aa4042 --- /dev/null +++ b/src/Plugin/Field/FieldWidget/OgAutocomplete.php @@ -0,0 +1,48 @@ +getEntity(); + if (!is_callable([$entity, 'getGroup'])) { + return $element; + } + + $element['target_id']['#type'] = 'og_autocomplete'; + $element['target_id']['#og_group'] = $entity->getGroup(); + + return $element; + } + + /** + * {@inheritdoc} + */ + public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) { + return $element; + } + +} diff --git a/src/Plugin/Validation/Constraint/UniqueOgMembershipConstraint.php b/src/Plugin/Validation/Constraint/UniqueOgMembershipConstraint.php new file mode 100644 index 000000000..905bc6a28 --- /dev/null +++ b/src/Plugin/Validation/Constraint/UniqueOgMembershipConstraint.php @@ -0,0 +1,26 @@ +getEntity(); + + // Only applicable to new memberships. + if (!$entity->isNew()) { + return; + } + + // The default entity reference constraint adds a violation in this case. + $value = $value->getValue(); + if (!isset($value[0]) || !isset($value[0]['target_id'])) { + return; + } + + $new_member_uid = $value[0]['target_id']; + + $query = \Drupal::service('entity_type.manager') + ->getStorage('og_membership') + ->getQuery() + ->condition('entity_type', $entity->getGroupEntityType()) + ->condition('entity_id', $entity->getGroupId()) + ->condition('uid', $new_member_uid); + $membership_ids = $query->execute(); + + if ($membership_ids) { + $user = \Drupal::service('entity_type.manager')->getStorage('user')->load($new_member_uid); + $this->context->addViolation($constraint->NotUniqueMembership, ['%user' => $user->getDisplayName()]); + return; + } + } + +} diff --git a/src/Plugin/Validation/Constraint/ValidOgRoleConstraint.php b/src/Plugin/Validation/Constraint/ValidOgRoleConstraint.php new file mode 100644 index 000000000..6fdbc8bdf --- /dev/null +++ b/src/Plugin/Validation/Constraint/ValidOgRoleConstraint.php @@ -0,0 +1,24 @@ +getEntity(); + if (!$entity) { + // Entity with that entity ID does not exists. This could happen if a + // stale entity is passed for validation. + return; + } + + $group_type = $entity->getGroup()->getEntityTypeId(); + $group_bundle = $entity->getGroup()->bundle(); + + foreach ($value->referencedEntities() as $og_role) { + if ($og_role->getGroupType() !== $group_type || $og_role->getGroupBundle() !== $group_bundle) { + $this->context->addViolation($constraint->NotValidRole); + } + } + + } + +} diff --git a/src/Routing/RouteSubscriber.php b/src/Routing/RouteSubscriber.php index 2ae800d96..24c6b095d 100644 --- a/src/Routing/RouteSubscriber.php +++ b/src/Routing/RouteSubscriber.php @@ -7,8 +7,6 @@ use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\Core\Routing\RoutingEvents; use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\og\Event\OgAdminRoutesEvent; -use Drupal\og\Event\OgAdminRoutesEventInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -74,7 +72,7 @@ protected function alterRoutes(RouteCollection $collection) { } $entity_type_id = $entity_type->id(); - $route_name = "entity.$entity_type_id.og_admin_routes"; + $route_name = "entity.$entity_type_id.og_admin"; $route = new Route($og_admin_path); $route @@ -86,7 +84,7 @@ protected function alterRoutes(RouteCollection $collection) { '_og_user_access_group' => 'administer group', ]) ->setOption('parameters', [ - $entity_type_id => ['type' => 'entity:' . $entity_type_id], + 'group' => ['type' => 'entity:' . $entity_type_id], ]) // As the above parameters doesn't send the entity, // so we will have to use the Route matcher to extract it. @@ -95,36 +93,10 @@ protected function alterRoutes(RouteCollection $collection) { $collection->add($route_name, $route); - // Add the routes defined in the event subscribers. - $this->createRoutesFromEventSubscribers($og_admin_path, $entity_type_id, $collection); - } } - /** - * Add all the OG admin items to the route collection. - * - * @param string $og_admin_path - * The OG admin path. - * @param string $entity_type_id - * The entity type ID. - * @param \Symfony\Component\Routing\RouteCollection $collection - * The route collection object. - */ - protected function createRoutesFromEventSubscribers($og_admin_path, $entity_type_id, RouteCollection $collection) { - $event = new OgAdminRoutesEvent(); - $this->eventDispatcher->dispatch(OgAdminRoutesEventInterface::EVENT_NAME, $event); - - foreach ($event->getRoutes($entity_type_id) as $name => $route_info) { - // Add the parent route. - $parent_route_name = "entity.$entity_type_id.og_admin_routes.$name"; - $parent_path = $og_admin_path . '/' . $route_info['path']; - - $this->addRoute($collection, $parent_route_name, $parent_path, $route_info); - } - } - /** * Helper method to add route to collection. * diff --git a/tests/src/Functional/GroupSubscribeFormatterTest.php b/tests/src/Functional/GroupSubscribeFormatterTest.php index 04cccc0ca..11a092880 100644 --- a/tests/src/Functional/GroupSubscribeFormatterTest.php +++ b/tests/src/Functional/GroupSubscribeFormatterTest.php @@ -19,7 +19,7 @@ class GroupSubscribeFormatterTest extends BrowserTestBase { /** * {@inheritdoc} */ - public static $modules = ['node', 'og']; + public static $modules = ['node', 'og', 'options']; /** * Test entity group. diff --git a/tests/src/Functional/GroupTabTest.php b/tests/src/Functional/GroupTabTest.php deleted file mode 100644 index 2bd2e6912..000000000 --- a/tests/src/Functional/GroupTabTest.php +++ /dev/null @@ -1,103 +0,0 @@ -bundle1 = mb_strtolower($this->randomMachineName()); - $this->bundle2 = mb_strtolower($this->randomMachineName()); - - // Create node types. - $node_type1 = NodeType::create(['type' => $this->bundle1, 'name' => $this->bundle1]); - $node_type1->save(); - - $node_type2 = NodeType::create(['type' => $this->bundle2, 'name' => $this->bundle2]); - $node_type2->save(); - - // Define the first bundle as group. - Og::groupTypeManager()->addGroup('node', $this->bundle1); - - // Create node author user. - $user = $this->createUser(); - - // Create nodes. - $this->group = Node::create([ - 'type' => $this->bundle1, - 'title' => $this->randomString(), - 'uid' => $user->id(), - ]); - $this->group->save(); - - $this->nonGroup = Node::create([ - 'type' => $this->bundle2, - 'title' => $this->randomString(), - 'uid' => $user->id(), - ]); - $this->nonGroup->save(); - - $this->user1 = $this->drupalCreateUser(['administer group']); - } - - /** - * Tests the formatter changes by user and membership. - */ - public function testGroupTab() { - $this->drupalLogin($this->user1); - $this->drupalGet('group/node/' . $this->group->id() . '/admin'); - $this->assertResponse(200); - - $this->drupalGet('group/node/' . $this->nonGroup->id() . '/admin'); - $this->assertResponse(403); - } - -} diff --git a/tests/src/Kernel/Access/AccessByOgMembershipTest.php b/tests/src/Kernel/Access/AccessByOgMembershipTest.php index 37c50ead3..0594d0a07 100644 --- a/tests/src/Kernel/Access/AccessByOgMembershipTest.php +++ b/tests/src/Kernel/Access/AccessByOgMembershipTest.php @@ -36,6 +36,7 @@ class AccessByOgMembershipTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Access/OgAccessHookTest.php b/tests/src/Kernel/Access/OgAccessHookTest.php index 275770da7..799e05088 100644 --- a/tests/src/Kernel/Access/OgAccessHookTest.php +++ b/tests/src/Kernel/Access/OgAccessHookTest.php @@ -33,6 +33,7 @@ class OgAccessHookTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Access/OgEntityAccessTest.php b/tests/src/Kernel/Access/OgEntityAccessTest.php index b26be94a0..ddd86d6e7 100644 --- a/tests/src/Kernel/Access/OgEntityAccessTest.php +++ b/tests/src/Kernel/Access/OgEntityAccessTest.php @@ -25,6 +25,7 @@ class OgEntityAccessTest extends KernelTestBase { 'user', 'field', 'og', + 'options', 'entity_test', ]; diff --git a/tests/src/Kernel/Access/OgGroupContentOperationAccessTest.php b/tests/src/Kernel/Access/OgGroupContentOperationAccessTest.php index 5a8573817..d25ec5135 100644 --- a/tests/src/Kernel/Access/OgGroupContentOperationAccessTest.php +++ b/tests/src/Kernel/Access/OgGroupContentOperationAccessTest.php @@ -32,6 +32,7 @@ class OgGroupContentOperationAccessTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Action/ActionTestBase.php b/tests/src/Kernel/Action/ActionTestBase.php index adc59c38a..1190f71b7 100644 --- a/tests/src/Kernel/Action/ActionTestBase.php +++ b/tests/src/Kernel/Action/ActionTestBase.php @@ -31,7 +31,7 @@ abstract class ActionTestBase extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['node', 'og', 'system', 'user']; + public static $modules = ['node', 'og', 'system', 'user', 'options']; /** * An array of test users. diff --git a/tests/src/Kernel/Console/DrupalConsoleAddFieldTest.php b/tests/src/Kernel/Console/DrupalConsoleAddFieldTest.php index 99ae6e631..de0471227 100644 --- a/tests/src/Kernel/Console/DrupalConsoleAddFieldTest.php +++ b/tests/src/Kernel/Console/DrupalConsoleAddFieldTest.php @@ -24,6 +24,7 @@ class DrupalConsoleAddFieldTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/DefaultRoleEventIntegrationTest.php b/tests/src/Kernel/DefaultRoleEventIntegrationTest.php index 5f316e0e8..545de2ebf 100644 --- a/tests/src/Kernel/DefaultRoleEventIntegrationTest.php +++ b/tests/src/Kernel/DefaultRoleEventIntegrationTest.php @@ -18,7 +18,14 @@ class DefaultRoleEventIntegrationTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['entity_test', 'og', 'system', 'user', 'field']; + public static $modules = [ + 'entity_test', + 'og', + 'system', + 'user', + 'field', + 'options', + ]; /** * The Symfony event dispatcher. diff --git a/tests/src/Kernel/Entity/CacheInvalidationOnGroupChangeTest.php b/tests/src/Kernel/Entity/CacheInvalidationOnGroupChangeTest.php index 2546c5f97..307c4a733 100644 --- a/tests/src/Kernel/Entity/CacheInvalidationOnGroupChangeTest.php +++ b/tests/src/Kernel/Entity/CacheInvalidationOnGroupChangeTest.php @@ -24,6 +24,7 @@ class CacheInvalidationOnGroupChangeTest extends KernelTestBase { 'og', 'system', 'user', + 'options', ]; /** diff --git a/tests/src/Kernel/Entity/EntityCreateAccessTest.php b/tests/src/Kernel/Entity/EntityCreateAccessTest.php index 0c919b11a..38f4ce611 100644 --- a/tests/src/Kernel/Entity/EntityCreateAccessTest.php +++ b/tests/src/Kernel/Entity/EntityCreateAccessTest.php @@ -31,6 +31,7 @@ class EntityCreateAccessTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Entity/GetBundleByBundleTest.php b/tests/src/Kernel/Entity/GetBundleByBundleTest.php index 24ed030ed..d89e83614 100644 --- a/tests/src/Kernel/Entity/GetBundleByBundleTest.php +++ b/tests/src/Kernel/Entity/GetBundleByBundleTest.php @@ -24,6 +24,7 @@ class GetBundleByBundleTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Entity/GetGroupContentTest.php b/tests/src/Kernel/Entity/GetGroupContentTest.php index c360b7440..b35cd0f58 100644 --- a/tests/src/Kernel/Entity/GetGroupContentTest.php +++ b/tests/src/Kernel/Entity/GetGroupContentTest.php @@ -26,6 +26,7 @@ class GetGroupContentTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Entity/GetMembershipsTest.php b/tests/src/Kernel/Entity/GetMembershipsTest.php index b8e813337..58088897d 100644 --- a/tests/src/Kernel/Entity/GetMembershipsTest.php +++ b/tests/src/Kernel/Entity/GetMembershipsTest.php @@ -28,6 +28,7 @@ class GetMembershipsTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Entity/GetUserGroupsTest.php b/tests/src/Kernel/Entity/GetUserGroupsTest.php index d183fdb74..543c35791 100644 --- a/tests/src/Kernel/Entity/GetUserGroupsTest.php +++ b/tests/src/Kernel/Entity/GetUserGroupsTest.php @@ -26,6 +26,7 @@ class GetUserGroupsTest extends KernelTestBase { 'user', 'field', 'og', + 'options', 'entity_test', ]; diff --git a/tests/src/Kernel/Entity/GroupAudienceTest.php b/tests/src/Kernel/Entity/GroupAudienceTest.php index d1132d337..de907ca91 100644 --- a/tests/src/Kernel/Entity/GroupAudienceTest.php +++ b/tests/src/Kernel/Entity/GroupAudienceTest.php @@ -29,6 +29,7 @@ class GroupAudienceTest extends KernelTestBase { 'entity_test', 'field', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Entity/GroupMembershipManagerTest.php b/tests/src/Kernel/Entity/GroupMembershipManagerTest.php index 705b81918..fb0bc4a9f 100644 --- a/tests/src/Kernel/Entity/GroupMembershipManagerTest.php +++ b/tests/src/Kernel/Entity/GroupMembershipManagerTest.php @@ -29,6 +29,7 @@ class GroupMembershipManagerTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Entity/GroupTypeTest.php b/tests/src/Kernel/Entity/GroupTypeTest.php index c3f16144e..eed80eb73 100644 --- a/tests/src/Kernel/Entity/GroupTypeTest.php +++ b/tests/src/Kernel/Entity/GroupTypeTest.php @@ -15,7 +15,7 @@ class GroupTypeTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['field', 'node', 'og', 'system', 'user']; + public static $modules = ['field', 'node', 'og', 'options', 'system', 'user']; /** * The group type manager. diff --git a/tests/src/Kernel/Entity/OgMembershipRoleReferenceTest.php b/tests/src/Kernel/Entity/OgMembershipRoleReferenceTest.php index 98a669fc0..019c70cdd 100644 --- a/tests/src/Kernel/Entity/OgMembershipRoleReferenceTest.php +++ b/tests/src/Kernel/Entity/OgMembershipRoleReferenceTest.php @@ -20,11 +20,12 @@ class OgMembershipRoleReferenceTest extends KernelTestBase { * {@inheritdoc} */ public static $modules = [ - 'og', 'field', 'node', - 'user', + 'og', + 'options', 'system', + 'user', ]; /** diff --git a/tests/src/Kernel/Entity/OgMembershipTest.php b/tests/src/Kernel/Entity/OgMembershipTest.php index e238d2528..e97725faf 100644 --- a/tests/src/Kernel/Entity/OgMembershipTest.php +++ b/tests/src/Kernel/Entity/OgMembershipTest.php @@ -30,6 +30,7 @@ class OgMembershipTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Entity/OgRoleTest.php b/tests/src/Kernel/Entity/OgRoleTest.php index 728e90444..932495d20 100644 --- a/tests/src/Kernel/Entity/OgRoleTest.php +++ b/tests/src/Kernel/Entity/OgRoleTest.php @@ -24,6 +24,7 @@ class OgRoleTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/Entity/OgStandardReferenceItemTest.php b/tests/src/Kernel/Entity/OgStandardReferenceItemTest.php index 7e336ad35..3ed486df0 100644 --- a/tests/src/Kernel/Entity/OgStandardReferenceItemTest.php +++ b/tests/src/Kernel/Entity/OgStandardReferenceItemTest.php @@ -18,7 +18,14 @@ class OgStandardReferenceItemTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['user', 'entity_test', 'field', 'og', 'system']; + public static $modules = [ + 'user', + 'entity_test', + 'field', + 'og', + 'options', + 'system', + ]; protected $bundles; protected $fieldName; diff --git a/tests/src/Kernel/Entity/ReferenceStringIdTest.php b/tests/src/Kernel/Entity/ReferenceStringIdTest.php index d7575dd0e..3ea0c4b5e 100644 --- a/tests/src/Kernel/Entity/ReferenceStringIdTest.php +++ b/tests/src/Kernel/Entity/ReferenceStringIdTest.php @@ -17,7 +17,14 @@ class ReferenceStringIdTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['user', 'entity_test', 'field', 'og', 'system']; + public static $modules = [ + 'user', + 'entity_test', + 'field', + 'og', + 'options', + 'system', + ]; /** * Array of test bundles. The first is a group, the second group content. diff --git a/tests/src/Kernel/Entity/SelectionHandlerTest.php b/tests/src/Kernel/Entity/SelectionHandlerTest.php index 5e84de5d4..76cbc7035 100644 --- a/tests/src/Kernel/Entity/SelectionHandlerTest.php +++ b/tests/src/Kernel/Entity/SelectionHandlerTest.php @@ -34,6 +34,7 @@ class SelectionHandlerTest extends KernelTestBase { 'entity_reference', 'node', 'og', + 'options', ]; /** diff --git a/tests/src/Kernel/EntityReference/Views/OgStandardReferenceRelationshipTest.php b/tests/src/Kernel/EntityReference/Views/OgStandardReferenceRelationshipTest.php index 03beeacfa..1a12e55d3 100644 --- a/tests/src/Kernel/EntityReference/Views/OgStandardReferenceRelationshipTest.php +++ b/tests/src/Kernel/EntityReference/Views/OgStandardReferenceRelationshipTest.php @@ -43,6 +43,7 @@ class OgStandardReferenceRelationshipTest extends ViewsKernelTestBase { 'views', 'og_standard_reference_test_views', 'og', + 'options', ]; /** diff --git a/tests/src/Kernel/Field/AudienceFieldFormatterTest.php b/tests/src/Kernel/Field/AudienceFieldFormatterTest.php index 1b95a78db..d485441e8 100644 --- a/tests/src/Kernel/Field/AudienceFieldFormatterTest.php +++ b/tests/src/Kernel/Field/AudienceFieldFormatterTest.php @@ -15,7 +15,7 @@ class AudienceFieldFormatterTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['field', 'og']; + public static $modules = ['field', 'og', 'options']; /** * Testing og_field_formatter_info_alter(). diff --git a/tests/src/Kernel/Form/GroupSubscribeFormTest.php b/tests/src/Kernel/Form/GroupSubscribeFormTest.php index 6b0fccafd..5b85cde8e 100644 --- a/tests/src/Kernel/Form/GroupSubscribeFormTest.php +++ b/tests/src/Kernel/Form/GroupSubscribeFormTest.php @@ -29,6 +29,7 @@ class GroupSubscribeFormTest extends KernelTestBase { 'field', 'node', 'og', + 'options', ]; /** diff --git a/tests/src/Kernel/GroupManagerSubscriptionTest.php b/tests/src/Kernel/GroupManagerSubscriptionTest.php index aa932451d..24949d904 100644 --- a/tests/src/Kernel/GroupManagerSubscriptionTest.php +++ b/tests/src/Kernel/GroupManagerSubscriptionTest.php @@ -26,6 +26,7 @@ class GroupManagerSubscriptionTest extends KernelTestBase { 'og_test', 'system', 'user', + 'options', ]; /** diff --git a/tests/src/Kernel/GroupTypeConditionTest.php b/tests/src/Kernel/GroupTypeConditionTest.php index ff2c52320..8306f394a 100644 --- a/tests/src/Kernel/GroupTypeConditionTest.php +++ b/tests/src/Kernel/GroupTypeConditionTest.php @@ -22,6 +22,7 @@ class GroupTypeConditionTest extends KernelTestBase { 'field', 'node', 'og', + 'options', 'system', 'user', ]; diff --git a/tests/src/Kernel/OgDeleteOrphansTest.php b/tests/src/Kernel/OgDeleteOrphansTest.php index 17fa75f3c..0ba6342f6 100644 --- a/tests/src/Kernel/OgDeleteOrphansTest.php +++ b/tests/src/Kernel/OgDeleteOrphansTest.php @@ -26,6 +26,7 @@ class OgDeleteOrphansTest extends KernelTestBase { 'entity_reference', 'node', 'og', + 'options', ]; /** diff --git a/tests/src/Kernel/OgRoleManagerTest.php b/tests/src/Kernel/OgRoleManagerTest.php index f226fe7a4..2d8378b94 100644 --- a/tests/src/Kernel/OgRoleManagerTest.php +++ b/tests/src/Kernel/OgRoleManagerTest.php @@ -23,6 +23,7 @@ class OgRoleManagerTest extends KernelTestBase { 'field', 'node', 'og', + 'options', ]; /** diff --git a/tests/src/Kernel/PermissionEventTest.php b/tests/src/Kernel/PermissionEventTest.php index c9a0ea94c..05089d1ec 100644 --- a/tests/src/Kernel/PermissionEventTest.php +++ b/tests/src/Kernel/PermissionEventTest.php @@ -28,6 +28,7 @@ class PermissionEventTest extends KernelTestBase { 'og', 'system', 'user', + 'options', ]; /** diff --git a/tests/src/Kernel/SelectionHandlerSettingsSchemaTest.php b/tests/src/Kernel/SelectionHandlerSettingsSchemaTest.php index c1effe04c..8d1e38da3 100644 --- a/tests/src/Kernel/SelectionHandlerSettingsSchemaTest.php +++ b/tests/src/Kernel/SelectionHandlerSettingsSchemaTest.php @@ -22,6 +22,7 @@ class SelectionHandlerSettingsSchemaTest extends KernelTestBase { 'og_ui', 'system', 'user', + 'options', ]; /** diff --git a/tests/src/Kernel/Views/OgAdminMembersViewTest.php b/tests/src/Kernel/Views/OgAdminMembersViewTest.php index 0a41fbc16..277848675 100644 --- a/tests/src/Kernel/Views/OgAdminMembersViewTest.php +++ b/tests/src/Kernel/Views/OgAdminMembersViewTest.php @@ -27,6 +27,7 @@ class OgAdminMembersViewTest extends ViewsKernelTestBase { 'field', 'node', 'og', + 'options', 'views', ]; diff --git a/tests/src/Unit/OgAdminRoutesControllerTest.php b/tests/src/Unit/OgAdminRoutesControllerTest.php index ddd303058..7768a7b60 100644 --- a/tests/src/Unit/OgAdminRoutesControllerTest.php +++ b/tests/src/Unit/OgAdminRoutesControllerTest.php @@ -2,17 +2,13 @@ namespace Drupal\Tests\og\Unit; -use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher; use Drupal\Core\Access\AccessManagerInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; use Drupal\og\Controller\OgAdminRoutesController; -use Drupal\og\Event\OgAdminRoutesEvent; -use Drupal\og\Event\OgAdminRoutesEventInterface; use Drupal\Tests\UnitTestCase; -use Prophecy\Argument; use Symfony\Component\Routing\Route; /** @@ -51,13 +47,6 @@ class OgAdminRoutesControllerTest extends UnitTestCase { */ protected $routeMatch; - /** - * The event dispatcher service. - * - * @var \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher|\Prophecy\Prophecy\ObjectProphecy - */ - protected $eventDispatcher; - /** * The group entity. * @@ -65,13 +54,6 @@ class OgAdminRoutesControllerTest extends UnitTestCase { */ protected $group; - /** - * The OG admin route event. - * - * @var \Drupal\og\Event\OgAdminRoutesEvent - */ - protected $event; - /** * The entity type ID of the group entity. * @@ -79,13 +61,6 @@ class OgAdminRoutesControllerTest extends UnitTestCase { */ protected $entityTypeId; - /** - * The routes info as returned from the event subscribers. - * - * @var array - */ - protected $routesInfo; - /** * The Url object. * @@ -109,25 +84,11 @@ public function setUp() { $this->routeMatch = $this->prophesize(RouteMatchInterface::class); $this->group = $this->prophesize(EntityInterface::class); - $this->event = $this->prophesize(OgAdminRoutesEvent::class); - $this->eventDispatcher = $this->prophesize(ContainerAwareEventDispatcher::class); $this->route = $this->prophesize(Route::class); $this->entityTypeId = $this->randomMachineName(); $this->entityId = rand(20, 30); $this->url = $this->prophesize(Url::class); - $this->routesInfo = [ - $this->randomMachineName() => [ - 'title' => $this->randomMachineName(), - 'description' => $this->randomMachineName(), - ], - - $this->randomMachineName() => [ - 'title' => $this->randomMachineName(), - 'description' => $this->randomMachineName(), - ], - ]; - $this ->routeMatch ->getRouteObject() @@ -142,7 +103,7 @@ public function setUp() { $this ->routeMatch - ->getParameter($parameter_name) + ->getParameter('group') ->willReturn($this->group->reveal()); $this @@ -155,18 +116,6 @@ public function setUp() { ->id() ->willReturn($this->entityId); - $this - ->eventDispatcher - ->dispatch(OgAdminRoutesEventInterface::EVENT_NAME, Argument::type(OgAdminRoutesEvent::class)) - ->willReturn($this->event->reveal()) - ->shouldBeCalled(); - - $this - ->event - ->getRoutes($this->entityTypeId) - ->willReturn($this->routesInfo) - ->shouldBeCalled(); - // Set the container for the string translation service. $translation = $this->getStringTranslationStub(); $container = new ContainerBuilder(); @@ -194,8 +143,8 @@ public function testRoutesWithAccess() { $result = $this->getRenderElementResult(TRUE); foreach ($result['og_admin_routes']['#content'] as $key => $value) { - $this->assertEquals($this->routesInfo[$key]['title'], $value['title']); - $this->assertEquals($this->routesInfo[$key]['description'], $value['description']); + $this->assertEquals('Members', $value['title']); + $this->assertEquals('Manage members', $value['description']); } } @@ -210,16 +159,15 @@ public function testRoutesWithAccess() { * The render array. */ protected function getRenderElementResult($allow_access) { - $parameters = [$this->entityTypeId => $this->entityId]; - foreach (array_keys($this->routesInfo) as $name) { - $route_name = "entity.{$this->entityTypeId}.og_admin_routes.$name"; - $this - ->accessManager - ->checkNamedRoute($route_name, $parameters) - ->willReturn($allow_access); - } + $parameters = ['entity_type_id' => $this->entityTypeId, 'group' => $this->entityId]; + + $route_name = "og_admin.members"; + $this + ->accessManager + ->checkNamedRoute($route_name, $parameters) + ->willReturn($allow_access); - $og_admin_routes_controller = new OgAdminRoutesController($this->eventDispatcher->reveal(), $this->accessManager->reveal()); + $og_admin_routes_controller = new OgAdminRoutesController($this->accessManager->reveal()); return $og_admin_routes_controller->overview($this->routeMatch->reveal()); } diff --git a/tests/src/Unit/OgLocalTaskTest.php b/tests/src/Unit/OgLocalTaskTest.php index e7dd53142..f5d130152 100644 --- a/tests/src/Unit/OgLocalTaskTest.php +++ b/tests/src/Unit/OgLocalTaskTest.php @@ -75,7 +75,7 @@ public function testGetDerivativeDefinitions() { ->willReturn($group_map); foreach (array_keys($group_map) as $entity_type_id) { - $route_name = "entity.$entity_type_id.og_admin_routes"; + $route_name = "entity.$entity_type_id.og_admin"; $this ->routeProvider diff --git a/tests/src/Unit/SubscriptionControllerTest.php b/tests/src/Unit/SubscriptionControllerTest.php index db2e2e67f..9688a11e8 100644 --- a/tests/src/Unit/SubscriptionControllerTest.php +++ b/tests/src/Unit/SubscriptionControllerTest.php @@ -13,6 +13,7 @@ use Drupal\og\OgMembershipInterface; use Drupal\Tests\UnitTestCase; use Drupal\user\EntityOwnerInterface; +use Drupal\Core\Messenger\MessengerInterface; /** * Tests the subscription controller. @@ -71,6 +72,13 @@ class SubscriptionControllerTest extends UnitTestCase { */ protected $user; + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + /** * {@inheritdoc} */ @@ -82,13 +90,14 @@ public function setUp() { $this->ogMembership = $this->prophesize(OgMembershipInterface::class); $this->url = $this->prophesize(Url::class); $this->user = $this->prophesize(AccountInterface::class); - + $this->messenger = $this->prophesize(MessengerInterface::class); // Set the container for the string translation service. $container = new ContainerBuilder(); $container->set('current_user', $this->user->reveal()); $container->set('entity.form_builder', $this->entityFormBuilder->reveal()); $container->set('og.membership_manager', $this->membershipManager->reveal()); $container->set('string_translation', $this->getStringTranslationStub()); + $container->set('messenger', $this->messenger->reveal()); \Drupal::setContainer($container); } @@ -255,21 +264,8 @@ public function testGroupManager($state) { * Invoke the unsubscribe method. */ protected function unsubscribe() { - $controller = new SubscriptionController($this->ogAccess->reveal()); + $controller = new SubscriptionController($this->ogAccess->reveal(), $this->messenger->reveal()); $controller->unsubscribe($this->group->reveal()); } } - -// @todo Delete after https://www.drupal.org/node/1858196 is in. -namespace Drupal\og\Controller; - -if (!function_exists('drupal_set_message')) { - - /** - * Mocking for drupal_set_message(). - */ - function drupal_set_message() { - } - -}