diff --git a/.travis.yml b/.travis.yml
index f121c28ab..c9a113c28 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,30 +2,27 @@ language: php
sudo: true
php:
- - 5.6
- - 7.0
- 7.1
- 7.2
+ - 7.3
env:
global:
- COMPOSER_MEMORY_LIMIT=2G
matrix:
- - TEST_SUITE=8.5.x
- - TEST_SUITE=8.6.x
+ - TEST_SUITE=8.7.x
+ - TEST_SUITE=8.8.x
- TEST_SUITE=PHP_CodeSniffer
# Only run the coding standards check once.
matrix:
exclude:
- - php: 5.6
- env: TEST_SUITE=PHP_CodeSniffer
- - php: 7.0
- env: TEST_SUITE=PHP_CodeSniffer
- php: 7.1
env: TEST_SUITE=PHP_CodeSniffer
+ - php: 7.2
+ env: TEST_SUITE=PHP_CodeSniffer
allow_failures:
- - env: TEST_SUITE=8.6.x
+ - env: TEST_SUITE=8.8.x
mysql:
database: og
@@ -44,9 +41,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/composer.json b/composer.json
index 86b654504..2719c766d 100644
--- a/composer.json
+++ b/composer.json
@@ -10,9 +10,10 @@
"source": "https://cgit.drupalcode.org/og"
},
"require": {
- "drupal/core": "~8.5"
+ "drupal/core": "~8.7"
},
"require-dev": {
"drupal/coder": "~8.2"
- }
+ },
+ "minimum-stability": "RC"
}
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..cf158dae4 100644
--- a/config/optional/views.view.og_members_overview.yml
+++ b/config/optional/views.view.og_members_overview.yml
@@ -3,6 +3,7 @@ status: true
dependencies:
module:
- og
+ - options
- user
_core:
default_config_hash: RnChWtrF4u0Q13iQMFypL0Uz6IsB4NY6ZTk_LorSbJg
@@ -22,8 +23,9 @@ display:
position: 0
display_options:
access:
- type: none
- options: { }
+ type: perm
+ options:
+ perm: 'access user profiles'
cache:
type: none
options: { }
@@ -46,12 +48,17 @@ display:
sort_asc_label: Asc
sort_desc_label: Desc
pager:
- type: mini
+ type: full
options:
- items_per_page: 10
+ items_per_page: 50
offset: 0
id: 0
total_pages: null
+ tags:
+ previous: ‹‹
+ next: ››
+ first: '« First'
+ last: 'Last »'
expose:
items_per_page: false
items_per_page_label: 'Items per page'
@@ -60,9 +67,7 @@ display:
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
- tags:
- previous: ‹‹
- next: ››
+ quantity: 9
style:
type: table
options:
@@ -356,10 +361,10 @@ display:
entity_type: og_membership
entity_field: state
plugin_id: field
- roles:
- id: roles
+ roles_target_id:
+ id: roles_target_id
table: og_membership__roles
- field: roles
+ field: roles_target_id
relationship: none
group_type: group
admin_label: ''
@@ -421,6 +426,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.install b/og.install
index d657d4709..80ad218cf 100644
--- a/og.install
+++ b/og.install
@@ -28,9 +28,14 @@ function og_update_8001(&$sandbox) {
\Drupal::entityDefinitionUpdateManager()->installFieldStorageDefinition('entity_bundle', 'og_membership', 'og', $storage_definition);
$sandbox['#finished'] = 0;
+ $sandbox['batch_size'] = 500;
$sandbox['current'] = 0;
$sandbox['total'] = $storage->getQuery()->count()->execute();
- $sandbox['batch_size'] = 500;
+
+ if (!$sandbox['total']) {
+ $sandbox['#finished'] = 1;
+ return t('No OG memberships found.');
+ }
}
// Update the existing memberships to include the group bundle ID.
diff --git a/og.links.action.yml b/og.links.action.yml
new file mode 100644
index 000000000..c70c87d8a
--- /dev/null
+++ b/og.links.action.yml
@@ -0,0 +1,8 @@
+og_membership.add:
+ deriver: \Drupal\og\Plugin\Derivative\OgActionLink
+
+og_membership.type_add:
+ route_name: og_membership.type_add
+ title: 'Add membership type'
+ appears_on:
+ - entity.og_membership_type.collection
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..2b013250d 100644
--- a/og.links.task.yml
+++ b/og.links.task.yml
@@ -1,2 +1,15 @@
og.og_admin_routes:
deriver: \Drupal\og\Plugin\Derivative\OgLocalTask
+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
diff --git a/og.module b/og.module
index d4bf89737..28da5518f 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;
@@ -53,11 +54,7 @@ function og_entity_insert(EntityInterface $entity) {
// Other modules that implement hook_entity_insert() might already have
// created a membership ahead of us.
- if (!Og::getMembership($entity, $entity->getOwner(), [
- OgMembershipInterface::STATE_ACTIVE,
- OgMembershipInterface::STATE_PENDING,
- OgMembershipInterface::STATE_BLOCKED,
- ])) {
+ if (!Og::getMembership($entity, $entity->getOwner(), OgMembershipInterface::ALL_STATES)) {
$membership = Og::createMembership($entity, $entity->getOwner());
$membership->save();
}
@@ -177,6 +174,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().
*/
@@ -378,6 +416,11 @@ function og_og_role_insert(OgRoleInterface $role) {
return;
}
+ // Do not create config while config import is in progress.
+ if (\Drupal::isConfigSyncing()) {
+ return;
+ }
+
$add_id = 'og_membership_add_single_role_action.' . $role->getName();
if (!Action::load($add_id)) {
$action = Action::create([
diff --git a/og.routing.yml b/og.routing.yml
index 06a78558e..df629a9e6 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,99 @@ 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'
diff --git a/og.services.yml b/og.services.yml
index 34a31b286..b1d13e80c 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']
@@ -42,10 +47,10 @@ services:
arguments: ['@entity_type.manager', '@entity_field.manager']
og.group_type_manager:
class: Drupal\og\GroupTypeManager
- arguments: ['@config.factory', '@entity_type.manager', '@entity_type.bundle.info', '@event_dispatcher', '@state', '@og.permission_manager', '@og.role_manager', '@router.builder', '@og.group_audience_helper']
+ arguments: ['@config.factory', '@entity_type.manager', '@entity_type.bundle.info', '@event_dispatcher', '@cache.data', '@og.permission_manager', '@og.role_manager', '@router.builder', '@og.group_audience_helper']
og.membership_manager:
class: Drupal\og\MembershipManager
- arguments: ['@entity_type.manager', '@og.group_audience_helper']
+ arguments: ['@entity_type.manager', '@og.group_audience_helper', '@database']
og.permission_manager:
class: Drupal\og\PermissionManager
arguments: ['@event_dispatcher']
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/src/Form/AdminSettingsForm.php b/og_ui/src/Form/AdminSettingsForm.php
index ef8d86cf0..57ba41f70 100644
--- a/og_ui/src/Form/AdminSettingsForm.php
+++ b/og_ui/src/Form/AdminSettingsForm.php
@@ -145,7 +145,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('og.settings')
->set('group_manager_full_access', $form_state->getValue('og_group_manager_full_access'))
->set('node_access_strict', $form_state->getValue('og_node_access_strict'))
- ->set('use_queue', $form_state->getValue('og_use_queue'))
->set('delete_orphans', $form_state->getValue('og_delete_orphans'))
->set('delete_orphans_plugin_id', $form_state->getValue('og_delete_orphans_plugin_id'))
->save();
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..22f7195b8 100644
--- a/src/Access/GroupCheck.php
+++ b/src/Access/GroupCheck.php
@@ -83,6 +83,9 @@ public function access(AccountInterface $user, Route $route, RouteMatchInterface
if (!$group = $route_match->getParameter($parameter_name)) {
return AccessResult::forbidden();
}
+ if (is_numeric($group)) {
+ $group = $this->entityTypeManager->getStorage($parameter_name)->load($group);
+ }
$entity_type_id = $group->getEntityTypeId();
}
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/Cache/Context/OgMembershipStateCacheContext.php b/src/Cache/Context/OgMembershipStateCacheContext.php
index a3b0b7f6c..c3c1b36a9 100644
--- a/src/Cache/Context/OgMembershipStateCacheContext.php
+++ b/src/Cache/Context/OgMembershipStateCacheContext.php
@@ -76,14 +76,8 @@ public function getContext() {
return self::NO_CONTEXT;
}
- $states = [
- OgMembershipInterface::STATE_ACTIVE,
- OgMembershipInterface::STATE_PENDING,
- OgMembershipInterface::STATE_BLOCKED,
- ];
-
/** @var \Drupal\og\OgMembershipInterface $membership */
- $membership = $this->membershipManager->getMembership($group, $this->user, $states);
+ $membership = $this->membershipManager->getMembership($group, $this->user, OgMembershipInterface::ALL_STATES);
return $membership ? $membership->getState() : self::NO_CONTEXT;
}
diff --git a/src/Controller/OgAdminMembersController.php b/src/Controller/OgAdminMembersController.php
index 9eb723aea..0fc4b48bf 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,82 @@ 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_type_id = $route_match->getRouteObject()->getOption('_og_entity_type_id');
+ $group = $route_match->getParameter($group_type_id);
$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) {
+ $entity_type_id = $route_match->getRouteObject()
+ ->getOption('_og_entity_type_id');
+
+ $group = $route_match->getParameter($entity_type_id);
+
+ $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/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..fca8dd4e2 100644
--- a/src/Controller/SubscriptionController.php
+++ b/src/Controller/SubscriptionController.php
@@ -5,7 +5,9 @@
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
+use Drupal\og\Og;
use Drupal\og\OgAccessInterface;
use Drupal\og\OgMembershipInterface;
use Drupal\og\OgMembershipTypeInterface;
@@ -14,14 +16,12 @@
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
-use Drupal\og\Og;
/**
* Controller for OG subscription routes.
*/
class SubscriptionController extends ControllerBase {
-
/**
* OG access service.
*
@@ -29,14 +29,24 @@ class SubscriptionController extends ControllerBase {
*/
protected $ogAccess;
+ /**
+ * The messenger service.
+ *
+ * @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 service.
*/
- public function __construct(OgAccessInterface $og_access) {
+ public function __construct(OgAccessInterface $og_access, MessengerInterface $messenger) {
$this->ogAccess = $og_access;
+ $this->messenger = $messenger;
}
/**
@@ -44,7 +54,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 +66,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 +94,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 +130,7 @@ public function subscribe($entity_type_id, EntityInterface $group, OgMembershipT
}
if ($redirect) {
- drupal_set_message($message, 'warning');
+ $this->messenger->addMessage($message, 'warning');
return new RedirectResponse($group->toUrl()->setAbsolute(TRUE)->toString());
}
@@ -127,7 +138,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;
@@ -146,13 +157,7 @@ public function subscribe($entity_type_id, EntityInterface $group, OgMembershipT
public function unsubscribe(ContentEntityInterface $group) {
$user = $this->currentUser();
- $states = [
- OgMembershipInterface::STATE_ACTIVE,
- OgMembershipInterface::STATE_PENDING,
- OgMembershipInterface::STATE_BLOCKED,
- ];
-
- if (!$membership = Og::getMembership($group, $user, $states)) {
+ if (!$membership = Og::getMembership($group, $user, OgMembershipInterface::ALL_STATES)) {
// User is not a member.
throw new AccessDeniedHttpException();
}
@@ -164,7 +169,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..30fa58d4e 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'))
@@ -463,7 +517,7 @@ public function preSave(EntityStorageInterface $storage) {
->execute();
if ($count) {
- throw new \LogicException(sprintf('An OG membership already exists for group of entity-type %s and ID: %s', $entity_type_id, $this->getGroup()->id()));
+ throw new \LogicException(sprintf('An OG membership already exists for uid %s in group of entity-type %s and ID: %s', $this->get('uid')->target_id, $entity_type_id, $this->getGroup()->id()));
}
parent::preSave($storage);
diff --git a/src/Entity/OgMembershipType.php b/src/Entity/OgMembershipType.php
index 821b29966..295c80f2f 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,31 @@
* @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"
+ * },
+ * config_export = {
+ * "type",
+ * "name",
+ * "description"
+ * },
+ * 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 +64,40 @@ public function id() {
return $this->type;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function save() {
+ $status = parent::save();
+
+ if (\Drupal::isConfigSyncing()) {
+ // Do not create config while config import is in progress.
+ return;
+ }
+
+ 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 && !\Drupal::isConfigSyncing()) {
+ throw new \Exception("The default OG membership type cannot be deleted.");
+ }
+ parent::delete();
+ }
+
}
diff --git a/src/Event/OgAdminRoutesEvent.php b/src/Event/OgAdminRoutesEvent.php
index 575ccf90c..4ae012172 100644
--- a/src/Event/OgAdminRoutesEvent.php
+++ b/src/Event/OgAdminRoutesEvent.php
@@ -2,6 +2,7 @@
namespace Drupal\og\Event;
+use Drupal\Component\Utility\NestedArray;
use Symfony\Component\EventDispatcher\Event;
/**
@@ -40,8 +41,9 @@ public function getRoutes($entity_type_id) {
$routes_info[$name] = $route_info;
- // Add default values.
- $routes_info[$name] += [
+ // Add default values. NestedArray::mergeDeep allows deep data to not be
+ // overwritten with the defaults.
+ $defaults = [
'description' => '',
'requirements' => [
@@ -64,6 +66,8 @@ public function getRoutes($entity_type_id) {
'_title' => $route_info['title'],
],
];
+
+ $routes_info[$name] = NestedArray::mergeDeep($defaults, $routes_info[$name]);
}
return $routes_info;
diff --git a/src/EventSubscriber/OgEventSubscriber.php b/src/EventSubscriber/OgEventSubscriber.php
index 3463cd3f2..b0f495890 100644
--- a/src/EventSubscriber/OgEventSubscriber.php
+++ b/src/EventSubscriber/OgEventSubscriber.php
@@ -357,12 +357,21 @@ public function provideOgAdminRoutes(OgAdminRoutesEventInterface $event) {
'description' => 'Manage members',
'path' => 'members',
'requirements' => [
- '_og_user_access_group' => 'administer group',
+ '_og_user_access_group' => 'administer group|manage members',
// Views module must be enabled.
'_module_dependencies' => 'views',
],
];
+ $routes_info['add_membership_page'] = [
+ 'controller' => '\Drupal\og\Controller\OgAdminMembersController::addPage',
+ 'title' => 'Add member',
+ 'path' => 'members/add',
+ 'requirements' => [
+ '_og_membership_add_access' => 'TRUE',
+ ],
+ ];
+
$event->setRoutesInfo($routes_info);
}
diff --git a/src/Form/GroupSubscribeForm.php b/src/Form/GroupSubscribeForm.php
index d77fc69a4..03c16e8bd 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')
);
}
@@ -234,8 +252,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
/** @var EntityInterface $group */
$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);
+ $message = $membership->isActive() ? $this->t('You are now subscribed to the group.') : $this->t('Your subscription request has been sent.');
+ $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..10767431a 100644
--- a/src/Form/GroupUnsubscribeConfirmForm.php
+++ b/src/Form/GroupUnsubscribeConfirmForm.php
@@ -4,6 +4,7 @@
use Drupal\Core\Entity\ContentEntityDeleteForm;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Url;
/**
@@ -11,6 +12,8 @@
*/
class GroupUnsubscribeConfirmForm extends ContentEntityDeleteForm {
+ use MessengerTrait;
+
/**
* {@inheritdoc}
*/
@@ -62,7 +65,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/GroupTypeManager.php b/src/GroupTypeManager.php
index adc596830..5fc6cec1b 100644
--- a/src/GroupTypeManager.php
+++ b/src/GroupTypeManager.php
@@ -6,7 +6,7 @@
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
-use Drupal\Core\State\StateInterface;
+use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\og\Event\GroupCreationEvent;
use Drupal\og\Event\GroupCreationEventInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -57,11 +57,11 @@ class GroupTypeManager implements GroupTypeManagerInterface {
protected $eventDispatcher;
/**
- * The state service.
+ * The cache backend.
*
- * @var \Drupal\Core\State\StateInterface
+ * @var \Drupal\Core\Cache\CacheBackendInterface
*/
- protected $state;
+ protected $cache;
/**
* The OG permission manager.
@@ -142,8 +142,8 @@ class GroupTypeManager implements GroupTypeManagerInterface {
* The service providing information about bundles.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
- * @param \Drupal\Core\State\StateInterface $state
- * The state service.
+ * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+ * The cache backend.
* @param \Drupal\og\PermissionManagerInterface $permission_manager
* The OG permission manager.
* @param \Drupal\og\OgRoleManagerInterface $og_role_manager
@@ -153,11 +153,11 @@ class GroupTypeManager implements GroupTypeManagerInterface {
* @param \Drupal\og\OgGroupAudienceHelperInterface $group_audience_helper
* The OG group audience helper.
*/
- public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EventDispatcherInterface $event_dispatcher, StateInterface $state, PermissionManagerInterface $permission_manager, OgRoleManagerInterface $og_role_manager, RouteBuilderInterface $route_builder, OgGroupAudienceHelperInterface $group_audience_helper) {
+ public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EventDispatcherInterface $event_dispatcher, CacheBackendInterface $cache, PermissionManagerInterface $permission_manager, OgRoleManagerInterface $og_role_manager, RouteBuilderInterface $route_builder, OgGroupAudienceHelperInterface $group_audience_helper) {
$this->configFactory = $config_factory;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->eventDispatcher = $event_dispatcher;
- $this->state = $state;
+ $this->cache = $cache;
$this->permissionManager = $permission_manager;
$this->ogRoleManager = $og_role_manager;
$this->routeBuilder = $route_builder;
@@ -251,6 +251,9 @@ public function getGroupBundleIdsByGroupContentBundle($group_content_entity_type
*/
public function getGroupContentBundleIdsByGroupBundle($group_entity_type_id, $group_bundle_id) {
$group_relation_map = $this->getGroupRelationMap();
+ if (!is_array($group_relation_map) && property_exists($group_relation_map,'data')) {
+ $group_relation_map = $group_relation_map->data;
+ }
return isset($group_relation_map[$group_entity_type_id][$group_bundle_id]) ? $group_relation_map[$group_entity_type_id][$group_bundle_id] : [];
}
@@ -331,7 +334,7 @@ public function resetGroupMap() {
*/
public function resetGroupRelationMap() {
$this->groupRelationMap = [];
- $this->state->delete(self::GROUP_RELATION_MAP_CACHE_KEY);
+ $this->cache->delete(self::GROUP_RELATION_MAP_CACHE_KEY);
}
/**
@@ -370,7 +373,7 @@ protected function refreshGroupMap() {
*/
protected function refreshGroupRelationMap() {
// Retrieve a cached version of the map if it exists.
- if ($group_relation_map = $this->state->get(self::GROUP_RELATION_MAP_CACHE_KEY)) {
+ if ($group_relation_map = $this->cache->get(self::GROUP_RELATION_MAP_CACHE_KEY)) {
$this->groupRelationMap = $group_relation_map;
return;
}
@@ -395,7 +398,7 @@ protected function refreshGroupRelationMap() {
}
}
// Cache the map.
- $this->state->set(self::GROUP_RELATION_MAP_CACHE_KEY, $this->groupRelationMap);
+ $this->cache->set(self::GROUP_RELATION_MAP_CACHE_KEY, $this->groupRelationMap);
}
}
diff --git a/src/MembershipManager.php b/src/MembershipManager.php
index 7677d35f4..a8743634e 100644
--- a/src/MembershipManager.php
+++ b/src/MembershipManager.php
@@ -2,6 +2,7 @@
namespace Drupal\og;
+use Drupal\Core\Database\Connection;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -36,6 +37,13 @@ class MembershipManager implements MembershipManagerInterface {
*/
protected $groupAudienceHelper;
+ /**
+ * The database service.
+ *
+ * @var \Drupal\Core\Database\Connection
+ */
+ protected $database;
+
/**
* Constructs a MembershipManager object.
*
@@ -43,10 +51,13 @@ class MembershipManager implements MembershipManagerInterface {
* The entity type manager.
* @param \Drupal\og\OgGroupAudienceHelperInterface $group_audience_helper
* The OG group audience helper.
+ * @param \Drupal\Core\Database\Connection $database
+ * The database service.
*/
- public function __construct(EntityTypeManagerInterface $entity_type_manager, OgGroupAudienceHelperInterface $group_audience_helper) {
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, OgGroupAudienceHelperInterface $group_audience_helper, Connection $database) {
$this->entityTypeManager = $entity_type_manager;
$this->groupAudienceHelper = $group_audience_helper;
+ $this->database = $database;
}
/**
@@ -81,54 +92,122 @@ public function getUserGroups(AccountInterface $user, array $states = [OgMembers
* {@inheritdoc}
*/
public function getMemberships(AccountInterface $user, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
- // Get a string identifier of the states, so we can retrieve it from cache.
- sort($states);
- $states_identifier = implode('|', array_unique($states));
+ // When an empty array is passed, retrieve memberships with all possible
+ // states.
+ $states = $this->prepareConditionArray($states, OgMembership::ALL_STATES);
$identifier = [
__METHOD__,
'user',
$user->id(),
- $states_identifier,
+ implode('|', $states),
];
$identifier = implode(':', $identifier);
- // Return cached result if it exists.
- if (isset($this->cache[$identifier])) {
- return $this->cache[$identifier];
+ // Use cached result if it exists.
+ if (!isset($this->cache[$identifier])) {
+ $query = $this->entityTypeManager
+ ->getStorage('og_membership')
+ ->getQuery()
+ ->condition('uid', $user->id())
+ ->condition('state', $states, 'IN');
+
+ $this->cache[$identifier] = $query->execute();
+ }
+
+ return $this->loadMemberships($this->cache[$identifier]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMembership(EntityInterface $group, AccountInterface $user, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
+ foreach ($this->getMemberships($user, $states) as $membership) {
+ if ($membership->getGroupEntityType() === $group->getEntityTypeId() && $membership->getGroupId() === $group->id()) {
+ return $membership;
+ }
}
+ // No membership matches the request.
+ return NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroupMembershipCount(EntityInterface $group, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
$query = $this->entityTypeManager
->getStorage('og_membership')
->getQuery()
- ->condition('uid', $user->id());
+ ->condition('entity_id', $group->id());
if ($states) {
$query->condition('state', $states, 'IN');
}
- $results = $query->execute();
-
- /** @var \Drupal\og\Entity\OgMembership[] $memberships */
- $this->cache[$identifier] = $this->entityTypeManager
- ->getStorage('og_membership')
- ->loadMultiple($results);
-
- return $this->cache[$identifier];
+ $query->count();
+ return $query->execute();
}
/**
* {@inheritdoc}
*/
- public function getMembership(EntityInterface $group, AccountInterface $user, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
- foreach ($this->getMemberships($user, $states) as $membership) {
- if ($membership->getGroupEntityType() === $group->getEntityTypeId() && $membership->getGroupId() === $group->id()) {
- return $membership;
+ public function getGroupMembershipIdsByRoleNames(EntityInterface $group, array $role_names, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
+ if (empty($role_names)) {
+ throw new \InvalidArgumentException('The array of role names should not be empty.');
+ }
+
+ // In case the 'member' role is one of the requested roles, we just need to
+ // return all memberships. We can safely ignore all other roles.
+ $retrieve_all_memberships = FALSE;
+ if (in_array(OgRoleInterface::AUTHENTICATED, $role_names)) {
+ $retrieve_all_memberships = TRUE;
+ $role_names = [OgRoleInterface::AUTHENTICATED];
+ }
+
+ $role_names = $this->prepareConditionArray($role_names);
+ $states = $this->prepareConditionArray($states, OgMembership::ALL_STATES);
+
+ $identifier = [
+ __METHOD__,
+ $group->id(),
+ implode('|', $role_names),
+ implode('|', $states),
+ ];
+ $identifier = implode(':', $identifier);
+
+ // Only query the database if no cached result exists.
+ if (!isset($this->cache[$identifier])) {
+ $entity_type_id = $group->getEntityTypeId();
+
+ $query = $this->entityTypeManager
+ ->getStorage('og_membership')
+ ->getQuery()
+ ->condition('entity_type', $entity_type_id)
+ ->condition('entity_id', $group->id())
+ ->condition('state', $states, 'IN');
+
+ if (!$retrieve_all_memberships) {
+ $bundle_id = $group->bundle();
+ $role_ids = array_map(function ($role_name) use ($entity_type_id, $bundle_id) {
+ return implode('-', [$entity_type_id, $bundle_id, $role_name]);
+ }, $role_names);
+
+ $query->condition('roles', $role_ids, 'IN');
}
+
+ $this->cache[$identifier] = $query->execute();
}
- // No membership matches the request.
- return NULL;
+ return $this->cache[$identifier];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroupMembershipsByRoleNames(EntityInterface $group, array $role_names, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
+ $ids = $this->getGroupMembershipIdsByRoleNames($group, $role_names, $states);
+ return $this->loadMemberships($ids);
}
/**
@@ -188,7 +267,9 @@ public function getGroupIds(EntityInterface $entity, $group_type_id = NULL, $gro
// Compile a list of group target IDs.
$target_ids = array_map(function ($value) {
- return $value['target_id'];
+ if (isset($value['target_id'])) {
+ return $value['target_id'];
+ }
}, $entity->get($field->getName())->getValue());
if (empty($target_ids)) {
@@ -322,4 +403,69 @@ public function reset() {
$this->cache = [];
}
+ /**
+ * Prepares a conditional array for use in a cache identifier and query.
+ *
+ * This will filter out any duplicate values from the array and sort the
+ * values so that a consistent cache identifier can be generated. Optionally
+ * it can substitute an empty array with a default value.
+ *
+ * @param array $value
+ * The array to prepare.
+ * @param array|null $default
+ * An optional default value to use in case the passed in value is empty. If
+ * set to NULL this will be ignored.
+ *
+ * @return array
+ * The prepared array.
+ */
+ protected function prepareConditionArray(array $value, array $default = NULL) {
+ // Fall back to the default value if the passed in value is empty and a
+ // default value is given.
+ if (empty($value) && $default !== NULL) {
+ $value = $default;
+ }
+ sort($value);
+ return array_unique($value);
+ }
+
+ /**
+ * Returns the full membership entities with the given memberships IDs.
+ *
+ * @param array $ids
+ * The IDs of the memberships to load.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface[]
+ * The membership entities.
+ */
+ protected function loadMemberships(array $ids) {
+ if (empty($ids)) {
+ return [];
+ }
+
+ return $this->entityTypeManager
+ ->getStorage('og_membership')
+ ->loadMultiple($ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroupMemberships(EntityInterface $group, array $states = [OgMembershipInterface::STATE_ACTIVE], $range = NULL) {
+ $query = $this->database->select('og_membership', 'ogm')
+ ->fields('ogm', ['uid'])
+ ->condition('entity_id', $group->id());
+
+ if ($states) {
+ $query->condition('state', $states, 'IN');
+ }
+ if ($range) {
+ $query->range(0, $range);
+ }
+ $result = $query->execute()->fetchAllAssoc('uid', 'PDO::FETCH_ASSOC');
+ $member_ids = array_keys($result);
+ $members = $this->entityTypeManager->getStorage('user')->loadMultiple($member_ids);
+ return $members;
+ }
+
}
diff --git a/src/MembershipManagerInterface.php b/src/MembershipManagerInterface.php
index d117645bb..78d46d70e 100644
--- a/src/MembershipManagerInterface.php
+++ b/src/MembershipManagerInterface.php
@@ -61,13 +61,28 @@ public function getUserGroups(AccountInterface $user, array $states = [OgMembers
* @param \Drupal\Core\Session\AccountInterface $user
* The user to get groups for.
* @param array $states
- * (optional) Array with the state to return. Defaults to active.
+ * (optional) Array with the states to return. Defaults to only returning
+ * active memberships. In order to retrieve all memberships regardless of
+ * state, pass `OgMembershipInterface::ALL_STATES`.
*
* @return \Drupal\og\OgMembershipInterface[]
* An array of OgMembership entities, keyed by ID.
*/
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.
*
@@ -76,7 +91,9 @@ public function getMemberships(AccountInterface $user, array $states = [OgMember
* @param \Drupal\Core\Session\AccountInterface $user
* The user to get the membership for.
* @param array $states
- * (optional) Array with the state to return. Defaults to active.
+ * (optional) Array with the states to return. Defaults to only returning
+ * active memberships. In order to retrieve all memberships regardless of
+ * state, pass `OgMembershipInterface::ALL_STATES`.
*
* @return \Drupal\og\OgMembershipInterface|null
* The OgMembership entity. NULL will be returned if no membership is
@@ -84,6 +101,42 @@ public function getMemberships(AccountInterface $user, array $states = [OgMember
*/
public function getMembership(EntityInterface $group, AccountInterface $user, array $states = [OgMembershipInterface::STATE_ACTIVE]);
+ /**
+ * Returns the membership IDs of the given group filtered by role names.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $group
+ * The group entity for which to return the memberships.
+ * @param array $role_names
+ * An array of role names to filter by. In order to retrieve a list of all
+ * membership IDs, pass `[OgRoleInterface::AUTHENTICATED]`.
+ * @param array $states
+ * (optional) Array with the states to return. Defaults to only returning
+ * active membership IDs. In order to retrieve all membership IDs regardless
+ * of state, pass `OgMembershipInterface::ALL_STATES`.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface[]
+ * The membership entities.
+ */
+ public function getGroupMembershipIdsByRoleNames(EntityInterface $group, array $role_names, array $states = [OgMembershipInterface::STATE_ACTIVE]);
+
+ /**
+ * Returns the memberships of the given group filtered by role name.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $group
+ * The group entity for which to return the memberships.
+ * @param array $role_names
+ * An array of role names to filter by. In order to retrieve a list of all
+ * memberships, pass `[OgRoleInterface::AUTHENTICATED]`.
+ * @param array $states
+ * (optional) Array with the states to return. Defaults to only returning
+ * active memberships. In order to retrieve all memberships regardless of
+ * state, pass `OgMembershipInterface::ALL_STATES`.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface[]
+ * The membership entities.
+ */
+ public function getGroupMembershipsByRoleNames(EntityInterface $group, array $role_names, array $states = [OgMembershipInterface::STATE_ACTIVE]);
+
/**
* Creates an OG membership.
*
@@ -238,4 +291,19 @@ public function isMemberBlocked(EntityInterface $group, AccountInterface $user);
*/
public function reset();
+ /**
+ * Returns all users that are active members of the group.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $group
+ * The group entity.
+ * @param array $states
+ * The member states.
+ * @param int $range
+ * The number of members to get.
+ *
+ * @return array
+ * Group members
+ */
+ public function getGroupMemberships(EntityInterface $group, array $states = [OgMembershipInterface::STATE_ACTIVE], $range = NULL);
+
}
diff --git a/src/Og.php b/src/Og.php
index 1aa2361e5..da330c192 100644
--- a/src/Og.php
+++ b/src/Og.php
@@ -133,9 +133,11 @@ public static function createField($plugin_id, $entity_type, $bundle, array $set
* @param \Drupal\Core\Session\AccountInterface $user
* The user to get groups for.
* @param array $states
- * (optional) Array with the state to return. Defaults to active.
+ * (optional) Array with the states to return. Defaults to only returning
+ * active memberships. In order to retrieve all memberships regardless of
+ * state, pass `OgMembershipInterface::ALL_STATES`.
*
- * @return \Drupal\og\Entity\OgMembership[]
+ * @return \Drupal\og\OgMembershipInterface[]
* An array of OgMembership entities, keyed by ID.
*/
public static function getMemberships(AccountInterface $user, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
@@ -152,9 +154,11 @@ public static function getMemberships(AccountInterface $user, array $states = [O
* @param \Drupal\Core\Session\AccountInterface $user
* The user to get the membership for.
* @param array $states
- * (optional) Array with the state to return. Defaults to active.
+ * (optional) Array with the states to return. Defaults to only returning
+ * active memberships. In order to retrieve all memberships regardless of
+ * state, pass `OgMembershipInterface::ALL_STATES`.
*
- * @return \Drupal\og\Entity\OgMembership|null
+ * @return \Drupal\og\OgMembershipInterface|null
* The OgMembership entity. NULL will be returned if no membership is
* available that matches the passed in $states.
*/
@@ -164,6 +168,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.
*
@@ -333,6 +354,8 @@ public static function invalidateCache() {
static::$cache = [];
// Invalidate the entity property cache.
+ // @todo We should not clear the entity type and field definition caches.
+ // @see https://github.com/Gizra/og/issues/219
\Drupal::entityTypeManager()->clearCachedDefinitions();
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
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/OgMembershipInterface.php b/src/OgMembershipInterface.php
index cecd1808b..2f6a4911f 100644
--- a/src/OgMembershipInterface.php
+++ b/src/OgMembershipInterface.php
@@ -38,6 +38,15 @@ interface OgMembershipInterface extends ContentEntityInterface, EntityOwnerInter
*/
const STATE_BLOCKED = 'blocked';
+ /**
+ * An array containing all possible group membership states.
+ */
+ const ALL_STATES = [
+ self::STATE_ACTIVE,
+ self::STATE_PENDING,
+ self::STATE_BLOCKED,
+ ];
+
/**
* The default group membership type that is the bundle of group membership.
*/
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/Action/AddSingleOgMembershipRole.php b/src/Plugin/Action/AddSingleOgMembershipRole.php
index 0283ca106..67dc79045 100644
--- a/src/Plugin/Action/AddSingleOgMembershipRole.php
+++ b/src/Plugin/Action/AddSingleOgMembershipRole.php
@@ -26,7 +26,7 @@ public function execute(OgMembership $membership = NULL) {
$role_name = $this->configuration['role_name'];
$role_id = implode('-', [
$membership->getGroupEntityType(),
- $membership->getGroup()->bundle(),
+ $membership->getGroupBundle(),
$role_name,
]);
// Only add the role if it is valid and doesn't exist yet.
diff --git a/src/Plugin/Action/RemoveSingleOgMembershipRole.php b/src/Plugin/Action/RemoveSingleOgMembershipRole.php
index c269bc3e2..53c70a6c3 100644
--- a/src/Plugin/Action/RemoveSingleOgMembershipRole.php
+++ b/src/Plugin/Action/RemoveSingleOgMembershipRole.php
@@ -25,7 +25,7 @@ public function execute(OgMembership $membership = NULL) {
$role_name = $this->configuration['role_name'];
$role_id = implode('-', [
$membership->getGroupEntityType(),
- $membership->getGroup()->bundle(),
+ $membership->getGroupBundle(),
$role_name,
]);
// Skip removing the role from the membership if it doesn't have it.
diff --git a/src/Plugin/Condition/GroupType.php b/src/Plugin/Condition/GroupType.php
index f6a3b64bd..a80f9a982 100644
--- a/src/Plugin/Condition/GroupType.php
+++ b/src/Plugin/Condition/GroupType.php
@@ -16,7 +16,7 @@
* @Condition(
* id = "og_group_type",
* label = @Translation("Group type"),
- * context = {
+ * context_definitions = {
* "og" = @ContextDefinition("entity", label = @Translation("Group"))
* }
* )
diff --git a/src/Plugin/Derivative/OgActionLink.php b/src/Plugin/Derivative/OgActionLink.php
new file mode 100644
index 000000000..cd2ca2f73
--- /dev/null
+++ b/src/Plugin/Derivative/OgActionLink.php
@@ -0,0 +1,84 @@
+groupTypeManager = $group_type_manager;
+ $this->routeProvider = $route_provider;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, $base_plugin_id) {
+ return new static(
+ $container->get('og.group_type_manager'),
+ $container->get('router.route_provider')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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.add_membership_page";
+
+ if (!$this->routeProvider->getRoutesByNames([$route_name])) {
+ // Route not found.
+ continue;
+ }
+
+ $derivatives["og_membership.$entity_type_id.add"] = [
+ 'route_name' => $route_name,
+ 'title' => $this->t('Add a member'),
+ 'appears_on' => ["entity.$entity_type_id.og_admin_routes.members"],
+ ];
+ }
+
+ foreach ($derivatives as &$entry) {
+ $entry += $base_plugin_definition;
+ }
+
+ return $derivatives;
+ }
+
+}
diff --git a/src/Plugin/Derivative/OgLocalTask.php b/src/Plugin/Derivative/OgLocalTask.php
index c861065c4..dd1019f80 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;
}
/**
@@ -62,7 +62,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
foreach (array_keys($this->groupTypeManager->getGroupMap()) as $entity_type_id) {
$route_name = "entity.$entity_type_id.og_admin_routes";
- if (!$this->routProvider->getRoutesByNames([$route_name])) {
+ if (!$this->routeProvider->getRoutesByNames([$route_name])) {
// Route not found.
continue;
}
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/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/OgComplexWidgetTest.php b/tests/src/Functional/OgComplexWidgetTest.php
index 1d54bc9d8..c8b565f15 100644
--- a/tests/src/Functional/OgComplexWidgetTest.php
+++ b/tests/src/Functional/OgComplexWidgetTest.php
@@ -7,9 +7,9 @@
use Drupal\node\Entity\Node;
use Drupal\og\Og;
use Drupal\og\OgGroupAudienceHelperInterface;
-use Drupal\simpletest\BrowserTestBase;
-use Drupal\simpletest\ContentTypeCreationTrait;
-use Drupal\simpletest\NodeCreationTrait;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
+use Drupal\Tests\node\Traits\NodeCreationTrait;
/**
* Tests the complex widget.
@@ -88,7 +88,6 @@ public function testFields($field, $field_name) {
$this->assertSession()->statusCodeEquals(200);
// Retrieve the post that was created from the database.
- /** @var QueryInterface $query */
$query = $this->container->get('entity_type.manager')->getStorage('node')->getQuery();
$result = $query
->condition('type', 'post')
@@ -97,7 +96,7 @@ public function testFields($field, $field_name) {
->execute();
$post_nid = reset($result);
- /** @var \Drupal\node\Entity\NodeInterface $post */
+ /** @var \Drupal\node\NodeInterface $post */
$post = Node::load($post_nid);
// Check that the post references the group correctly.
diff --git a/tests/src/Kernel/Access/AccessByOgMembershipTest.php b/tests/src/Kernel/Access/AccessByOgMembershipTest.php
index 37c50ead3..9949d611f 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',
];
@@ -72,7 +73,7 @@ public function setUp() {
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
// Create a user role for a standard authenticated user.
$role = Role::create([
diff --git a/tests/src/Kernel/Access/OgAccessHookTest.php b/tests/src/Kernel/Access/OgAccessHookTest.php
index 275770da7..ad35f4cc4 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',
];
@@ -83,7 +84,7 @@ public function setUp() {
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
// Create two roles: one for normal users, and one for administrators.
foreach (['authenticated', 'administrator'] as $role_id) {
diff --git a/tests/src/Kernel/Access/OgEntityAccessTest.php b/tests/src/Kernel/Access/OgEntityAccessTest.php
index b26be94a0..5446bedec 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',
];
@@ -143,7 +144,7 @@ protected function setUp() {
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
$this->groupBundle = mb_strtolower($this->randomMachineName());
diff --git a/tests/src/Kernel/Access/OgGroupContentOperationAccessTest.php b/tests/src/Kernel/Access/OgGroupContentOperationAccessTest.php
index 5a8573817..fd5cb4659 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',
];
@@ -92,7 +93,7 @@ protected function setUp() {
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
$this->entityTypeManager = $this->container->get('entity_type.manager');
@@ -174,7 +175,7 @@ protected function setUp() {
}
// Create a 'blocked' user. This user is identical to the normal
- // 'authenticated' member, except that she has the 'blocked' state.
+ // 'authenticated' member, except that they have the 'blocked' state.
$this->users['blocked'] = User::create(['name' => $this->randomString()]);
$this->users['blocked']->save();
$this->createOgMembership($this->group, $this->users['blocked'], NULL, OgMembershipInterface::STATE_BLOCKED);
@@ -231,10 +232,12 @@ protected function setUp() {
case 'comment':
$values = [
+ 'field_name' => $this->randomString(),
'subject' => 'subscribe',
'comment_type' => $bundle_id,
'entity_id' => $this->group->id(),
'entity_type' => 'entity_test',
+ 'field_name' => 'an_imaginary_field',
OgGroupAudienceHelperInterface::DEFAULT_FIELD => [['target_id' => $this->group->id()]],
];
break;
diff --git a/tests/src/Kernel/Action/ActionTestBase.php b/tests/src/Kernel/Action/ActionTestBase.php
index adc59c38a..2ecf08f9a 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.
@@ -84,7 +84,7 @@ public function setUp() {
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
- $this->installSchema('system', ['queue', 'sequences']);
+ $this->installSchema('system', ['sequences']);
$this->membershipManager = $this->container->get('og.membership_manager');
$this->groupTypeManager = $this->container->get('og.group_type_manager');
diff --git a/tests/src/Kernel/Console/DrupalConsoleAddFieldTest.php b/tests/src/Kernel/Console/DrupalConsoleAddFieldTest.php
index 99ae6e631..ba046c9c3 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',
];
@@ -37,7 +38,7 @@ public function setUp() {
$this->installConfig(['og']);
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
NodeType::create([
'name' => $this->randomString(),
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..25bf9caf5 100644
--- a/tests/src/Kernel/Entity/EntityCreateAccessTest.php
+++ b/tests/src/Kernel/Entity/EntityCreateAccessTest.php
@@ -7,8 +7,8 @@
use Drupal\node\Entity\NodeType;
use Drupal\og\Og;
use Drupal\og\OgGroupAudienceHelperInterface;
-use Drupal\simpletest\ContentTypeCreationTrait;
-use Drupal\simpletest\NodeCreationTrait;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
+use Drupal\Tests\node\Traits\NodeCreationTrait;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
@@ -31,6 +31,7 @@ class EntityCreateAccessTest extends KernelTestBase {
'field',
'node',
'og',
+ 'options',
'system',
'user',
];
@@ -59,7 +60,7 @@ public function setUp() {
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
// Create a "group" node type and turn it into a group type.
$this->groupType = NodeType::create([
diff --git a/tests/src/Kernel/Entity/GetBundleByBundleTest.php b/tests/src/Kernel/Entity/GetBundleByBundleTest.php
index 24ed030ed..27a6b7cd2 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',
];
@@ -60,7 +61,7 @@ protected function setUp() {
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
$this->groupTypeManager = $this->container->get('og.group_type_manager');
diff --git a/tests/src/Kernel/Entity/GetGroupContentTest.php b/tests/src/Kernel/Entity/GetGroupContentTest.php
index c360b7440..e4f63652a 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',
];
@@ -55,7 +56,7 @@ protected function setUp() {
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = $this->container->get('entity_type.manager');
diff --git a/tests/src/Kernel/Entity/GetMembershipsTest.php b/tests/src/Kernel/Entity/GetMembershipsTest.php
index b8e813337..0c75604f1 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',
];
@@ -63,7 +64,7 @@ protected function setUp() {
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
$this->installSchema('user', ['users_data']);
$this->entityTypeManager = $this->container->get('entity_type.manager');
diff --git a/tests/src/Kernel/Entity/GetUserGroupsTest.php b/tests/src/Kernel/Entity/GetUserGroupsTest.php
index d183fdb74..5fcd8f544 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',
];
@@ -88,7 +89,7 @@ protected function setUp() {
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
$this->groupBundle = mb_strtolower($this->randomMachineName());
$this->groupContentBundle = mb_strtolower($this->randomMachineName());
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..b3453ea8f 100644
--- a/tests/src/Kernel/Entity/GroupMembershipManagerTest.php
+++ b/tests/src/Kernel/Entity/GroupMembershipManagerTest.php
@@ -9,8 +9,12 @@
use Drupal\entity_test\Entity\EntityTest;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
+use Drupal\og\Entity\OgRole;
use Drupal\og\Og;
use Drupal\og\OgGroupAudienceHelperInterface;
+use Drupal\og\OgMembershipInterface;
+use Drupal\og\OgRoleInterface;
+use Drupal\Tests\og\Traits\OgMembershipCreationTrait;
use Drupal\user\Entity\User;
/**
@@ -21,6 +25,8 @@
*/
class GroupMembershipManagerTest extends KernelTestBase {
+ use OgMembershipCreationTrait;
+
/**
* {@inheritdoc}
*/
@@ -29,6 +35,7 @@ class GroupMembershipManagerTest extends KernelTestBase {
'field',
'node',
'og',
+ 'options',
'system',
'user',
];
@@ -47,6 +54,13 @@ class GroupMembershipManagerTest extends KernelTestBase {
*/
protected $groupContent;
+ /**
+ * Test users.
+ *
+ * @var \Drupal\user\UserInterface[]
+ */
+ protected $users;
+
/**
* The entity type manager.
*
@@ -54,6 +68,13 @@ class GroupMembershipManagerTest extends KernelTestBase {
*/
protected $entityTypeManager;
+ /**
+ * The membership manager. This is the system under test.
+ *
+ * @var \Drupal\og\MembershipManagerInterface
+ */
+ protected $membershipManager;
+
/**
* {@inheritdoc}
*/
@@ -67,15 +88,17 @@ protected function setUp() {
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
$this->entityTypeManager = $this->container->get('entity_type.manager');
+ $this->membershipManager = $this->container->get('og.membership_manager');
$this->groups = [];
// Create group admin user.
$group_admin = User::create(['name' => $this->randomString()]);
$group_admin->save();
+ $this->users[0] = $group_admin;
// Create four groups of two different entity types.
for ($i = 0; $i < 2; $i++) {
@@ -156,10 +179,7 @@ protected function setUp() {
* @dataProvider groupContentProvider
*/
public function testGetGroupIds($group_type_id, $group_bundle, array $expected) {
- /** @var \Drupal\og\MembershipManagerInterface $membership_manager */
- $membership_manager = \Drupal::service('og.membership_manager');
-
- $result = $membership_manager->getGroupIds($this->groupContent, $group_type_id, $group_bundle);
+ $result = $this->membershipManager->getGroupIds($this->groupContent, $group_type_id, $group_bundle);
// Check that the correct number of results is returned.
$this->assertEquals(count($expected, COUNT_RECURSIVE), count($result, COUNT_RECURSIVE));
@@ -181,8 +201,6 @@ public function testGetGroupIds($group_type_id, $group_bundle, array $expected)
* @covers ::getGroups
*/
public function testStaticCache() {
- /** @var \Drupal\og\MembershipManagerInterface $membership_manager */
- $membership_manager = \Drupal::service('og.membership_manager');
$bundle_rev = mb_strtolower($this->randomMachineName());
$bundle_with_bundle = mb_strtolower($this->randomMachineName());
EntityTestBundle::create(['id' => $bundle_with_bundle, 'label' => $bundle_with_bundle])->save();
@@ -222,11 +240,11 @@ public function testStaticCache() {
// ensure that the next assertions are addressing the proper issue.
$this->assertEquals($group_content_rev->id(), $group_content_with_bundle->id());
- $group_content_rev_group = $membership_manager->getGroups($group_content_rev);
+ $group_content_rev_group = $this->membershipManager->getGroups($group_content_rev);
/** @var \Drupal\node\NodeInterface $group */
$group = reset($group_content_rev_group['node']);
$this->assertEquals($this->groups['node'][0]->id(), $group->id());
- $group_content_with_bundle_group = $membership_manager->getGroups($group_content_with_bundle);
+ $group_content_with_bundle_group = $this->membershipManager->getGroups($group_content_with_bundle);
$group = reset($group_content_with_bundle_group['node']);
$this->assertEquals($this->groups['node'][1]->id(), $group->id());
}
@@ -247,10 +265,7 @@ public function testStaticCache() {
* @dataProvider groupContentProvider
*/
public function testGetGroups($group_type_id, $group_bundle, array $expected) {
- /** @var \Drupal\og\MembershipManagerInterface $membership_manager */
- $membership_manager = \Drupal::service('og.membership_manager');
-
- $result = $membership_manager->getGroups($this->groupContent, $group_type_id, $group_bundle);
+ $result = $this->membershipManager->getGroups($this->groupContent, $group_type_id, $group_bundle);
// Check that the correct number of results is returned.
$this->assertEquals(count($expected, COUNT_RECURSIVE), count($result, COUNT_RECURSIVE));
@@ -289,10 +304,7 @@ public function testGetGroups($group_type_id, $group_bundle, array $expected) {
* @dataProvider groupContentProvider
*/
public function testGetGroupCount($group_type_id, $group_bundle, array $expected) {
- /** @var \Drupal\og\MembershipManagerInterface $membership_manager */
- $membership_manager = \Drupal::service('og.membership_manager');
-
- $result = $membership_manager->getGroupCount($this->groupContent, $group_type_id, $group_bundle);
+ $result = $this->membershipManager->getGroupCount($this->groupContent, $group_type_id, $group_bundle);
// Check that the correct results is returned.
$this->assertEquals(count($expected, COUNT_RECURSIVE) - count($expected), $result);
@@ -318,4 +330,366 @@ public function groupContentProvider() {
];
}
+ /**
+ * Tests retrieval of group memberships filtered by role names.
+ *
+ * @covers ::getGroupMembershipsByRoleNames
+ */
+ public function testGetGroupMembershipsByRoleNames() {
+ $retrieve_membership_owner_id = function (OgMembershipInterface $membership) {
+ return $membership->getOwnerId();
+ };
+ $this->doTestGetGroupMembershipsByRoleNames('getGroupMembershipsByRoleNames', $retrieve_membership_owner_id);
+ }
+
+ /**
+ * Tests retrieval of group membership IDs filtered by role names.
+ *
+ * @covers ::getGroupMembershipIdsByRoleNames
+ */
+ public function testGetGroupMembershipIdsByRoleNames() {
+ $membership_storage = $this->container->get('entity_type.manager')->getStorage('og_membership');
+ $retrieve_membership_owner_id = function ($membership_id) use ($membership_storage) {
+ /** @var \Drupal\og\OgMembershipInterface $membership */
+ $membership = $membership_storage->load($membership_id);
+ return $membership->getOwnerId();
+ };
+ $this->doTestGetGroupMembershipsByRoleNames('getGroupMembershipIdsByRoleNames', $retrieve_membership_owner_id);
+ }
+
+ /**
+ * Tests retrieval of group memberships or their IDs filtered by role names.
+ *
+ * Contains the actual test logic of ::testGetGroupMembershipsByRoleNames()
+ * and ::testGetGroupMembershipIdsByRoleNames().
+ *
+ * @param string $method_name
+ * The name of the method under test. Can be one of the following:
+ * - 'getGroupMembershipIdsByRoleNames'
+ * - 'getGroupMembershipsByRoleNames'.
+ * @param callable $retrieve_membership_owner_id
+ * A callable that will retrieve the ID of the owner of the membership or
+ * membership ID.
+ */
+ protected function doTestGetGroupMembershipsByRoleNames($method_name, callable $retrieve_membership_owner_id) {
+ $this->createTestMemberships();
+
+ // Check that an exception is thrown if no role names are passed.
+ try {
+ $this->membershipManager->$method_name($this->groups['node'][0], []);
+ $this->fail('MembershipManager::getGroupsMembershipsByRoleNames() throws an exception when called without passing any role names.');
+ }
+ catch (\InvalidArgumentException $e) {
+ // Expected result.
+ }
+
+ // Define a test matrix to iterate over. We're not using a data provider
+ // because the large number of test cases would slow down the test too much.
+ // The test matrix has the following structure:
+ // @code
+ // [
+ // // The machine name of the group entity type being tested.
+ // {entity_type_id} => [
+ // // The key of the test group as created in ::setUp().
+ // {group_key} => [ //
+ // // The roles being passed to the method.
+ // 'roles' => [{role_id}],
+ // // The membership states being passed to the method.
+ // 'states' => [{state_id}],
+ // // The memberships that should be returned by the method.
+ // 'expected_memberships' => [{expected_membership_id}],
+ // ],
+ // ],
+ // ];
+ // @endcode
+ $matrix = [
+ 'node' => [
+ 0 => [
+ // All memberships with all possible states. The authenticated role
+ // covers all memberships.
+ [
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ 'states' => OgMembershipInterface::ALL_STATES,
+ 'expected_memberships' => [0, 1, 4, 7],
+ ],
+ // All memberships with all possible states, by passing an empty
+ // states array, and passing all defined roles.
+ [
+ 'roles' => [
+ OgRoleInterface::AUTHENTICATED,
+ OgRoleInterface::ADMINISTRATOR,
+ 'moderator',
+ ],
+ 'states' => [],
+ 'expected_memberships' => [0, 1, 4, 7],
+ ],
+ // Pending members.
+ [
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ 'states' => [OgMembershipInterface::STATE_PENDING],
+ 'expected_memberships' => [4],
+ ],
+ ],
+ 1 => [
+ // All administrators.
+ [
+ 'roles' => [OgRoleInterface::ADMINISTRATOR],
+ 'states' => [],
+ 'expected_memberships' => [2, 6],
+ ],
+ // Pending administrators.
+ [
+ 'roles' => [OgRoleInterface::ADMINISTRATOR],
+ 'states' => [OgMembershipInterface::STATE_PENDING],
+ 'expected_memberships' => [2],
+ ],
+ // Blocked administrators. There are none.
+ [
+ 'roles' => [OgRoleInterface::ADMINISTRATOR],
+ 'states' => [OgMembershipInterface::STATE_BLOCKED],
+ 'expected_memberships' => [],
+ ],
+ // Pending and blocked administrators and moderators. Should be the
+ // same result as the pending administrators, since there are no
+ // moderators or blocked users.
+ [
+ 'roles' => [OgRoleInterface::ADMINISTRATOR, 'moderator'],
+ 'states' => [
+ OgMembershipInterface::STATE_BLOCKED,
+ OgMembershipInterface::STATE_PENDING,
+ ],
+ 'expected_memberships' => [2],
+ ],
+ // Switch the order of the arguments, this should not affect the
+ // result.
+ [
+ 'roles' => ['moderator', OgRoleInterface::ADMINISTRATOR],
+ 'states' => [
+ OgMembershipInterface::STATE_PENDING,
+ OgMembershipInterface::STATE_BLOCKED,
+ ],
+ 'expected_memberships' => [2],
+ ],
+ // There are no pending or blocked moderators.
+ [
+ 'roles' => ['moderator'],
+ 'states' => [
+ OgMembershipInterface::STATE_BLOCKED,
+ OgMembershipInterface::STATE_PENDING,
+ ],
+ 'expected_memberships' => [],
+ ],
+ ],
+ ],
+ 'entity_test' => [
+ 0 => [
+ // The first test entity group doesn't have any moderators or admins.
+ // Check that duplicated array values doesn't affect the result.
+ [
+ 'roles' => [
+ 'moderator',
+ OgRoleInterface::ADMINISTRATOR,
+ 'moderator',
+ 'moderator',
+ OgRoleInterface::ADMINISTRATOR,
+ ],
+ 'states' => [
+ OgMembershipInterface::STATE_ACTIVE,
+ OgMembershipInterface::STATE_BLOCKED,
+ OgMembershipInterface::STATE_PENDING,
+ OgMembershipInterface::STATE_PENDING,
+ OgMembershipInterface::STATE_BLOCKED,
+ OgMembershipInterface::STATE_ACTIVE,
+ ],
+ 'expected_memberships' => [],
+ ],
+ ],
+ // Check active members.
+ [
+ 'roles' => [
+ OgRoleInterface::AUTHENTICATED,
+ ],
+ 'states' => [
+ OgMembershipInterface::STATE_ACTIVE,
+ ],
+ 'expected_memberships' => [0, 3],
+ ],
+ 1 => [
+ // There are two blocked users in the second test entity group.
+ [
+ 'roles' => [
+ OgRoleInterface::AUTHENTICATED,
+ OgRoleInterface::ADMINISTRATOR,
+ 'moderator',
+ ],
+ 'states' => [
+ OgMembershipInterface::STATE_BLOCKED,
+ ],
+ 'expected_memberships' => [4, 7],
+ ],
+ // There is one blocked moderator.
+ [
+ 'roles' => [
+ OgRoleInterface::ADMINISTRATOR,
+ 'moderator',
+ ],
+ 'states' => [
+ OgMembershipInterface::STATE_BLOCKED,
+ ],
+ 'expected_memberships' => [4],
+ ],
+ ],
+ ],
+ ];
+
+ foreach ($matrix as $entity_type_id => $groups) {
+ foreach ($groups as $group_key => $test_cases) {
+ foreach ($test_cases as $test_case) {
+ $group = $this->groups[$entity_type_id][$group_key];
+ $role_names = $test_case['roles'];
+ $states = $test_case['states'];
+ $expected_memberships = $test_case['expected_memberships'];
+
+ $actual_memberships = $this->membershipManager->$method_name($group, $role_names, $states);
+ $this->assertSameSize($expected_memberships, $actual_memberships);
+
+ foreach ($expected_memberships as $expected_membership_key) {
+ $expected_user_id = $this->users[$expected_membership_key]->id();
+ foreach ($actual_memberships as $actual_membership) {
+ if ($retrieve_membership_owner_id($actual_membership) == $expected_user_id) {
+ // Match found.
+ continue 2;
+ }
+ }
+ // The expected membership was not returned. Fail the test.
+ $this->fail("The membership for user $expected_membership_key is present in the result.");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a number of users that are members of the test groups.
+ */
+ protected function createTestMemberships() {
+ // Create a 'moderator' role in each of the test group types.
+ foreach (['node', 'entity_test'] as $entity_type_id) {
+ for ($i = 0; $i < 2; $i++) {
+ $bundle = "${entity_type_id}_$i";
+ $og_role = OgRole::create();
+ $og_role
+ ->setName('moderator')
+ ->setGroupType($entity_type_id)
+ ->setGroupBundle($bundle)
+ ->save();
+ }
+ }
+
+ // Create test users with different membership states in the various groups.
+ // Note that the group admin (test user 0) is also a member of all groups.
+ $matrix = [
+ // A user which is an active member of the first node group.
+ 1 => [
+ 'node' => [
+ 0 => [
+ 'state' => OgMembershipInterface::STATE_ACTIVE,
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ ],
+ ],
+ ],
+
+ // A user which is a pending administrator of the second node group.
+ 2 => [
+ 'node' => [
+ 1 => [
+ 'state' => OgMembershipInterface::STATE_PENDING,
+ 'roles' => [OgRoleInterface::ADMINISTRATOR],
+ ],
+ ],
+ ],
+
+ // A user which is an active member of both test entity groups.
+ 3 => [
+ 'entity_test' => [
+ 0 => [
+ 'state' => OgMembershipInterface::STATE_ACTIVE,
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ ],
+ 1 => [
+ 'state' => OgMembershipInterface::STATE_ACTIVE,
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ ],
+ ],
+ ],
+
+ // A user which is a pending member of the first node group and a blocked
+ // moderator in the second test entity group.
+ 4 => [
+ 'node' => [
+ 0 => [
+ 'state' => OgMembershipInterface::STATE_PENDING,
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ ],
+ ],
+ 'entity_test' => [
+ 1 => [
+ 'state' => OgMembershipInterface::STATE_BLOCKED,
+ 'roles' => ['moderator'],
+ ],
+ ],
+ ],
+
+ // A user which is not subscribed to any of the groups.
+ 5 => [],
+
+ // A user which is both an administrator and a moderator in the second
+ // node group.
+ 6 => [
+ 'node' => [
+ 1 => [
+ 'state' => OgMembershipInterface::STATE_ACTIVE,
+ 'roles' => [OgRoleInterface::ADMINISTRATOR, 'moderator'],
+ ],
+ ],
+ ],
+
+ // A troll who is banned everywhere.
+ 7 => [
+ 'node' => [
+ 0 => [
+ 'state' => OgMembershipInterface::STATE_BLOCKED,
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ ],
+ 1 => [
+ 'state' => OgMembershipInterface::STATE_BLOCKED,
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ ],
+ ],
+ 'entity_test' => [
+ 0 => [
+ 'state' => OgMembershipInterface::STATE_BLOCKED,
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ ],
+ 1 => [
+ 'state' => OgMembershipInterface::STATE_BLOCKED,
+ 'roles' => [OgRoleInterface::AUTHENTICATED],
+ ],
+ ],
+ ],
+ ];
+
+ foreach ($matrix as $user_key => $entity_types) {
+ $user = User::create(['name' => $this->randomString()]);
+ $user->save();
+ $this->users[$user_key] = $user;
+ foreach ($entity_types as $entity_type_id => $groups) {
+ foreach ($groups as $group_key => $membership_info) {
+ $group = $this->groups[$entity_type_id][$group_key];
+ $this->createOgMembership($group, $user, $membership_info['roles'], $membership_info['state']);
+ }
+ }
+ }
+ }
+
}
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..66cd39034 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',
];
/**
@@ -59,7 +60,7 @@ protected function setUp() {
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
// Create a "group" node type and turn it into a group type.
$this->groupBundle = mb_strtolower($this->randomMachineName());
diff --git a/tests/src/Kernel/Entity/OgMembershipTest.php b/tests/src/Kernel/Entity/OgMembershipTest.php
index e238d2528..c00bbf54c 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',
];
@@ -73,7 +74,7 @@ protected function setUp() {
$this->installEntitySchema('entity_test');
$this->installEntitySchema('node');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
$this->entityTypeManager = $this->container->get('entity_type.manager');
$storage = $this->entityTypeManager->getStorage('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..4c40a8272 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;
@@ -35,7 +42,7 @@ protected function setUp() {
$this->installEntitySchema('entity_test');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
// Create several bundles.
for ($i = 0; $i <= 2; $i++) {
diff --git a/tests/src/Kernel/Entity/ReferenceStringIdTest.php b/tests/src/Kernel/Entity/ReferenceStringIdTest.php
index d7575dd0e..4c2a8df94 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.
@@ -51,7 +58,7 @@ protected function setUp() {
$this->installEntitySchema('entity_test_string_id');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
// Create two bundles, one will serve as group, the other as group content.
for ($i = 0; $i < 2; $i++) {
diff --git a/tests/src/Kernel/Entity/SelectionHandlerTest.php b/tests/src/Kernel/Entity/SelectionHandlerTest.php
index 5e84de5d4..1c2a081fe 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',
];
/**
@@ -82,7 +83,7 @@ protected function setUp() {
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
// Setting up variables.
$this->groupBundle = mb_strtolower($this->randomMachineName());
@@ -133,14 +134,15 @@ public function testSelectionHandler() {
/**
* Testing OG selection handler results.
*
- * We need to verify that each user get the groups he own in the normal widget
- * and the other users group's in the other groups widget and vice versa.
+ * We need to verify that each user gets the groups they own in the normal
+ * widget and the other users' groups in the other groups widget and vice
+ * versa.
*/
public function testSelectionHandlerResults() {
$user1_groups = $this->createGroups(2, $this->user1);
$user2_groups = $this->createGroups(2, $this->user2);
- // Checking that the user get the groups he mange.
+ // Check that users get the groups they manage.
$this->setCurrentAccount($this->user1);
$groups = $this->selectionHandler->getReferenceableEntities();
$this->assertEquals($user1_groups, array_keys($groups[$this->groupBundle]));
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..174e4005f 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',
];
/**
@@ -69,7 +70,7 @@ protected function setUp() {
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
- $this->installSchema('system', 'sequences');
+ $this->installSchema('system', ['sequences']);
// Create 3 test bundles and declare them as groups.
$bundle_names = [];
diff --git a/tests/src/Kernel/GroupManagerSubscriptionTest.php b/tests/src/Kernel/GroupManagerSubscriptionTest.php
index aa932451d..efdf4039d 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',
];
/**
@@ -63,7 +64,7 @@ protected function setUp() {
$this->installEntitySchema('node');
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
- $this->installSchema('system', ['queue', 'sequences']);
+ $this->installSchema('system', ['sequences']);
// Create a group type.
NodeType::create([
diff --git a/tests/src/Kernel/GroupTypeConditionTest.php b/tests/src/Kernel/GroupTypeConditionTest.php
index ff2c52320..7ee4361c6 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',
];
@@ -61,7 +62,7 @@ protected function setUp() {
$this->installEntitySchema('entity_test');
$this->installEntitySchema('node');
$this->installEntitySchema('user');
- $this->installSchema('system', ['queue', 'sequences']);
+ $this->installSchema('system', ['sequences']);
// Create three test groups of different types.
for ($i = 0; $i < 2; $i++) {
diff --git a/tests/src/Kernel/OgDeleteOrphansTest.php b/tests/src/Kernel/OgDeleteOrphansTest.php
index 17fa75f3c..fd80646c2 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',
];
/**
@@ -60,8 +61,8 @@ public function setUp() {
$this->installEntitySchema('og_membership');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
- $this->installSchema('node', 'node_access');
- $this->installSchema('system', ['queue', 'sequences']);
+ $this->installSchema('node', ['node_access']);
+ $this->installSchema('system', ['sequences']);
/** @var \Drupal\og\OgDeleteOrphansPluginManager $plugin_manager */
$plugin_manager = \Drupal::service('plugin.manager.og.delete_orphans');
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..5565b070b 100644
--- a/tests/src/Kernel/Views/OgAdminMembersViewTest.php
+++ b/tests/src/Kernel/Views/OgAdminMembersViewTest.php
@@ -5,7 +5,7 @@
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\og\Og;
-use Drupal\simpletest\UserCreationTrait;
+use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Views;
@@ -27,6 +27,7 @@ class OgAdminMembersViewTest extends ViewsKernelTestBase {
'field',
'node',
'og',
+ 'options',
'views',
];
diff --git a/tests/src/Unit/CreateMembershipTest.php b/tests/src/Unit/CreateMembershipTest.php
index 3a461c003..dd00b211c 100644
--- a/tests/src/Unit/CreateMembershipTest.php
+++ b/tests/src/Unit/CreateMembershipTest.php
@@ -13,6 +13,7 @@
use Drupal\Tests\UnitTestCase;
use Drupal\user\UserInterface;
use Prophecy\Argument;
+use Drupal\Core\Database\Connection;
/**
* Tests create membership helper function.
@@ -85,6 +86,13 @@ class CreateMembershipTest extends UnitTestCase {
*/
protected $membership;
+ /**
+ * The database service.
+ *
+ * @var \Drupal\Core\Database\Connection
+ */
+ protected $database;
+
/**
* {@inheritdoc}
*/
@@ -98,6 +106,7 @@ public function setUp() {
$this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
$this->entityTypeRepository = $this->prophesize(EntityTypeRepositoryInterface::class);
$this->groupAudienceHelper = $this->prophesize(OgGroupAudienceHelperInterface::class);
+ $this->database = $this->prophesize(Connection::class);
$this->entityTypeManager->getStorage('og_membership')
->willReturn($this->entityStorage->reveal());
@@ -130,6 +139,7 @@ public function setUp() {
$container = new ContainerBuilder();
$container->set('entity_type.manager', $this->entityTypeManager->reveal());
$container->set('entity_type.repository', $this->entityTypeRepository->reveal());
+ $container->set('database', $this->database->reveal());
\Drupal::setContainer($container);
}
@@ -139,7 +149,7 @@ public function setUp() {
* @covers ::createMembership
*/
public function testNewGroup() {
- $membership_manager = new MembershipManager($this->entityTypeManager->reveal(), $this->groupAudienceHelper->reveal());
+ $membership_manager = new MembershipManager($this->entityTypeManager->reveal(), $this->groupAudienceHelper->reveal(), $this->database->reveal());
$membership = $membership_manager->createMembership($this->group->reveal(), $this->user->reveal());
$this->assertInstanceOf(OgMembershipInterface::class, $membership);
}
diff --git a/tests/src/Unit/GroupTypeManagerTest.php b/tests/src/Unit/GroupTypeManagerTest.php
index c00815481..37ebdc1b3 100644
--- a/tests/src/Unit/GroupTypeManagerTest.php
+++ b/tests/src/Unit/GroupTypeManagerTest.php
@@ -2,13 +2,13 @@
namespace Drupal\Tests\og\Unit;
+use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
-use Drupal\Core\State\StateInterface;
use Drupal\og\Event\GroupCreationEvent;
use Drupal\og\Event\GroupCreationEventInterface;
use Drupal\og\GroupTypeManager;
@@ -87,11 +87,11 @@ class GroupTypeManagerTest extends UnitTestCase {
protected $permissionEvent;
/**
- * The state prophecy used in the test.
+ * The cache prophecy used in the test.
*
- * @var \Drupal\Core\State\StateInterface|\Prophecy\Prophecy\ObjectProphecy
+ * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ObjectProphecy
*/
- protected $state;
+ protected $cache;
/**
* The OG permission manager prophecy used in the test.
@@ -135,7 +135,7 @@ public function setUp() {
$this->ogRoleManager = $this->prophesize(OgRoleManagerInterface::class);
$this->permissionEvent = $this->prophesize(PermissionEventInterface::class);
$this->permissionManager = $this->prophesize(PermissionManagerInterface::class);
- $this->state = $this->prophesize(StateInterface::class);
+ $this->cache = $this->prophesize(CacheBackendInterface::class);
$this->routeBuilder = $this->prophesize(RouteBuilderInterface::class);
$this->groupAudienceHelper = $this->prophesize(OgGroupAudienceHelperInterface::class);
}
@@ -331,7 +331,7 @@ protected function createGroupManager() {
$this->entityTypeManager->reveal(),
$this->entityTypeBundleInfo->reveal(),
$this->eventDispatcher->reveal(),
- $this->state->reveal(),
+ $this->cache->reveal(),
$this->permissionManager->reveal(),
$this->ogRoleManager->reveal(),
$this->routeBuilder->reveal(),
diff --git a/tests/src/Unit/SubscriptionControllerTest.php b/tests/src/Unit/SubscriptionControllerTest.php
index db2e2e67f..19b69d9e9 100644
--- a/tests/src/Unit/SubscriptionControllerTest.php
+++ b/tests/src/Unit/SubscriptionControllerTest.php
@@ -5,6 +5,7 @@
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFormBuilderInterface;
+use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\og\Controller\SubscriptionController;
@@ -50,6 +51,13 @@ class SubscriptionControllerTest extends UnitTestCase {
*/
protected $ogAccess;
+ /**
+ * The mocked messenger service.
+ *
+ * @var \Drupal\Core\Messenger\MessengerInterface|\Prophecy\Prophecy\ObjectProphecy
+ */
+ protected $messenger;
+
/**
* The OG membership entity.
*
@@ -79,16 +87,18 @@ public function setUp() {
$this->group = $this->prophesize(ContentEntityInterface::class);
$this->membershipManager = $this->prophesize(MembershipManagerInterface::class);
$this->ogAccess = $this->prophesize(OgAccessInterface::class);
+ $this->messenger = $this->prophesize(MessengerInterface::class);
$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 +265,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() {
- }
-
-}