Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save zanvidmar/cf36baff499fb12f0a7d03ccb4deecf2 to your computer and use it in GitHub Desktop.

Select an option

Save zanvidmar/cf36baff499fb12f0a7d03ccb4deecf2 to your computer and use it in GitHub Desktop.
social-re-use-group-join-methods-3254715-29-zan.patch
diff --git a/composer.json b/composer.json
index dd0736126..e0c52f12f 100644
--- a/composer.json
+++ b/composer.json
@@ -140,6 +140,12 @@
},
"drupal/redirect": {
"Redirection issue when interface language is different from content language": "https://www.drupal.org/files/issues/2020-06-01/redirect-interface_language_different_from_content_language_2991423-13.patch"
+ },
+ "drupal/socialbase": {
+ "Make join methods for groups re-usable": "https://www.drupal.org/files/issues/2021-12-29/re-use-group-join-methods-3256324-3.patch"
+ },
+ "drupal/socialblue": {
+ "Make join methods for groups re-usable": "https://www.drupal.org/files/issues/2021-12-29/re-use-group-join-methods-3256328-3.patch"
}
}
},
diff --git a/modules/social_features/social_album/src/Controller/SocialAlbumController.php b/modules/social_features/social_album/src/Controller/SocialAlbumController.php
index 13aaadb61..b7352a5dc 100644
--- a/modules/social_features/social_album/src/Controller/SocialAlbumController.php
+++ b/modules/social_features/social_album/src/Controller/SocialAlbumController.php
@@ -231,10 +231,10 @@ class SocialAlbumController extends ControllerBase {
$storage = $this->entityTypeManager()->getStorage('group_content');
if ($group_content = $storage->loadByEntity($node)) {
- /** @var \Drupal\group\Entity\GroupInterface $group */
+ /** @var \Drupal\social_group\SocialGroupInterface $group */
$group = reset($group_content)->getGroup();
- return AccessResult::allowedIf($group->getMember($account) !== FALSE);
+ return AccessResult::allowedIf($group->hasMember($account));
}
}
}
diff --git a/modules/social_features/social_event/src/Plugin/Block/EventRequestEnrollmentNotification.php b/modules/social_features/social_event/src/Plugin/Block/EventRequestEnrollmentNotification.php
index 3b5c16043..82ca2e29f 100644
--- a/modules/social_features/social_event/src/Plugin/Block/EventRequestEnrollmentNotification.php
+++ b/modules/social_features/social_event/src/Plugin/Block/EventRequestEnrollmentNotification.php
@@ -65,7 +65,7 @@ class EventRequestEnrollmentNotification extends BlockBase implements ContainerF
protected $loggerFactory;
/**
- * Constructs SocialGroupRequestMembershipNotification.
+ * Constructs EventRequestEnrollmentNotification.
*
* @param array $configuration
* Configuration array.
diff --git a/modules/social_features/social_group/modules/social_group_default_route/src/EventSubscriber/RedirectSubscriber.php b/modules/social_features/social_group/modules/social_group_default_route/src/EventSubscriber/RedirectSubscriber.php
index bc7404e81..6b7e08417 100644
--- a/modules/social_features/social_group/modules/social_group_default_route/src/EventSubscriber/RedirectSubscriber.php
+++ b/modules/social_features/social_group/modules/social_group_default_route/src/EventSubscriber/RedirectSubscriber.php
@@ -5,8 +5,7 @@ namespace Drupal\social_group_default_route\EventSubscriber;
use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\Core\Session\AccountProxy;
use Drupal\Core\Url;
-use Drupal\group\Entity\Group;
-use Drupal\user\Entity\User;
+use Drupal\social_group\SocialGroupInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
@@ -19,6 +18,26 @@ use Symfony\Component\HttpKernel\KernelEvents;
*/
class RedirectSubscriber implements EventSubscriberInterface {
+ /**
+ * The route name of the default page of any group type except closed groups.
+ */
+ private const DEFAULT_ROUTE = 'social_group.stream';
+
+ /**
+ * The route name of the group default page is provided by the current module.
+ */
+ private const ALTERNATIVE_ROUTE = 'social_group_default.group_home';
+
+ /**
+ * The route name of the default page of any group.
+ */
+ private const DEFAULT_GROUP_ROUTE = 'entity.group.canonical';
+
+ /**
+ * The route name of the default page of closed groups.
+ */
+ private const DEFAULT_CLOSED_ROUTE = 'view.group_information.page_group_about';
+
/**
* The current route.
*
@@ -34,7 +53,7 @@ class RedirectSubscriber implements EventSubscriberInterface {
protected $currentUser;
/**
- * Redirectsubscriber construct.
+ * RedirectSubscriber constructor.
*
* @param \Drupal\Core\Routing\CurrentRouteMatch $route_match
* The current route.
@@ -47,10 +66,7 @@ class RedirectSubscriber implements EventSubscriberInterface {
}
/**
- * Get the request events.
- *
- * @return mixed
- * Returns request events.
+ * {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = ['groupLandingPage'];
@@ -66,52 +82,54 @@ class RedirectSubscriber implements EventSubscriberInterface {
public function groupLandingPage(RequestEvent $event) {
// First check if the current route is the group canonical.
- $routeMatch = $this->currentRoute->getRouteName();
+ $route_name = $this->currentRoute->getRouteName();
// Not group canonical, then we leave.
if (
- $routeMatch !== 'entity.group.canonical' &&
- $routeMatch !== 'social_group_default.group_home'
+ $route_name !== self::DEFAULT_GROUP_ROUTE &&
+ $route_name !== self::ALTERNATIVE_ROUTE
) {
return;
}
// Fetch the group parameter and check if's an actual group.
$group = $this->currentRoute->getParameter('group');
+
// Not group, then we leave.
- if (!$group instanceof Group) {
+ if (!$group instanceof SocialGroupInterface) {
return;
}
- // Set the already default redirect route.
- $defaultRoute = 'social_group.stream';
- $defaultClosedRoute = 'view.group_information.page_group_about';
-
- // Check if this group has a custom route set.
- $route = $group->getFieldValue('default_route', 'value');
-
// Check if current user is a member.
- if ($group->getMember(User::load($this->currentUser->id())) === FALSE) {
- $route = $group->getFieldValue('default_route_an', 'value');
+ if (!$group->getMember($this->currentUser)) {
+ /** @var string|null $route */
+ $route = $group->default_route_an->value;
+
// If you're not a member and the group type is closed.
if ($route === NULL) {
- $route = ($group->getGroupType()->id() === 'closed_group') ? $defaultClosedRoute : $defaultRoute;
+ $route = $group->getGroupType()->id() === 'closed_group'
+ ? self::DEFAULT_CLOSED_ROUTE : self::DEFAULT_ROUTE;
}
}
+ else {
+ /** @var string|null $route */
+ $route = $group->default_route->value;
- // Still no route here? Then we use the normal default.
- if ($route === NULL) {
- $route = $defaultRoute;
+ // Still no route here? Then we use the normal default.
+ if ($route === NULL) {
+ $route = self::DEFAULT_ROUTE;
+ }
}
// Determine the URL we want to redirect to.
$url = Url::fromRoute($route, ['group' => $group->id()]);
// If it's not set, set to canonical, or the current user has no access.
- if (!isset($route) || ($route === $routeMatch) || $url->access($this->currentUser) === FALSE) {
+ if ($route === $route_name || $url->access($this->currentUser) === FALSE) {
// This basically means that the normal flow remains intact.
return;
}
+
// Redirect.
$event->setResponse(new RedirectResponse($url->toString()));
}
diff --git a/modules/social_features/social_group/modules/social_group_flexible_group/config/install/field.storage.group.field_group_allowed_join_method.yml b/modules/social_features/social_group/modules/social_group_flexible_group/config/install/field.storage.group.field_group_allowed_join_method.yml
index 5aee3aa4a..9b49ffa3a 100644
--- a/modules/social_features/social_group/modules/social_group_flexible_group/config/install/field.storage.group.field_group_allowed_join_method.yml
+++ b/modules/social_features/social_group/modules/social_group_flexible_group/config/install/field.storage.group.field_group_allowed_join_method.yml
@@ -9,14 +9,8 @@ field_name: field_group_allowed_join_method
entity_type: group
type: list_string
settings:
- allowed_values:
- -
- value: direct
- label: 'Join directly'
- -
- value: added
- label: 'Be added'
- allowed_values_function: ''
+ allowed_values: { }
+ allowed_values_function: '_social_group_allowed_values_callback'
module: options
locked: false
cardinality: -1
diff --git a/modules/social_features/social_group/modules/social_group_flexible_group/config/update/social_group_flexible_group_update_11204.yml b/modules/social_features/social_group/modules/social_group_flexible_group/config/update/social_group_flexible_group_update_11204.yml
new file mode 100644
index 000000000..c0ba3f366
--- /dev/null
+++ b/modules/social_features/social_group/modules/social_group_flexible_group/config/update/social_group_flexible_group_update_11204.yml
@@ -0,0 +1,16 @@
+field.storage.group.field_group_allowed_join_method:
+ expected_config:
+ settings:
+ allowed_values:
+ -
+ value: direct
+ label: 'Join directly'
+ -
+ value: added
+ label: 'Be added'
+ allowed_values_function: ''
+ update_actions:
+ change:
+ settings:
+ allowed_values: { }
+ allowed_values_function: '_social_group_allowed_values_callback'
diff --git a/modules/social_features/social_group/modules/social_group_flexible_group/social_group_flexible_group.install b/modules/social_features/social_group/modules/social_group_flexible_group/social_group_flexible_group.install
index 68d5b6325..d8b885796 100644
--- a/modules/social_features/social_group/modules/social_group_flexible_group/social_group_flexible_group.install
+++ b/modules/social_features/social_group/modules/social_group_flexible_group/social_group_flexible_group.install
@@ -502,3 +502,17 @@ function social_group_flexible_group_update_11203(): void {
$config_storage->write('language.content_settings.taxonomy_term.group_type', (array) $source->read('language.content_settings.taxonomy_term.group_type_11203'));
}
+
+/**
+ * Allow extending list of allowed join methods dynamically.
+ */
+function social_group_flexible_group_update_11204(): string {
+ /** @var \Drupal\update_helper\UpdaterInterface $update_helper */
+ $update_helper = \Drupal::service('update_helper.updater');
+
+ // Execute configuration update definitions with logging of success.
+ $update_helper->executeUpdate('social_group_flexible_group', __FUNCTION__);
+
+ // Output logged messages to related channel of update execution.
+ return $update_helper->logger()->output();
+}
diff --git a/modules/social_features/social_group/modules/social_group_flexible_group/social_group_flexible_group.module b/modules/social_features/social_group/modules/social_group_flexible_group/social_group_flexible_group.module
index 65ad3dde9..0bcff1257 100644
--- a/modules/social_features/social_group/modules/social_group_flexible_group/social_group_flexible_group.module
+++ b/modules/social_features/social_group/modules/social_group_flexible_group/social_group_flexible_group.module
@@ -19,6 +19,7 @@ use Drupal\block\Entity\Block;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\group\Entity\Group;
use Drupal\group\Entity\GroupInterface;
+use Drupal\social_group\SocialGroupInterface;
use Drupal\social_group_flexible_group\FlexibleGroupContentVisibilityUpdate;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\Plugin\views\row\EntityRow;
@@ -78,20 +79,6 @@ function social_group_flexible_group_form_alter(&$form, FormStateInterface $form
if ($form['#id'] === 'views-exposed-form-newest-groups-page-all-groups' ||
$form['#id'] === 'views-exposed-form-search-groups-page-no-value' ||
$form['#id'] === 'views-exposed-form-search-groups-page') {
-
- // Update filter values so it matches the join methods in the popover.
- if (!empty($form['field_group_allowed_join_method'])) {
- if (array_key_exists('added', $form['field_group_allowed_join_method']['#options'])) {
- $form['field_group_allowed_join_method']['#options']['added'] = t('Invite only');
- }
- if (array_key_exists('direct', $form['field_group_allowed_join_method']['#options'])) {
- $form['field_group_allowed_join_method']['#options']['direct'] = t('Open to join');
- }
- if (array_key_exists('request', $form['field_group_allowed_join_method']['#options'])) {
- $form['field_group_allowed_join_method']['#options']['request'] = t('Request to join');
- }
- }
-
// Add states so this is only available when flexible groups is checked.
// Could be hidden when only flexible groups is enabled, so check that.
// @todo remove this once everything is migrated to flexible groups.
@@ -130,18 +117,6 @@ function social_group_flexible_group_form_alter(&$form, FormStateInterface $form
}
// Change the allowed join method on flexible groups.
if (!empty($form['field_group_allowed_join_method'])) {
- // First we reorder the elmements, if invite only is part of it
- // we always want to show this last.
- if (!empty($form['field_group_allowed_join_method']['widget']['#options'])) {
- if (array_key_exists('added', $form['field_group_allowed_join_method']['widget']['#options'])) {
- $option = $form['field_group_allowed_join_method']['widget']['#options']['added'];
- // Unset it.
- unset($form['field_group_allowed_join_method']['widget']['#options']['added']);
- // Add it at the end.
- $form['field_group_allowed_join_method']['widget']['#options']['added'] = $option;
- }
- }
-
if (!empty($form['field_group_allowed_join_method']['widget']['#title'])) {
$form['field_group_allowed_join_method']['widget']['#title'] = t('Join methods');
}
@@ -284,7 +259,7 @@ function social_group_flexible_group_group_access(EntityInterface $entity, $oper
$result = AccessResult::neutral();
// Write custom access checks based on the new group visibility field.
// If group visibility doesn't exist we can skip this.
- /** @var \Drupal\group\Entity\GroupInterface $entity */
+ /** @var \Drupal\social_group\SocialGroupInterface $entity */
if ($operation !== 'view' || !$entity->hasField('field_flexible_group_visibility')) {
return $result;
}
@@ -294,32 +269,32 @@ function social_group_flexible_group_group_access(EntityInterface $entity, $oper
}
// If group visibility value doesn't exist we can skip.
- if (empty($entity->getFieldValue('field_flexible_group_visibility', 'value'))) {
+ if ($entity->field_flexible_group_visibility->isEmpty()) {
return $result;
}
- // If group visibility exists and public is selected, we can skip.
- $group_visibility = $entity->getFieldValue('field_flexible_group_visibility', 'value');
- if ($group_visibility === 'public') {
- return $result;
- }
+ switch ($entity->field_flexible_group_visibility->value) {
+ // If group visibility exists and public is selected, we can skip.
+ case 'public':
+ return $result;
- // If group visibility exists and community or members is selected, check
- // if user is logged.
- if ($group_visibility === 'community') {
- return AccessResult::forbiddenIf($account->isAnonymous())
- ->cachePerUser()
- ->addCacheableDependency($entity);
- }
- // If group visibility exists and members only is selected, we need to check
- // if user is logged in and is a member of the group.
- if ($group_visibility === 'members') {
- $not_a_member = !$entity->getMember($account) || $account->isAnonymous();
- return AccessResult::forbiddenIf($not_a_member)
- ->cachePerPermissions()
- ->cachePerUser()
- ->addCacheableDependency($entity)
- ->addCacheableDependency($account);
+ // If group visibility exists and community or members is selected, check
+ // if user is logged.
+ case 'community':
+ return AccessResult::forbiddenIf($account->isAnonymous())
+ ->cachePerUser()
+ ->addCacheableDependency($entity);
+
+ // If group visibility exists and members only is selected, we need to check
+ // if user is logged in and is a member of the group.
+ case 'members':
+ $not_a_member = !$entity->hasMember($account) || $account->isAnonymous();
+
+ return AccessResult::forbiddenIf($not_a_member)
+ ->cachePerPermissions()
+ ->cachePerUser()
+ ->addCacheableDependency($entity)
+ ->addCacheableDependency($account);
}
return $result;
@@ -357,45 +332,53 @@ function social_group_flexible_group_preprocess_form_element(&$variables) {
}
/**
- * Implements template_preprocess_form_element().
+ * Implements hook_preprocess_HOOK().
*/
-function social_group_flexible_group_preprocess_fieldset(&$variables) {
+function social_group_flexible_group_preprocess_fieldset(array &$variables): void {
// Make sure our flexible group visibility field renders a tooltip, since
// this field is rendered as fieldset with legend and radios as children
// we need to do it in this preprocess.
$element = $variables['element'];
- if (!empty($element['#field_name'])) {
- if ($element['#field_name'] === 'field_flexible_group_visibility') {
- $description = '';
- foreach ($element['#options'] as $key => $label) {
- $description .= social_group_group_visibility_description($key);
- }
- // Render a specific tooltip based on a field name and description.
- // This is done in the fieldset, next to the <legend>.
- $variables['popover'] = social_group_render_tooltip('field_flexible_group_visibility', t('Group Visibility'), $description);
- }
- if ($element['#field_name'] === 'field_group_allowed_visibility') {
- $description = '';
- foreach ($element['#options'] as $key => $label) {
- $description .= social_group_allowed_visibility_description($key);
- }
+ if (empty($element['#field_name'])) {
+ return;
+ }
- // Render a specific tooltip based on a field name and description.
- // This is done in the fieldset, next to the <legend>.
- $variables['popover'] = social_group_render_tooltip('field_group_allowed_visibility', t('Group content visibility'), $description);
- }
- if ($element['#field_name'] === 'field_group_allowed_join_method') {
- $description = '';
- foreach ($element['#options'] as $key => $label) {
- $description .= social_group_allowed_join_method_description($key);
- }
+ $fields = [
+ 'field_flexible_group_visibility' => t('Group Visibility'),
+ 'field_group_allowed_visibility' => t('Group content visibility'),
+ ];
- // Render a specific tooltip based on a field name and description.
- // This is done in the fieldset, next to the <legend>.
- $variables['popover'] = social_group_render_tooltip('field_group_allowed_join_method', t('Join methods'), $description);
- }
+ if (!isset($fields[$element['#field_name']])) {
+ return;
+ }
+
+ $description = '';
+
+ foreach ($element['#options'] as $key => $label) {
+ $description .= social_group_group_visibility_description($key);
}
+
+ // Render a specific tooltip based on a field name and description.
+ // This is done in the fieldset, next to the <legend>.
+ $variables['popover'] = social_group_render_tooltip(
+ $element['#field_name'],
+ $fields[$element['#field_name']],
+ $description,
+ );
+}
+
+/**
+ * Implements hook_social_group_join_method_usage().
+ */
+function social_group_flexible_group_social_group_join_method_usage(): array {
+ return [
+ [
+ 'entity_type' => 'group',
+ 'bundle' => 'flexible_group',
+ 'field' => 'field_group_allowed_join_method',
+ ],
+ ];
}
/**
@@ -478,20 +461,20 @@ function social_group_flexible_group_can_join_directly(GroupInterface $group) {
/**
* Check if a user can be added to a group.
*
- * @param \Drupal\group\Entity\Group $group
+ * @param \Drupal\social_group\SocialGroupInterface $group
* The group we are checking.
*
* @return bool
* TRUE when users can join.
*/
-function social_group_flexible_group_can_be_added(Group $group) {
- $join_methods = $group->get('field_group_allowed_join_method')->getValue();
-
- if (!in_array('added', array_column($join_methods, 'value'), FALSE)) {
- return FALSE;
- }
-
- return TRUE;
+function social_group_flexible_group_can_be_added(SocialGroupInterface $group): bool {
+ return in_array(
+ 'added',
+ array_column(
+ $group->get('field_group_allowed_join_method')->getValue(),
+ 'value',
+ ),
+ );
}
/**
@@ -554,18 +537,17 @@ function social_group_flexible_group_members_enabled(Group $group) {
/**
* Implements hook_menu_local_actions_alter().
*/
-function social_group_flexible_group_menu_local_actions_alter(&$local_actions) {
- $group = _social_group_get_current_group();
- $user = \Drupal::currentUser();
-
- // Remove the social_group add member action on the
- // membership overview if we can't add members directly.
- // SM+ can still add members though.
- if ($group instanceof GroupInterface
- && $group->getGroupType()->id() === 'flexible_group'
- && !social_group_flexible_group_can_be_added($group)
- && !$user->hasPermission('manage all groups')
- && !$group->hasPermission('administer members', $user)
+function social_group_flexible_group_menu_local_actions_alter(array &$local_actions): void {
+ $account = \Drupal::currentUser();
+
+ // Remove the social_group add member action on the membership overview if we
+ // can't add members directly. SM+ can still add members though.
+ if (
+ ($group = _social_group_get_current_group()) !== NULL &&
+ $group->getGroupType()->id() === 'flexible_group' &&
+ !social_group_flexible_group_can_be_added($group) &&
+ !$account->hasPermission('manage all groups') &&
+ !$group->hasPermission('administer members', $account)
) {
unset($local_actions['social_group.add_member']);
}
@@ -574,7 +556,7 @@ function social_group_flexible_group_menu_local_actions_alter(&$local_actions) {
/**
* Determine whether a user can see flexible groups as outsider.
*
- * @param \Drupal\group\Entity\Group $group
+ * @param \Drupal\social_group\SocialGroupInterface $group
* The group we are checking.
* @param \Drupal\Core\Session\AccountInterface $account
* The user to check for.
@@ -582,14 +564,17 @@ function social_group_flexible_group_menu_local_actions_alter(&$local_actions) {
* @return bool
* Whether the user is allowed to view this flexible groups.
*/
-function social_group_flexible_group_can_view_flexible_groups(Group $group, AccountInterface $account) : bool {
+function social_group_flexible_group_can_view_flexible_groups(
+ SocialGroupInterface $group,
+ AccountInterface $account
+): bool {
// Users who can manage all can manage everything.
if ($account->hasPermission('manage all groups')) {
return TRUE;
}
// If User is a member it can see it.
- if ($group->getMember($account) !== FALSE) {
+ if ($group->hasMember($account)) {
return TRUE;
}
@@ -730,9 +715,11 @@ function social_group_flexible_group_block_access(Block $block, $operation, Acco
return AccessResult::neutral();
}
- if (!$group->getMember($account) &&
+ if (
+ !$group->hasMember($account) &&
!social_group_flexible_group_community_enabled($group) &&
- !social_group_flexible_group_public_enabled($group)) {
+ !social_group_flexible_group_public_enabled($group)
+ ) {
// If it is flexible and the current user is not an member of this group,
// and content visibility is not public and also not community
// hide it.
diff --git a/modules/social_features/social_group/modules/social_group_flexible_group/src/Access/FlexibleGroupContentAccessCheck.php b/modules/social_features/social_group/modules/social_group_flexible_group/src/Access/FlexibleGroupContentAccessCheck.php
index d6f8d858f..e1fd15ecf 100644
--- a/modules/social_features/social_group/modules/social_group_flexible_group/src/Access/FlexibleGroupContentAccessCheck.php
+++ b/modules/social_features/social_group/modules/social_group_flexible_group/src/Access/FlexibleGroupContentAccessCheck.php
@@ -2,13 +2,12 @@
namespace Drupal\social_group_flexible_group\Access;
-use Drupal\group\Entity\Group;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\group\Entity\GroupTypeInterface;
-use Drupal\group\GroupMembership;
+use Drupal\social_group\SocialGroupInterface;
use Symfony\Component\Routing\Route;
/**
@@ -45,7 +44,7 @@ class FlexibleGroupContentAccessCheck implements AccessInterface {
// Don't interfere if the group isn't a real group.
$group = $parameters->get('group');
- if (!$group instanceof Group) {
+ if (!$group instanceof SocialGroupInterface) {
return AccessResult::allowed();
}
@@ -54,12 +53,11 @@ class FlexibleGroupContentAccessCheck implements AccessInterface {
return AccessResult::allowed();
}
+ $is_member = $group->hasMember($account);
+
// Handling the visibility of a group.
if ($group->hasField('field_flexible_group_visibility')) {
- $group_visibility_value = $group->getFieldValue('field_flexible_group_visibility', 'value');
- $is_member = $group->getMember($account) instanceof GroupMembership;
-
- switch ($group_visibility_value) {
+ switch ($group->field_flexible_group_visibility->value) {
case 'members':
if (!$is_member) {
return AccessResult::forbidden();
@@ -86,7 +84,7 @@ class FlexibleGroupContentAccessCheck implements AccessInterface {
}
// If User is a member we can also rely on Group to take permissions.
- if ($group->getMember($account) !== FALSE) {
+ if ($is_member) {
return AccessResult::allowed()->addCacheableDependency($group);
}
diff --git a/modules/social_features/social_group/modules/social_group_flexible_group/src/Access/FlexibleGroupJoinPermissionAccessCheck.php b/modules/social_features/social_group/modules/social_group_flexible_group/src/Access/FlexibleGroupJoinPermissionAccessCheck.php
index ae4c62bea..6e799e214 100644
--- a/modules/social_features/social_group/modules/social_group_flexible_group/src/Access/FlexibleGroupJoinPermissionAccessCheck.php
+++ b/modules/social_features/social_group/modules/social_group_flexible_group/src/Access/FlexibleGroupJoinPermissionAccessCheck.php
@@ -3,13 +3,12 @@
namespace Drupal\social_group_flexible_group\Access;
use Drupal\group\Access\GroupAccessResult;
-use Drupal\group\Entity\Group;
-use Drupal\group\Entity\GroupInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\group\Entity\GroupTypeInterface;
+use Drupal\social_group\SocialGroupInterface;
use Symfony\Component\Routing\Route;
/**
@@ -47,9 +46,11 @@ class FlexibleGroupJoinPermissionAccessCheck implements AccessInterface {
// Don't interfere if the group isn't a real group.
$group = $parameters->get('group');
- if (!$group instanceof GroupInterface) {
+
+ if (!$group instanceof SocialGroupInterface) {
$group = _social_group_get_current_group();
- if (!$group instanceof GroupInterface) {
+
+ if (!$group instanceof SocialGroupInterface) {
return AccessResult::neutral();
}
}
@@ -105,7 +106,7 @@ class FlexibleGroupJoinPermissionAccessCheck implements AccessInterface {
*
* @param string $permission
* The permission we need to check access for.
- * @param \Drupal\group\Entity\Group $group
+ * @param \Drupal\social_group\SocialGroupInterface $group
* The Group we are on.
* @param \Drupal\Core\Session\AccountInterface $account
* The account to check access for.
@@ -113,9 +114,14 @@ class FlexibleGroupJoinPermissionAccessCheck implements AccessInterface {
* The parametrized route.
*
* @return bool
- * FALSE if its not allowed.
+ * FALSE if it's not allowed.
*/
- private function calculateJoinPermission($permission, Group $group, AccountInterface $account, RouteMatchInterface $route_match) {
+ private function calculateJoinPermission(
+ string $permission,
+ SocialGroupInterface $group,
+ AccountInterface $account,
+ RouteMatchInterface $route_match
+ ): bool {
$direct_option = social_group_flexible_group_can_join_directly($group);
$added_option = social_group_flexible_group_can_be_added($group);
@@ -129,13 +135,13 @@ class FlexibleGroupJoinPermissionAccessCheck implements AccessInterface {
if (!$direct_option &&
$route_match->getRouteName() === 'view.group_manage_members.page_group_manage_members' &&
$account->isAuthenticated() &&
- !$group->getMember($account) &&
+ !$group->hasMember($account) &&
!$group->hasPermission('administer members', $account)) {
return FALSE;
}
// There is no direct join method so it's not allowed to go to /join.
- if ($permission === 'join direct' && !$direct_option && !$group->getMember($account)) {
+ if ($permission === 'join direct' && !$direct_option && !$group->hasMember($account)) {
return FALSE;
}
diff --git a/modules/social_features/social_group/modules/social_group_flexible_group/src/EventSubscriber/RedirectSubscriber.php b/modules/social_features/social_group/modules/social_group_flexible_group/src/EventSubscriber/RedirectSubscriber.php
index 128a72e0c..f3bafa6cc 100644
--- a/modules/social_features/social_group/modules/social_group_flexible_group/src/EventSubscriber/RedirectSubscriber.php
+++ b/modules/social_features/social_group/modules/social_group_flexible_group/src/EventSubscriber/RedirectSubscriber.php
@@ -79,7 +79,7 @@ class RedirectSubscriber implements EventSubscriberInterface {
// If the user can manage groups or the user is a member.
if (
$this->currentUser->hasPermission('manage all groups') ||
- $group->getMember($this->currentUser)
+ $group->hasMember($this->currentUser)
) {
return;
}
diff --git a/modules/social_features/social_group/modules/social_group_gvbo/src/Form/SocialGroupViewsBulkOperationsConfigureAction.php b/modules/social_features/social_group/modules/social_group_gvbo/src/Form/SocialGroupViewsBulkOperationsConfigureAction.php
index c0624d8f8..faced972b 100644
--- a/modules/social_features/social_group/modules/social_group_gvbo/src/Form/SocialGroupViewsBulkOperationsConfigureAction.php
+++ b/modules/social_features/social_group/modules/social_group_gvbo/src/Form/SocialGroupViewsBulkOperationsConfigureAction.php
@@ -33,8 +33,10 @@ class SocialGroupViewsBulkOperationsConfigureAction extends GroupViewsBulkOperat
if ($url->getRouteName() === 'views_bulk_operations.confirm') {
$parameters = $url->getRouteParameters();
- if (empty($parameters['group'])) {
- $group = _social_group_get_current_group();
+ if (
+ empty($parameters['group']) &&
+ ($group = _social_group_get_current_group()) !== NULL
+ ) {
$parameters['group'] = $group->id();
}
diff --git a/modules/social_features/social_group/modules/social_group_invite/social_group_invite.module b/modules/social_features/social_group/modules/social_group_invite/social_group_invite.module
index a905e5ce1..ce56ef7f7 100644
--- a/modules/social_features/social_group/modules/social_group_invite/social_group_invite.module
+++ b/modules/social_features/social_group/modules/social_group_invite/social_group_invite.module
@@ -16,7 +16,6 @@ use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\Exception\MissingDataException;
use Drupal\Core\Url;
-use Drupal\ginvite\GroupInvitation as GroupInvitationWrapper;
use Drupal\ginvite\Plugin\GroupContentEnabler\GroupInvitation;
use Drupal\group\Entity\GroupContent;
use Drupal\group\Entity\GroupContentInterface;
@@ -166,8 +165,10 @@ function social_group_invite_preprocess_views_view(&$variables) {
}
}
// Implement button to go back to the group for our custom view.
- if ($variables['view']->id() === 'social_group_invitations') {
- $group = _social_group_get_current_group();
+ elseif (
+ $view->id() === 'social_group_invitations' &&
+ ($group = _social_group_get_current_group()) !== NULL
+ ) {
$variables['more'] = [
'#title' => t('Back to group'),
'#type' => 'link',
@@ -308,17 +309,17 @@ function social_group_invite_theme_registry_alter(&$theme_registry) {
*/
function social_group_invite_preprocess_page_title(&$variables) {
// Add count of pending invites to the page title for a group.
- if (\Drupal::routeMatch()->getParameter('view_id') === 'social_group_invitations' &&
- !empty(\Drupal::routeMatch()->getParameter('group'))) {
- $group = _social_group_get_current_group();
- if (!empty($group->label())) {
- $loader = \Drupal::service('ginvite.invitation_loader');
- $count = count($loader->loadByProperties(['gid' => $group->id()]));
- $title = \Drupal::translation()->formatPlural($count, '1 membership invite for group: @group_name', '@count membership invites for group: @group_name', ['@group_name' => $group->label()]);
- $variables['title'] = $title;
- $variables['#cache']['tags'][] = 'group_content_list:group:' . $group->id();
- $variables['#cache']['tags'][] = 'group_content_list:plugin:group_invitation:group:' . $group->id();
- }
+ if (
+ \Drupal::routeMatch()->getParameter('view_id') === 'social_group_invitations' &&
+ !empty(\Drupal::routeMatch()->getParameter('group')) &&
+ ($group = _social_group_get_current_group()) !== NULL
+ ) {
+ $loader = \Drupal::service('ginvite.invitation_loader');
+ $count = count($loader->loadByProperties(['gid' => $group->id()]));
+ $title = \Drupal::translation()->formatPlural($count, '1 membership invite for group: @group_name', '@count membership invites for group: @group_name', ['@group_name' => $group->label()]);
+ $variables['title'] = $title;
+ $variables['#cache']['tags'][] = 'group_content_list:group:' . $group->id();
+ $variables['#cache']['tags'][] = 'group_content_list:plugin:group_invitation:group:' . $group->id();
}
// Add count of pending invites to the page title for a user.
if (\Drupal::routeMatch()->getParameter('view_id') === 'social_group_user_invitations' &&
@@ -487,86 +488,58 @@ function social_group_invite_preprocess_activity(&$variables) {
}
/**
- * Implements hook_preprocess_group().
+ * Implements hook_preprocess_join().
*/
-function social_group_invite_preprocess_group(&$variables) {
- /** @var \Drupal\group\Entity\GroupInterface $group */
- $group = $variables['group'];
- $group_type = $group->getGroupType();
- $variables['user_is_invited'] = FALSE;
-
- if ($variables['view_mode'] === 'statistic') {
- $url = Url::fromRoute('ginvite.invitation.bulk', ['group' => $group->id()]);
- $group_url = Url::fromRoute('view.group_information.page_group_about', ['group' => $group->id()], ['absolute' => TRUE]);
- // For groups that only group members have access to, we should not show the
- // share link functionality.
- if (
- ($group->hasField('field_flexible_group_visibility') &&
- !$group->get('field_flexible_group_visibility')->isEmpty() &&
- $group->get('field_flexible_group_visibility')->getValue()[0]['value'] === 'members') ||
- in_array($group->bundle(), ['closed_group', 'secret_group'])
- ) {
- $group_url = NULL;
- }
+function social_group_invite_preprocess_join(array &$variables): void {
+ $entity = $variables['entity'];
- $group_settings = \Drupal::config('social_group.settings');
- // Show share/invite button only if member is allowed to do that.
- if (
- (bool) $group_settings->get('group_invite.invite_by_members') &&
- $url->access(\Drupal::currentUser())
- ) {
- $variables['members_allowed_to_invite'] = TRUE;
- $variables['invite_content'] = [
- '#theme' => 'invite_to_group_by_member',
- '#url' => $group_url,
- '#link' => Link::fromTextAndUrl(t('Invite via email'), $url)
- ->toRenderable(),
- ];
- }
+ if (!$entity instanceof GroupInterface) {
+ return;
}
- // Only for groups that have invites enabled.
- if (!$group_type->hasContentPlugin('group_invitation')) {
+ $url = Url::fromRoute('ginvite.invitation.bulk', [
+ 'group' => $entity->id(),
+ ]);
+
+ if (!$url->access()) {
return;
}
- // Only for LU.
- $account = \Drupal::currentUser();
- if ($account->isAnonymous()) {
+ $config = \Drupal::config('social_group.settings');
+
+ if (!$config->get('group_invite.invite_by_members')) {
return;
}
- // Check if the user (entity_id) has a pending invite for the group.
- $properties = [
- 'entity_id' => $account->id(),
- 'gid' => $group->id(),
- 'invitation_status' => GroupInvitation::INVITATION_PENDING,
- ];
- /** @var \Drupal\ginvite\GroupInvitationLoaderInterface $loader */
- $loader = \Drupal::service('ginvite.invitation_loader');
- $invitations = $loader->loadByProperties($properties);
- // We have pending invites, let's build a button to accept or decline one.
- if ($invitations > 0) {
- // Build routes.
- $invitation = reset($invitations);
- if ($invitation instanceof GroupInvitationWrapper) {
- // Lets grab the group content so we can build the URL.
- $group_content = $invitation->getGroupContent();
- if ($group_content instanceof GroupContent) {
- $variables['user_is_invited'] = TRUE;
- // It will become two links with a dropdown button.
- $variables['group_invite_accept_operations_url'] = Url::fromRoute('ginvite.invitation.accept', ['group_content' => $group_content->id()]);
- $variables['group_invite_decline_operations_url'] = Url::fromRoute('ginvite.invitation.decline', ['group_content' => $group_content->id()]);
- $variables['#cache']['tags'][] = 'group_content_list:entity:' . $account->id();
- $variables['#cache']['tags'][] = 'group_content_list:plugin:group_invitation:entity:' . $account->id();
- $variables['#cache']['contexts'][] = 'user';
- }
- }
+ // For groups that only group members have access to, we should not show the
+ // share link functionality.
+ if (
+ $entity->hasField('field_flexible_group_visibility') &&
+ !$entity->field_flexible_group_visibility->isEmpty() &&
+ $entity->field_flexible_group_visibility->value === 'members' ||
+ in_array($entity->bundle(), ['closed_group', 'secret_group'])
+ ) {
+ $group_url = NULL;
+ }
+ else {
+ $group_url = Url::fromRoute('view.group_information.page_group_about', [
+ 'group' => $entity->id(),
+ ], [
+ 'absolute' => TRUE,
+ ]);
}
+
+ // Show share/invite button only if member is allowed to do that.
+ $variables['invite_widget'] = [
+ '#theme' => 'invite_to_group_by_member',
+ '#url' => $group_url,
+ '#link' => Link::fromTextAndUrl(t('Invite via email'), $url)
+ ->toRenderable(),
+ ];
}
/**
- * Implements hook_form_FORM_ID_alter() for "social_group_form".
+ * Implements hook_form_FORM_ID_alter().
*/
function social_group_invite_form_social_group_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$invite_config = \Drupal::config('social_group.settings')
diff --git a/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Block/SocialGroupInviteNotificationBlock.php b/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Block/SocialGroupInviteNotificationBlock.php
index a313d0172..7c0183ac5 100644
--- a/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Block/SocialGroupInviteNotificationBlock.php
+++ b/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Block/SocialGroupInviteNotificationBlock.php
@@ -10,6 +10,7 @@ use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslationManager;
use Drupal\ginvite\GroupInvitationLoaderInterface;
use Drupal\ginvite\Plugin\GroupContentEnabler\GroupInvitation;
+use Drupal\social_group\SocialGroupInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -30,11 +31,9 @@ class SocialGroupInviteNotificationBlock extends BlockBase implements ContainerF
protected $account;
/**
- * Group entity.
- *
- * @var \Drupal\group\Entity\GroupInterface
+ * The group entity object.
*/
- protected $group;
+ protected ?SocialGroupInterface $group;
/**
* Translation manager.
@@ -100,7 +99,10 @@ class SocialGroupInviteNotificationBlock extends BlockBase implements ContainerF
*/
public function build() {
// Only when group invite is installed.
- if (!$this->group->getGroupType()->hasContentPlugin('group_invitation')) {
+ if (
+ $this->group === NULL ||
+ !$this->group->getGroupType()->hasContentPlugin('group_invitation')
+ ) {
return [];
}
@@ -157,11 +159,16 @@ class SocialGroupInviteNotificationBlock extends BlockBase implements ContainerF
* {@inheritdoc}
*/
public function getCacheTags() {
- return Cache::mergeTags(parent::getCacheTags(), [
+ $tags = [
'group_content_list:entity:' . $this->account->id(),
'group_content_list:plugin:group_invitation:entity:' . $this->account->id(),
- 'group:' . $this->group->id(),
- ]);
+ ];
+
+ if ($this->group !== NULL) {
+ $tags[] = 'group:' . $this->group->id();
+ }
+
+ return Cache::mergeTags(parent::getCacheTags(), $tags);
}
}
diff --git a/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Block/SocialInviteLocalActionsBlock.php b/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Block/SocialInviteLocalActionsBlock.php
index 2e1313327..828e45861 100644
--- a/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Block/SocialInviteLocalActionsBlock.php
+++ b/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Block/SocialInviteLocalActionsBlock.php
@@ -124,10 +124,10 @@ class SocialInviteLocalActionsBlock extends BlockBase implements ContainerFactor
$build = [];
// Get current group so we can build correct links.
- if (_social_group_invite_current_type_enabled_invites()) {
- /** @var \Drupal\group\Entity\GroupInterface $group */
- $group = _social_group_get_current_group();
-
+ if (
+ _social_group_invite_current_type_enabled_invites() &&
+ ($group = _social_group_get_current_group()) !== NULL
+ ) {
$button = [
'#type' => 'link',
'#title' => $this->t('View invites'),
diff --git a/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Join/SocialGroupInviteJoin.php b/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Join/SocialGroupInviteJoin.php
new file mode 100644
index 000000000..8b73d6a9c
--- /dev/null
+++ b/modules/social_features/social_group/modules/social_group_invite/src/Plugin/Join/SocialGroupInviteJoin.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Drupal\social_group_invite\Plugin\Join;
+
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\ginvite\GroupInvitation as GroupInvitationWrapper;
+use Drupal\ginvite\GroupInvitationLoaderInterface;
+use Drupal\ginvite\Plugin\GroupContentEnabler\GroupInvitation as GroupInvitationEnabler;
+use Drupal\group\Entity\GroupContentInterface;
+use Drupal\social_group\EntityMemberInterface;
+use Drupal\social_group\Plugin\Join\SocialGroupDirectJoin;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a join plugin instance for joining after invitation.
+ *
+ * @Join(
+ * id = "social_group_invite_join",
+ * entityTypeId = "group",
+ * method = "added",
+ * weight = 30,
+ * )
+ */
+class SocialGroupInviteJoin extends SocialGroupDirectJoin {
+
+ /**
+ * The group invitation loader.
+ */
+ private GroupInvitationLoaderInterface $loader;
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(
+ ContainerInterface $container,
+ array $configuration,
+ $plugin_id,
+ $plugin_definition
+ ): self {
+ /** @var self $instance */
+ $instance = parent::create(
+ $container,
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ );
+
+ $instance->loader = $container->get('ginvite.invitation_loader');
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function actions(EntityMemberInterface $entity, array &$variables): array {
+ $items = [];
+ $invited = FALSE;
+
+ // Only for groups that have invites enabled.
+ /** @var \Drupal\social_group\SocialGroupInterface $entity */
+ if (
+ $entity->getGroupType()->hasContentPlugin('group_invitation') &&
+ $this->currentUser->isAuthenticated()
+ ) {
+ // Check if the user has a pending invite for the group.
+ $invitations = $this->loader->loadByProperties([
+ 'entity_id' => $this->currentUser->id(),
+ 'gid' => $entity->id(),
+ 'invitation_status' => GroupInvitationEnabler::INVITATION_PENDING,
+ ]);
+
+ // We have pending invites, let's build a button to accept or decline one.
+ if (count($invitations) > 0) {
+ $invitation = reset($invitations);
+
+ if ($invitation instanceof GroupInvitationWrapper) {
+ // Let's grab the group content, so we can build the URL.
+ $group_content = $invitation->getGroupContent();
+
+ if ($group_content instanceof GroupContentInterface) {
+ $invited = TRUE;
+ }
+ }
+ }
+ }
+
+ if ($invited && isset($group_content)) {
+ $items[] = [
+ 'label' => $this->t('Accept'),
+ 'url' => Url::fromRoute('ginvite.invitation.accept', [
+ 'group_content' => $group_content->id(),
+ ]),
+ ];
+
+ $items[] = Link::createFromRoute(
+ $this->t('Decline'),
+ 'ginvite.invitation.decline',
+ ['group_content' => $group_content->id()],
+ );
+
+ $variables['user_is_invited'] = TRUE;
+ $variables['#cache']['contexts'][] = 'user';
+ $variables['#cache']['tags'][] = 'group_content_list:entity:' . $this->currentUser->id();
+ $variables['#cache']['tags'][] = 'group_content_list:plugin:group_invitation:entity:' . $this->currentUser->id();
+ }
+ elseif (
+ count($items = parent::actions($entity, $variables)) === 1 &&
+ $entity->bundle() === 'flexible_group' ||
+ $entity->bundle() === 'closed_group' &&
+ !$entity->hasPermission('manage all groups', $this->currentUser)
+ ) {
+ if (count($items) === 0) {
+ $items[] = ['attributes' => ['class' => ['btn-accent']]];
+ }
+ else {
+ unset($items[0]['url']);
+ }
+
+ $items[0]['label'] = $variables['cta'] = $this->t('Invitation only');
+ $variables['closed_group'] = TRUE;
+ }
+
+ return $items;
+ }
+
+}
diff --git a/modules/social_features/social_group/modules/social_group_quickjoin/src/Controller/SocialQuickJoinController.php b/modules/social_features/social_group/modules/social_group_quickjoin/src/Controller/SocialQuickJoinController.php
index 3c899c9ac..2803c00e1 100644
--- a/modules/social_features/social_group/modules/social_group_quickjoin/src/Controller/SocialQuickJoinController.php
+++ b/modules/social_features/social_group/modules/social_group_quickjoin/src/Controller/SocialQuickJoinController.php
@@ -6,8 +6,7 @@ use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Routing\CurrentRouteMatch;
-use Drupal\Core\Url;
-use Drupal\group\Entity\GroupInterface;
+use Drupal\social_group\SocialGroupInterface;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -62,21 +61,12 @@ class SocialQuickJoinController extends ControllerBase {
/**
* Function that add the current user to a group without confirmation step.
*
- * @param \Drupal\group\Entity\GroupInterface $group
+ * @param \Drupal\social_group\SocialGroupInterface $group
* The group you want to join.
*
- * @return \Symfony\Component\HttpFoundation\RedirectResponse
- * Where to redirect to.
- *
* @throws \Drupal\Core\Entity\EntityMalformedException
*/
- public function quickJoin(GroupInterface $group) {
-
- // No group, so go home.
- if (!$group instanceof GroupInterface) {
- return new RedirectResponse(Url::fromRoute('<front>')->toString());
- }
-
+ public function quickJoin(SocialGroupInterface $group): RedirectResponse {
// It's a group, so determine the path for redirection.
$groupRedirect = $group->toUrl()->toString();
@@ -90,7 +80,7 @@ class SocialQuickJoinController extends ControllerBase {
}
// Already a member.
- if ($group->getMember($this->currentUser())) {
+ if ($group->hasMember($this->currentUser())) {
// Set a message.
$this->messenger()->addMessage($this->t("You're already a member of this group."));
// Redirect to the group.
diff --git a/modules/social_features/social_group/modules/social_group_request/social_group_request.api.php b/modules/social_features/social_group/modules/social_group_request/social_group_request.api.php
index 9b3d33e67..a8f2de032 100644
--- a/modules/social_features/social_group/modules/social_group_request/social_group_request.api.php
+++ b/modules/social_features/social_group/modules/social_group_request/social_group_request.api.php
@@ -16,6 +16,11 @@
* @param array $group_types
* List of group types used in open social.
*
+ * @deprecated in social:11.2.0 and is removed from social:12.0.0. Use
+ * hook_social_group_join_method_usage instead.
+ *
+ * @see https://www.drupal.org/node/3254715
+ *
* @ingroup social_group_api
*/
function hook_social_group_request_alter(array &$group_types) {
diff --git a/modules/social_features/social_group/modules/social_group_request/social_group_request.module b/modules/social_features/social_group/modules/social_group_request/social_group_request.module
index 5cf5c1d35..16cbe1dc0 100644
--- a/modules/social_features/social_group/modules/social_group_request/social_group_request.module
+++ b/modules/social_features/social_group/modules/social_group_request/social_group_request.module
@@ -7,9 +7,9 @@
use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
-use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
@@ -18,7 +18,6 @@ use Drupal\field\Entity\FieldStorageConfig;
use Drupal\grequest\Plugin\GroupContentEnabler\GroupMembershipRequest;
use Drupal\group\Entity\GroupContentTypeInterface;
use Drupal\group\Entity\GroupContentInterface;
-use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;
use Drupal\message\Entity\Message;
@@ -65,6 +64,83 @@ function social_group_request_entity_base_field_info(EntityTypeInterface $entity
return $fields;
}
+/**
+ * Implements hook_social_group_join_method_usage_alter().
+ */
+function social_group_request_social_group_join_method_usage_alter(array &$items): void {
+ /** @var array $all_bundles */
+ $all_bundles = \Drupal::entityQuery('group_type')
+ ->accessCheck(FALSE)
+ ->execute();
+
+ $bundles = [];
+
+ foreach ($items as $item) {
+ if ($item['entity_type'] === 'group') {
+ $add = FALSE;
+
+ if (isset($item['field'])) {
+ if (isset($item['method'])) {
+ if (in_array('request', (array) $item['method'])) {
+ $add = TRUE;
+ }
+ }
+ else {
+ $add = TRUE;
+ }
+ }
+ elseif (in_array('request', (array) $item['method'])) {
+ $add = TRUE;
+ }
+
+ if ($add) {
+ $bundles = array_merge(
+ $bundles,
+ isset($item['bundle']) ? (array) $item['bundle'] : $all_bundles,
+ );
+ }
+ }
+ }
+
+ $query = \Drupal::entityQuery('group_type')->accessCheck(FALSE);
+
+ if (!empty($bundles)) {
+ $query->condition('id', $bundles, 'NOT IN');
+ }
+
+ /** @var array $bundles */
+ $bundles = $query->execute();
+
+ if (!empty($bundles)) {
+ $item = [
+ 'entity_type' => 'group',
+ 'field' => 'allow_request',
+ 'method' => 'request',
+ ];
+
+ if (count($bundles) < count($all_bundles)) {
+ $item['bundle'] = array_values($bundles);
+ }
+
+ $items[] = $item;
+ }
+}
+
+/**
+ * Implements hook_social_group_join_method_info().
+ */
+function social_group_request_social_group_join_method_info(): array {
+ return [
+ 'request' => [
+ 'title' => t('Request to join'),
+ 'description' => t('users can "request to join" this @entity_type_id which
+@entity_type_id managers approve/decline.'),
+ 'icon' => 'join_close',
+ 'weight' => 15,
+ ],
+ ];
+}
+
/**
* Implements hook_ENTITY_TYPE_insert().
*/
@@ -128,96 +204,37 @@ function social_group_request_theme_registry_alter(&$theme_registry) {
}
/**
- * Implements hook_preprocess_group().
+ * Implements hook_preprocess_HOOK().
*/
-function social_group_request_preprocess_group(&$variables) {
- /** @var \Drupal\group\Entity\GroupInterface $group */
- $group = $variables['group'];
- $group_type = $group->getGroupType();
-
- $account = \Drupal::currentUser();
- // If the user is already a member we don't bother processing any further.
- if ($group->getMember($account)) {
+function social_group_request_preprocess_group(array &$variables): void {
+ if (!\Drupal::config('social_group.settings')->get('social_group_type_required')) {
return;
}
- if (!$group_type->hasContentPlugin('group_membership_request')) {
- return;
- }
-
- // If user has a pending invite we should skip the request button.
- if (\Drupal::hasService('ginvite.invitation_loader')) {
- /** @var \Drupal\ginvite\GroupInvitationLoader $loader */
- $loader = \Drupal::service('ginvite.invitation_loader');
- $group_invites = count($loader->loadByProperties(['gid' => $group->id(), 'uid' => $account->id()]));
- if (NULL !== $group_invites && $group_invites > 0) {
- return;
- }
- }
-
- $group_types = ['flexible_group'];
- \Drupal::moduleHandler()->alter('social_group_request', $group_types);
-
- if (in_array($group_type->id(), $group_types)) {
- $join_methods = $group->get('field_group_allowed_join_method')->getValue();
-
- $request_option = in_array('request', array_column($join_methods, 'value'), FALSE);
- if (!$request_option) {
- $variables['allow_request'] = FALSE;
- return;
- }
- }
- else {
- $allow_request = $group->get('allow_request');
- if ($allow_request->isEmpty() || $allow_request->value == 0) {
- $variables['allow_request'] = FALSE;
- return;
- }
- }
+ /** @var \Drupal\social_group\SocialGroupInterface $group */
+ $group = $variables['group'];
- if ($account->isAnonymous()) {
- $variables['anonymous_request'] = TRUE;
- $variables['group_operations_url'] = Url::fromRoute('social_group_request.anonymous_request_membership', ['group' => $group->id()]);
- $variables['#attached']['library'][] = 'core/drupal.dialog.ajax';
- $variables['#attached']['library'][] = 'social_group_request/social_group_popup';
+ if (!$group->hasField('field_group_type')) {
return;
}
- if (
- !$group->hasPermission('request group membership', $account) ||
- !$group->hasField('allow_request')
- ) {
- $variables['allow_request'] = FALSE;
- return;
- }
+ /** @var \Drupal\social_group\JoinManagerInterface $manager */
+ $manager = \Drupal::service('plugin.manager.social_group.join');
- $variables['closed_group'] = TRUE;
- $variables['allow_request'] = TRUE;
- $variables['group_operations_url'] = Url::fromRoute('grequest.request_membership', ['group' => $group->id()]);
- $variables['cta'] = t('Request to join');
+ /** @var string $bundle */
+ $bundle = $group->getGroupType()->id();
- $contentTypeConfigId = $group
- ->getGroupType()
- ->getContentPlugin('group_membership_request')
- ->getContentTypeConfigId();
+ if ($manager->hasMethod($bundle, 'request')) {
+ $field = $group->field_group_type;
- $request = \Drupal::entityQuery('group_content')
- ->condition('type', $contentTypeConfigId)
- ->condition('gid', $group->id())
- ->condition('entity_id', $account->id())
- ->condition('grequest_status', GroupMembershipRequest::REQUEST_PENDING)
- ->count()
- ->execute();
+ if (!$field->isEmpty()) {
+ /** @var \Drupal\taxonomy\TermInterface $term */
+ $term = $field->entity;
- if ($request > 0) {
- $variables['requested'] = TRUE;
- $variables['group_operations_url'] = Url::fromRoute('social_group_request.cancel_request', ['group' => $group->id()]);
+ if ($term instanceof \Drupal\taxonomy\Entity\Term) { $variables['group_type'] = $term->getName();
+ $variables['group_type_icon'] = $term->field_group_type_icon->value; }
+ }
}
-
- $variables['#attached']['library'][] = 'social_group_request/social_group_popup';
- $variables['#attached']['library'][] = 'social_group_request/social_group_request_popup';
- $variables['#attached']['library'][] = 'core/drupal.dialog.ajax';
- $variables['#cache']['tags'][] = 'request-membership:' . $group->id();
}
/**
@@ -236,7 +253,7 @@ function social_group_request_activity_send_email_notifications_alter(array &$it
/**
* Implements hook_form_alter().
*/
-function social_group_request_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
+function social_group_request_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
$social_group_types = [
'open_group',
'closed_group',
@@ -245,19 +262,21 @@ function social_group_request_form_alter(array &$form, FormStateInterface $form_
\Drupal::moduleHandler()->alter('social_group_types', $social_group_types);
- $group_membership_add_forms = [];
- foreach ($social_group_types as $social_group_type) {
- $group_membership_add_forms[] = "group_content_{$social_group_type}-group_membership_add_form";
- }
-
- $route = \Drupal::routeMatch()->getRouteName();
- if (in_array($form_id, $group_membership_add_forms) && $route === 'grequest.group_request_membership_approve') {
+ if (
+ \Drupal::routeMatch()->getRouteName() === 'grequest.group_request_membership_approve' &&
+ preg_match('/^group_content_(' . implode('|', $social_group_types) . ')-group_membership_add_form$/', $form_id)
+ ) {
// Name of user which we're adding to the group.
- $user_name = $form['entity_id']['widget'][0]['target_id']['#default_value']->getDisplayName();
+ $user_name = $form['entity_id']['widget'][0]['target_id']['#default_value']
+ ->getDisplayName();
+
$form['question'] = [
'#type' => 'html_tag',
'#tag' => 'p',
- '#value' => t('Are you sure you want to approve the membership request for @name?', ['@name' => $user_name]),
+ '#value' => t(
+ 'Are you sure you want to approve the membership request for @name?',
+ ['@name' => $user_name],
+ ),
'#weight' => 1,
];
@@ -272,21 +291,35 @@ function social_group_request_form_alter(array &$form, FormStateInterface $form_
$form['actions']['submit']['#value'] = t('Yes');
}
+ elseif (preg_match(
+ '/^group_(' . implode('|', $social_group_types) . ')_(add|edit)_form$/',
+ $form_id,
+ )) {
+ /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
+ $form_object = $form_state->getFormObject();
- $group_forms = [];
- foreach ($social_group_types as $social_group_type) {
- $group_forms[] = "group_{$social_group_type}_edit_form";
- $group_forms[] = "group_{$social_group_type}_add_form";
- }
+ /** @var \Drupal\group\Entity\GroupInterface $group */
+ $group = $form_object->getEntity();
- if (in_array($form_id, $group_forms)) {
- /** @var \Drupal\group\Entity\GroupTypeInterface $group_type */
- $group_type = $form_state->getFormObject()->getEntity()->getGroupType();
+ $group_type = $group->getGroupType();
+ $prohibit = FALSE;
- $group_types = ['flexible_group'];
- \Drupal::moduleHandler()->alter('social_group_request', $group_types);
+ if ($group_type->hasContentPlugin('group_membership_request')) {
+ /** @var \Drupal\social_group\JoinManagerInterface $manager */
+ $manager = \Drupal::service('plugin.manager.social_group.join');
- if (in_array($group_type->id(), $group_types) || !$group_type->hasContentPlugin('group_membership_request')) {
+ /** @var string $bundle */
+ $bundle = $group_type->id();
+
+ if (!$manager->hasMethod($bundle, 'request')) {
+ $prohibit = TRUE;
+ }
+ }
+ else {
+ $prohibit = TRUE;
+ }
+
+ if ($prohibit) {
unset($form['allow_request']);
}
}
@@ -367,15 +400,6 @@ function social_group_request_preprocess_page_title(&$variables) {
$variables['#cache']['tags'][] = 'request-membership:' . $group->id();
}
-/**
- * Allowed join method values.
- */
-function social_group_request_allowed_join_method_values(FieldStorageConfig $definition, ContentEntityInterface $entity = NULL, $cacheable) {
- $allowed_values = $definition->getSetting('allowed_values');
- $allowed_values['request'] = t('Request to join');
- return $allowed_values;
-}
-
/**
* Implements hook_views_pre_view().
*/
diff --git a/modules/social_features/social_group/modules/social_group_request/src/Plugin/Block/SocialGroupRequestMembershipNotification.php b/modules/social_features/social_group/modules/social_group_request/src/Plugin/Block/SocialGroupRequestMembershipNotification.php
index e2ba82c30..231b0b5e2 100644
--- a/modules/social_features/social_group/modules/social_group_request/src/Plugin/Block/SocialGroupRequestMembershipNotification.php
+++ b/modules/social_features/social_group/modules/social_group_request/src/Plugin/Block/SocialGroupRequestMembershipNotification.php
@@ -6,14 +6,14 @@ use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Link;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslationManager;
use Drupal\Core\Url;
use Drupal\grequest\Plugin\GroupContentEnabler\GroupMembershipRequest;
-use Drupal\social_group\Entity\Group;
+use Drupal\social_group\JoinManagerInterface;
+use Drupal\social_group\SocialGroupInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -34,11 +34,9 @@ class SocialGroupRequestMembershipNotification extends BlockBase implements Cont
protected $account;
/**
- * Group entity.
- *
- * @var \Drupal\group\Entity\GroupInterface
+ * The group entity object.
*/
- protected $group;
+ protected ?SocialGroupInterface $group;
/**
* Entity type manger.
@@ -48,18 +46,9 @@ class SocialGroupRequestMembershipNotification extends BlockBase implements Cont
protected $entityTypeManager;
/**
- * Translation manager.
- *
- * @var \Drupal\Core\StringTranslation\TranslationManager
+ * The join manager.
*/
- protected $translation;
-
- /**
- * The module handler.
- *
- * @var \Drupal\Core\Extension\ModuleHandlerInterface
- */
- protected $moduleHandler;
+ private JoinManagerInterface $joinManager;
/**
* Constructs SocialGroupRequestMembershipNotification.
@@ -76,8 +65,8 @@ class SocialGroupRequestMembershipNotification extends BlockBase implements Cont
* The entity type manager.
* @param \Drupal\Core\StringTranslation\TranslationManager $translation
* The translation manager.
- * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
- * The module handler.
+ * @param \Drupal\social_group\JoinManagerInterface $join_manager
+ * The join manager.
*/
public function __construct(
array $configuration,
@@ -86,14 +75,14 @@ class SocialGroupRequestMembershipNotification extends BlockBase implements Cont
AccountInterface $account,
EntityTypeManagerInterface $entity_type_manager,
TranslationManager $translation,
- ModuleHandlerInterface $module_handler
+ JoinManagerInterface $join_manager
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->account = $account;
$this->group = _social_group_get_current_group();
$this->entityTypeManager = $entity_type_manager;
- $this->translation = $translation;
- $this->moduleHandler = $module_handler;
+ $this->setStringTranslation($translation);
+ $this->joinManager = $join_manager;
}
/**
@@ -107,7 +96,7 @@ class SocialGroupRequestMembershipNotification extends BlockBase implements Cont
$container->get('current_user'),
$container->get('entity_type.manager'),
$container->get('string_translation'),
- $container->get('module_handler')
+ $container->get('plugin.manager.social_group.join'),
);
}
@@ -115,40 +104,50 @@ class SocialGroupRequestMembershipNotification extends BlockBase implements Cont
* {@inheritdoc}
*/
public function build() {
- if (!$this->group->getGroupType()->hasContentPlugin('group_membership_request')) {
+ if ($this->group === NULL) {
+ return [];
+ }
+
+ $group_type = $this->group->getGroupType();
+
+ if (!$group_type->hasContentPlugin('group_membership_request')) {
return [];
}
- $group_types = ['flexible_group'];
- $this->moduleHandler->alter('social_group_request', $group_types);
+ /** @var string $bundle */
+ $bundle = $group_type->id();
+
+ if (
+ $this->joinManager->hasMethod($bundle, 'request') &&
+ $this->group->hasField('field_group_allowed_join_method')
+ ) {
+ $join_methods = $this->group->field_group_allowed_join_method->getValue();
- if (in_array($this->group->getGroupType()->id(), $group_types)) {
- $join_methods = $this->group->get('field_group_allowed_join_method')->getValue();
- $request_option = in_array('request', array_column($join_methods, 'value'), FALSE);
- if (!$request_option) {
+ if (!in_array('request', array_column($join_methods, 'value'))) {
return [];
}
}
else {
- $allow_request = $this->group->get('allow_request');
+ $allow_request = $this->group->allow_request;
+
if ($allow_request->isEmpty() || $allow_request->value == 0) {
return [];
}
}
- $contentTypeConfigId = $this->group
- ->getGroupType()
+ $content_type_config_id = $group_type
->getContentPlugin('group_membership_request')
->getContentTypeConfigId();
- $requests = $this->entityTypeManager->getStorage('group_content')->getQuery()
- ->condition('type', $contentTypeConfigId)
+ $requests = (int) $this->entityTypeManager->getStorage('group_content')
+ ->getQuery()
+ ->condition('type', $content_type_config_id)
->condition('gid', $this->group->id())
->condition('grequest_status', GroupMembershipRequest::REQUEST_PENDING)
->count()
->execute();
- if (!$requests) {
+ if ($requests === 0) {
return [];
}
@@ -157,8 +156,15 @@ class SocialGroupRequestMembershipNotification extends BlockBase implements Cont
'#tag' => 'div',
'#value' => $this->t('There @link to join this group.', [
'@link' => Link::fromTextAndUrl(
- $this->translation->formatPlural($requests, 'is (1) new request', 'are (@count) new requests'),
- Url::fromRoute('view.group_pending_members.membership_requests', ['arg_0' => $this->group->id()])
+ $this->getStringTranslation()->formatPlural(
+ $requests,
+ 'is (1) new request',
+ 'are (@count) new requests',
+ ),
+ Url::fromRoute(
+ 'view.group_pending_members.membership_requests',
+ ['arg_0' => $this->group->id()],
+ ),
)->toString(),
]),
'#attributes' => [
@@ -174,38 +180,45 @@ class SocialGroupRequestMembershipNotification extends BlockBase implements Cont
* {@inheritdoc}
*/
public function access(AccountInterface $account, $return_as_object = FALSE) {
- $is_group_page = isset($this->group);
- if ($this->group instanceof Group) {
- $is_group_manager = $this->group->hasPermission('administer members', $account);
- return AccessResult::allowedIf($is_group_page && $is_group_manager);
+ if ($this->group === NULL) {
+ $access = AccessResult::forbidden();
+ }
+ else {
+ $access = AccessResult::allowedIf(
+ $this->group->hasPermission('administer members', $account),
+ );
}
- return AccessResult::forbidden();
+ return $return_as_object ? $access : $access->isAllowed();
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
- $contexts = parent::getCacheContexts();
- // Ensure the context keeps track of the URL
- // so we don't see the message on every group.
- $contexts = Cache::mergeContexts($contexts, [
+ // Ensure the context keeps track of the URL, so we don't see the message on
+ // every group.
+ return Cache::mergeContexts(parent::getCacheContexts(), [
'url',
'user.permissions',
'route.group',
]);
- return $contexts;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
- return Cache::mergeTags(parent::getCacheTags(), [
- 'request-membership:' . $this->group->id(),
- 'group:' . $this->group->id(),
- ]);
+ $tags = parent::getCacheTags();
+
+ if ($this->group !== NULL) {
+ $tags = Cache::mergeTags($tags, [
+ 'request-membership:' . $this->group->id(),
+ 'group:' . $this->group->id(),
+ ]);
+ }
+
+ return $tags;
}
}
diff --git a/modules/social_features/social_group/modules/social_group_request/src/Plugin/Join/SocialGroupRequestJoin.php b/modules/social_features/social_group/modules/social_group_request/src/Plugin/Join/SocialGroupRequestJoin.php
new file mode 100644
index 000000000..f826b19ec
--- /dev/null
+++ b/modules/social_features/social_group/modules/social_group_request/src/Plugin/Join/SocialGroupRequestJoin.php
@@ -0,0 +1,160 @@
+<?php
+
+namespace Drupal\social_group_request\Plugin\Join;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\ginvite\GroupInvitationLoaderInterface;
+use Drupal\grequest\Plugin\GroupContentEnabler\GroupMembershipRequest;
+use Drupal\social_group\EntityMemberInterface;
+use Drupal\social_group\JoinBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a join plugin instance for joining after sending a request.
+ *
+ * @Join(
+ * id = "social_group_request_join",
+ * entityTypeId = "group",
+ * method = "request",
+ * weight = 20,
+ * )
+ */
+class SocialGroupRequestJoin extends JoinBase {
+
+ /**
+ * The group invitation loader.
+ */
+ private ?GroupInvitationLoaderInterface $loader = NULL;
+
+ /**
+ * The entity type manager.
+ */
+ private EntityTypeManagerInterface $entityTypeManager;
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(
+ ContainerInterface $container,
+ array $configuration,
+ $plugin_id,
+ $plugin_definition
+ ): self {
+ /** @var self $instance */
+ $instance = parent::create(
+ $container,
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ );
+
+ if ($container->has($id = 'ginvite.invitation_loader')) {
+ /** @var \Drupal\ginvite\GroupInvitationLoaderInterface $loader */
+ $loader = $container->get($id);
+
+ $instance->loader = $loader;
+ }
+
+ $instance->entityTypeManager = $container->get('entity_type.manager');
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function actions(EntityMemberInterface $entity, array &$variables): array {
+ $items = [];
+
+ /** @var \Drupal\social_group\SocialGroupInterface $group */
+ $group = $entity;
+
+ $group_type = $group->getGroupType();
+
+ if (!$group_type->hasContentPlugin('group_membership_request')) {
+ return $items;
+ }
+
+ // If user has a pending invite we should skip the request button.
+ if ($this->loader !== NULL) {
+ $group_invites = $this->loader->loadByProperties([
+ 'gid' => $group->id(),
+ 'uid' => $this->currentUser->id(),
+ ]);
+
+ if ($group_invites !== []) {
+ return $items;
+ }
+ }
+
+ $variables['#attached']['library'][] = 'core/drupal.dialog.ajax';
+ $variables['#attached']['library'][] = 'social_group_request/social_group_popup';
+
+ if ($this->currentUser->isAnonymous()) {
+ $items[] = [
+ 'label' => $this->t('Request to join'),
+ 'url' => Url::fromRoute(
+ 'social_group_request.anonymous_request_membership',
+ ['group' => $group->id()],
+ ),
+ 'attributes' => [
+ 'class' => ['btn-accent', 'use-ajax'],
+ ],
+ ];
+
+ return $items;
+ }
+
+ if (
+ !$group->hasPermission('request group membership', $this->currentUser) ||
+ !$group->hasField('allow_request')
+ ) {
+ return $items;
+ }
+
+ $types = $group
+ ->getGroupType()
+ ->getContentPlugin('group_membership_request')
+ ->getContentTypeConfigId();
+
+ $count = $this->entityTypeManager->getStorage('group_content')
+ ->getQuery()
+ ->condition('type', $types)
+ ->condition('gid', $group->id())
+ ->condition('entity_id', $this->currentUser->id())
+ ->condition('grequest_status', GroupMembershipRequest::REQUEST_PENDING)
+ ->range(0, 1)
+ ->count()
+ ->execute();
+
+ if ($count > 0) {
+ $items[] = $this->t('Request sent');
+
+ $items[] = Link::createFromRoute(
+ $this->t('Cancel request'),
+ 'social_group_request.cancel_request',
+ ['group' => $group->id()],
+ );
+ }
+ else {
+ $items[] = [
+ 'label' => $this->t('Request to join'),
+ 'url' => Url::fromRoute(
+ 'grequest.request_membership',
+ ['group' => $group->id()],
+ ),
+ 'attributes' => [
+ 'class' => ['btn-accent', 'use-ajax'],
+ ],
+ ];
+ }
+
+ $variables['#attached']['library'][] = 'social_group_request/social_group_request_popup';
+ $variables['#cache']['tags'][] = 'request-membership:' . $group->id();
+
+ return $items;
+ }
+
+}
diff --git a/modules/social_features/social_group/modules/social_group_request/src/SocialGroupRequestConfigOverride.php b/modules/social_features/social_group/modules/social_group_request/src/SocialGroupRequestConfigOverride.php
index 0b76035c8..dd00dee57 100644
--- a/modules/social_features/social_group/modules/social_group_request/src/SocialGroupRequestConfigOverride.php
+++ b/modules/social_features/social_group/modules/social_group_request/src/SocialGroupRequestConfigOverride.php
@@ -124,15 +124,6 @@ class SocialGroupRequestConfigOverride implements ConfigFactoryOverrideInterface
}
}
- $config_name = 'field.storage.group.field_group_allowed_join_method';
- if (in_array($config_name, $names)) {
- $overrides[$config_name] = [
- 'settings' => [
- 'allowed_values_function' => 'social_group_request_allowed_join_method_values',
- ],
- ];
- }
-
$config_name = 'views.view.group_pending_members';
if (in_array($config_name, $names)) {
$overrides[$config_name] = [
diff --git a/modules/social_features/social_group/modules/social_group_secret/social_group_secret.module b/modules/social_features/social_group/modules/social_group_secret/social_group_secret.module
index bf527ea63..fd5e311f6 100644
--- a/modules/social_features/social_group/modules/social_group_secret/social_group_secret.module
+++ b/modules/social_features/social_group/modules/social_group_secret/social_group_secret.module
@@ -215,3 +215,16 @@ function social_group_secret_form_node_form_alter(&$form, FormStateInterface $fo
}
}
}
+
+/**
+ * Implements hook_social_group_join_method_usage().
+ */
+function social_group_secret_social_group_join_method_usage(): array {
+ return [
+ [
+ 'entity_type' => 'group',
+ 'bundle' => 'secret_group',
+ 'method' => 'added',
+ ],
+ ];
+}
diff --git a/modules/social_features/social_group/social_group.api.php b/modules/social_features/social_group/social_group.api.php
index 3e2aeef5a..294a35fd0 100644
--- a/modules/social_features/social_group/social_group.api.php
+++ b/modules/social_features/social_group/social_group.api.php
@@ -169,6 +169,111 @@ function hook_social_group_group_visibility_description_alter($key, &$descriptio
}
}
+/**
+ * Alter the list of join plugin definitions.
+ *
+ * @param array $info
+ * The join plugin definitions to be altered.
+ *
+ * @see \Drupal\social_group\Annotation\Join
+ * @see \Drupal\social_group\JoinManager
+ */
+function hook_social_group_join_info_alter(array &$info) {
+ if (isset($info['social_group_request_join'])) {
+ unset($info['social_group_request_join']['entityTypeId']);
+ }
+}
+
+/**
+ * Define join methods.
+ *
+ * @return array
+ * An associative array of join method definitions. The keys are the
+ * identifiers. The values are associative arrays that should contain the
+ * following elements:
+ * - title: The human-readable name of the join method. If this should be
+ * translated, create a \Drupal\Core\StringTranslation\TranslatableMarkup
+ * object.
+ * - description: The description of the join method. The "@entity_type_id"
+ * placeholder will be replaced automatically. If this should be translated,
+ * create a \Drupal\Core\StringTranslation\TranslatableMarkup object.
+ * - icon: The join method unique icon.
+ * - weight: Integer weight used for sorting join methods.
+ *
+ * @see _social_group_allowed_values_callback()
+ * @see social_group_allowed_join_method_description()
+ * @see social_group_form_alter()
+ *
+ * @ingroup social_group_api
+ */
+function hook_social_group_join_method_info() {
+ return [
+ 'direct' => [
+ 'title' => t('Open to join'),
+ 'description' => t('users can join this @entity_type_id without approval.'),
+ 'icon' => 'join_open',
+ 'weight' => 10,
+ ],
+ 'added' => [
+ 'title' => t('Invite only'),
+ 'description' => t('users can only join this @entity_type_id if they are
+added/invited by @entity_type_id managers.'),
+ 'icon' => 'invite',
+ 'weight' => 20,
+ ],
+ ];
+}
+
+/**
+ * Define entity type bundles that support join methods.
+ *
+ * @return array
+ * An array of entity types/bundles definitions that support join methods. The
+ * values are associative arrays that should contain the following elements:
+ * - entity_type: The entity type ID.
+ * - bundle: (optional) The bundle(s).
+ * - field: (optional) The field contains a list of supported join methods
+ * when the "method" item isn't defined. Otherwise, the field indicates if
+ * an entity can use the join method defined in the "method" item.
+ * - method: (optional) The join method(s).
+ *
+ * @see \Drupal\social_group\JoinManager::relations()
+ *
+ * @ingroup social_group_api
+ */
+function hook_social_group_join_method_usage() {
+ return [
+ [
+ 'entity_type' => 'group',
+ 'bundle' => 'flexible_group',
+ 'field' => 'field_group_allowed_join_method',
+ ],
+ ];
+}
+
+/**
+ * Alter the list of relations between entity type bundles and join methods.
+ *
+ * @param array $items
+ * The join method fields to be altered.
+ *
+ * @see \Drupal\social_group\JoinManager::relations()
+ */
+function hook_social_group_join_method_usage_alter(array &$items) {
+ foreach ($items as &$item) {
+ if (
+ $item['entity_type'] === 'group' &&
+ isset($item['bundle']) &&
+ $item['bundle'] === 'closed_group' &&
+ !isset($item['field'])
+ ) {
+ $item['field'] = 'field_group_allowed_join_method';
+
+ break;
+ }
+ }
+}
+
/**
* Provide a description for a given key from the content visibility #options.
*
diff --git a/modules/social_features/social_group/social_group.module b/modules/social_features/social_group/social_group.module
index 750ee4144..02311f459 100644
--- a/modules/social_features/social_group/social_group.module
+++ b/modules/social_features/social_group/social_group.module
@@ -8,22 +8,32 @@
use Drupal\block\Entity\Block;
use Drupal\bootstrap\Bootstrap;
use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\SortArray;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
+use Drupal\Core\Block\BlockPluginInterface;
+use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Url;
+use Drupal\group\Entity\GroupContent;
use Drupal\group\Entity\GroupContentInterface;
use Drupal\group\Entity\GroupContentType;
use Drupal\group\Entity\GroupInterface;
-use Drupal\group\Entity\GroupContent;
use Drupal\group\Entity\GroupType;
use Drupal\group\GroupMembership;
+use Drupal\image\Entity\ImageStyle;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
@@ -33,20 +43,14 @@ use Drupal\social_group\Element\SocialGroupEntityAutocomplete;
use Drupal\social_group\Entity\Access\SocialGroupAccessControlHandler;
use Drupal\social_group\Entity\Group;
use Drupal\social_group\Form\SocialGroupAddForm;
-use Drupal\views\ViewExecutable;
+use Drupal\social_group\GroupContentVisibilityUpdate;
+use Drupal\social_group\SocialGroupInterface;
+use Drupal\user\Entity\Role;
+use Drupal\user\Entity\User;
use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\Plugin\views\row\EntityRow;
-use Drupal\Core\Url;
-use Drupal\Core\Cache\Cache;
-use Drupal\image\Entity\ImageStyle;
-use Drupal\Core\Block\BlockPluginInterface;
-use Drupal\user\Entity\Role;
-use Drupal\user\Entity\User;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\social_group\GroupContentVisibilityUpdate;
-use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\views\ViewExecutable;
use Drupal\views_bulk_operations\ViewsBulkOperationsBatch;
/**
@@ -64,6 +68,15 @@ function social_group_theme() {
'flag__mute_group_notifications' => [
'base hook' => 'flag',
],
+ 'join' => [
+ 'variables' => [
+ 'primary' => NULL,
+ 'secondaries' => NULL,
+ 'attributes' => [],
+ 'entity' => NULL,
+ ],
+ 'file' => 'social_group.theme.inc',
+ ],
];
}
@@ -101,9 +114,10 @@ function template_preprocess_group_settings_help(array &$variables) {
* TRUE if a user may edit a existings groups group type.
*/
function social_group_group_type_permission_check() {
- $user = \Drupal::currentUser();
// Get the Group object from the route.
- $group = _social_group_get_current_group();
+ if (($group = _social_group_get_current_group()) === NULL) {
+ return FALSE;
+ }
// Check if we have a default visibility, if we don't it must be a custom
// group type, we need to have a visibility in order to update group content.
@@ -113,8 +127,8 @@ function social_group_group_type_permission_check() {
return FALSE;
}
- // Otherwise return true when we are able to edit the current group type.
- return ($group instanceof GroupInterface && $user->hasPermission('edit group types'));
+ // Otherwise, return true when we are able to edit the current group type.
+ return \Drupal::currentUser()->hasPermission('edit group types');
}
/**
@@ -212,6 +226,24 @@ function _social_group_get_group_labels() {
}
+/**
+ * Implements hook_preprocess().
+ */
+function social_group_preprocess(array &$variables, string $hook): void {
+ if (
+ isset(
+ $variables['elements']['#view_mode'],
+ $variables['elements']['#' . $hook],
+ ) &&
+ in_array($variables['elements']['#view_mode'], ['hero', 'statistic'])
+ ) {
+ /** @var \Drupal\social_group\JoinManagerInterface $manager */
+ $manager = \Drupal::service('plugin.manager.social_group.join');
+
+ $manager->preprocess($variables, $hook);
+ }
+}
+
/**
* Prepares variables for profile templates.
*
@@ -226,8 +258,10 @@ function _social_group_get_group_labels() {
function social_group_preprocess_group(array &$variables) {
/** @var \Drupal\social_group\GroupStatistics $group_statistics */
$group_statistics = \Drupal::service('social_group.group_statistics');
- /** @var \Drupal\group\Entity\GroupInterface $group */
+
+ /** @var \Drupal\social_group\SocialGroupInterface $group */
$group = $variables['group'];
+
$variables['title'] = $group->label();
$variables['joined'] = FALSE;
$variables['closed_group'] = FALSE;
@@ -241,68 +275,30 @@ function social_group_preprocess_group(array &$variables) {
$variables['group_type_id'] = $group_type_id;
$variables['group_type'] = $group->getGroupType()->label();
- $group_types = ['flexible_group'];
- \Drupal::moduleHandler()->alter('social_group_request', $group_types);
-
- if (in_array($group_type_id, $group_types)
- && $group->hasField('field_group_type')
- && !empty($term = $group->get('field_group_type')->entity)
- && \Drupal::config('social_group.settings')
- ->get('social_group_type_required')) {
- $variables['group_type'] = $term->getName();
- $variables['group_type_icon'] = $term->get('field_group_type_icon')->value;
- }
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = \Drupal::service('renderer');
// Render the group settings help, gear icon with popover.
$group_settings_help = _social_group_render_group_settings_hero($group);
- $variables['group_settings_help'] = \Drupal::service('renderer')
- ->renderPlain($group_settings_help);
+ $variables['group_settings_help'] = $renderer->renderPlain($group_settings_help);
$account = \Drupal::currentUser();
- // Prevent access to mute group notifications if a user is not a group member.
- if (!in_array($variables['view_mode'], [
- 'statistic',
- 'hero',
- ]) || !$group->getMember($account)) {
- unset($variables['content']['flag_mute_group_notifications']);
- }
- // Set joined to true for teaser when current logged in
- // user is member of the group.
- if ($group->getMember($account)) {
- $variables['joined'] = TRUE;
- if ($group->hasPermission('leave group', $account)) {
- $variables['group_operations_url'] = Url::fromRoute('entity.group.leave', ['group' => $group->id()]);
- }
- }
- elseif ($group->hasPermission('join group', $account)) {
- // @todo switch this to get URL from routes correctly.
- $variables['group_operations_url'] = Url::fromRoute('entity.group.join', ['group' => $group->id()]);
+ if (
+ in_array($variables['view_mode'], ['statistic', 'hero']) &&
+ $group->hasMember($account)
+ ) {
+ if ($account->isAuthenticated()) {
+ $content = $renderer->render($variables['content']['flag_mute_group_notifications']);
- if (
- in_array($group_type_id, $group_types) &&
- !social_group_flexible_group_can_join_directly($group)
- ) {
- $variables['group_operations_url'] = Url::fromRoute('entity.group.join', ['group' => $group->id()]);
- $variables['closed_group'] = TRUE;
- $variables['cta'] = t('Invitation only');
+ if ((string) $content) {
+ $variables['join']['#secondaries'][] = $content;
+ }
}
}
- // If the group type is a closed_group.
- elseif ($group_type_id == 'closed_group' && !$group->hasPermission('manage all groups', $account)) {
- // Users can only be invited.
- $variables['group_operations_url'] = Url::fromRoute('entity.group.join', ['group' => $group->id()]);
- $variables['closed_group'] = TRUE;
- $variables['cta'] = t('Invitation only');
- }
- // Non logged in users should still see "join" button on a group page.
- if (
- $account->isAnonymous() &&
- (in_array($group_type_id, $group_types) &&
- social_group_flexible_group_can_join_directly($group) ||
- in_array($group_type_id, ['public_group']))
- ) {
- $variables['group_operations_url'] = Url::fromRoute('entity.group.join', ['group' => $group->id()]);
+ // Prevent access to mute group notifications if a user is not a group member.
+ else {
+ unset($variables['content']['flag_mute_group_notifications']);
}
// Add the hero styled image.
@@ -653,15 +649,85 @@ function social_group_group_visibility_description($key) {
}
/**
- * Returns a description array for the field_group_allowed_join_method options.
+ * Sets dynamic allowed values for the join method field.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
+ * The field storage definition.
+ * @param \Drupal\Core\Entity\FieldableEntityInterface|null $entity
+ * (optional) The entity object.
+ */
+function _social_group_allowed_values_callback(
+ FieldStorageDefinitionInterface $definition,
+ FieldableEntityInterface $entity = NULL
+): array {
+ $values = [];
+ $hook = 'social_group_join_method_info';
+
+ foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) {
+ /** @var callable $function */
+ $function = $module . '_' . $hook;
+
+ foreach ($function() as $id => $item) {
+ $values[] = ['id' => $id] + $item;
+ }
+ }
+
+ usort($values, [SortArray::class, 'sortByWeightElement']);
+
+ return array_column($values, 'title', 'id');
+}
+
+/**
+ * Implements hook_social_group_join_method_usage().
+ */
+function social_group_social_group_join_method_usage(): array {
+ return [
+ [
+ 'entity_type' => 'group',
+ 'bundle' => 'closed_group',
+ 'method' => 'added',
+ ],
+ [
+ 'entity_type' => 'group',
+ 'bundle' => ['open_group', 'public_group'],
+ 'method' => ['direct', 'added'],
+ ],
+ ];
+}
+
+/**
+ * Implements hook_social_group_join_method_info().
+ */
+function social_group_social_group_join_method_info(): array {
+ return [
+ 'direct' => [
+ 'title' => t('Open to join'),
+ 'description' => t('users can join this @entity_type_id without approval.'),
+ 'icon' => 'join_open',
+ 'weight' => 10,
+ ],
+ 'added' => [
+ 'title' => t('Invite only'),
+ 'description' => t('users can only join this @entity_type_id if they are
+added/invited by @entity_type_id managers.'),
+ 'icon' => 'invite',
+ 'weight' => 20,
+ ],
+ ];
+}
+
+/**
+ * Returns a description for the field_group_allowed_join_method options.
*
* @param string $key
* The join method key.
- *
- * @return string
- * The render array containing the description.
+ * @param string $entity_type_id
+ * (optional) The entity type ID. Defaults to 'group'.
*/
-function social_group_allowed_join_method_description($key) {
+function social_group_allowed_join_method_description(
+ string $key,
+ string $entity_type_id = 'group'
+): string {
$description = '';
// We need it to be specified otherwise we can't build the markup.
@@ -669,37 +735,62 @@ function social_group_allowed_join_method_description($key) {
return $description;
}
- // Add explanatory descriptive text after the icon.
- switch ($key) {
- case 'direct':
- $description = '<p>';
- $description .= '<p><strong><svg class="icon-small"><use xlink:href="#icon-join_open"></use></svg></strong>';
- $description .= '<strong>' . t('Open to join')->render() . '</strong>';
- $description .= ' - ' . t('users can join this group without approval.')->render();
- $description .= '</p>';
- break;
+ $hook = 'social_group_join_method_info';
- case 'request':
- $description = '<p>';
- $description .= '<p><strong><svg class="icon-small"><use xlink:href="#icon-join_close"></use></svg></strong>';
- $description .= '<strong>' . t('Request to join')->render() . '</strong>';
- $description .= ' - ' . t('users can "request to join" this group which group managers approve/decline.')->render();
- $description .= '</p>';
- break;
+ if (!($modules = \Drupal::moduleHandler()->getImplementations($hook))) {
+ return $description;
+ }
- case 'added':
- $description = '<p>';
- $description .= '<p><strong><svg class="icon-small"><use xlink:href="#icon-invite"></use></svg></strong>';
- $description .= '<strong>' . t('Invite only')->render() . '</strong>';
- $description .= ' - ' . t('users can only join this group if they are added/invited by group managers.')->render();
- $description .= '</p>';
- break;
+ $found = FALSE;
+
+ foreach ($modules as $module) {
+ /** @var callable $function */
+ $function = $module . '_' . $hook;
+
+ foreach ($function() as $id => $item) {
+ if ($id === $key) {
+ $found = TRUE;
+ break 2;
+ }
+ }
+ }
+
+ if (!$found || !isset($item)) {
+ return $description;
+ }
+
+ /** @var \Drupal\Core\StringTranslation\TranslatableMarkup $text */
+ $text = $item['description'];
+
+ $definition = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+
+ if ($definition === NULL) {
+ return $description;
}
+ // @codingStandardsIgnoreStart
+ $text = t(
+ $text->getUntranslatedString(),
+ $text->getArguments() + [
+ '@entity_type_id' => $definition->getSingularLabel(),
+ ],
+ );
+ // @codingStandardsIgnoreEnd
+
+ // Add explanatory descriptive text after the icon.
+ $description = '<p>';
+ $description .= '<p><strong><svg class="icon-small"><use xlink:href="#icon-' . $item['icon'] . '"></use></svg></strong>';
+ $description .= '<strong>' . $item['title']->render() . '</strong>';
+ $description .= ' - ' . $text->render();
+ $description .= '</p>';
+
// Allow modules to provide their own markup for a given key in the
// join method #options array.
- \Drupal::moduleHandler()
- ->alter('social_group_allowed_join_method_description', $key, $description);
+ \Drupal::moduleHandler()->alter(
+ 'social_group_allowed_join_method_description',
+ $key,
+ $description,
+ );
return $description;
}
@@ -827,8 +918,7 @@ function social_group_form_alter(&$form, FormStateInterface $form_state, $form_i
// Change the form when adding members directly in groups.
if (
in_array($form_id, $membership_add_forms, TRUE) &&
- \Drupal::routeMatch()
- ->getRouteName() !== 'grequest.group_request_membership_approve'
+ \Drupal::routeMatch()->getRouteName() !== 'grequest.group_request_membership_approve'
) {
// Lets add the new select 2 widget to add members to a group.
$form['entity_id']['widget'][0]['target_id'] = $helper->addMemberFormField();
@@ -841,6 +931,12 @@ function social_group_form_alter(&$form, FormStateInterface $form_state, $form_i
}
}
+ $form_object = $form_state->getFormObject();
+
+ if ($form_object instanceof EntityFormInterface) {
+ $entity = $form_object->getEntity();
+ }
+
// Check if form is group content create form.
if (isset($form['#entity_type']) && $form['#entity_type'] === 'node') {
// Add custom validate handler to check if groups field values in node.
@@ -853,17 +949,14 @@ function social_group_form_alter(&$form, FormStateInterface $form_state, $form_i
// We don't want to override the form::save in group.
$form['actions']['submit']['#submit'][] = '_social_group_node_form_submit';
}
- else {
+ elseif (isset($entity)) {
// If node don't belong to any group as a group content plugin
// we don't need group visibility field.
- if (method_exists($form_state->getFormObject(), 'getEntity')) {
- /** @var \Drupal\Core\Entity\EntityInterface $node */
- $node = $form_state->getFormObject()->getEntity();
- /** @var \Drupal\group\Plugin\GroupContentEnablerManagerInterface $gc_manager */
- $gc_manager = \Drupal::service('plugin.manager.group_content_enabler');
- if (!$gc_manager->getGroupContentTypeIds('group_node:' . $node->bundle())) {
- unset($form['groups']);
- }
+ /** @var \Drupal\group\Plugin\GroupContentEnablerManagerInterface $gc_manager */
+ $gc_manager = \Drupal::service('plugin.manager.group_content_enabler');
+
+ if (!$gc_manager->getGroupContentTypeIds('group_node:' . $entity->bundle())) {
+ unset($form['groups']);
}
}
}
@@ -884,11 +977,13 @@ function social_group_form_alter(&$form, FormStateInterface $form_state, $form_i
if (in_array($form_id, $group_forms['edit'])) {
$social_group_form = SocialGroupAddForm::create(\Drupal::getContainer());
$group_type_element = $social_group_form->getGroupTypeElement(TRUE);
+
// Get the current group.
- $group = _social_group_get_current_group();
- // Set the default value in the form.
- $group_type_element['widget']['#default_value'] = $group->getGroupType()
- ->id();
+ if (($group = _social_group_get_current_group()) !== NULL) {
+ // Set the default value in the form.
+ $group_type_element['widget']['#default_value'] = $group->getGroupType()
+ ->id();
+ }
// If user doesn't have permission to change group types disable it.
// Or if group types can't be edited due to visibility issues.
@@ -923,9 +1018,11 @@ function social_group_form_alter(&$form, FormStateInterface $form_state, $form_i
$form['actions']['submit']['#submit'][] = '_social_group_type_edit_submit';
}
- if (in_array($form_id, $group_forms['delete'])) {
+ if (
+ in_array($form_id, $group_forms['delete']) &&
+ ($group = _social_group_get_current_group()) !== NULL
+ ) {
// Add custom submit handler to delete all content of the group.
- $group = _social_group_get_current_group();
$form['description']['#markup'] = t('Are you sure you want to delete your group "@group" along with all of the posts, events and topics inside this group? This action cannot be undone.', ['@group' => $group->label()]);
$form['actions']['cancel'] = [
'#type' => 'submit',
@@ -960,33 +1057,78 @@ function social_group_form_alter(&$form, FormStateInterface $form_state, $form_i
}
}
- $group_types = ['flexible_group'];
- \Drupal::moduleHandler()->alter('social_group_request', $group_types);
+ if (
+ isset($entity) &&
+ $form_object instanceof EntityFormInterface &&
+ in_array($form_object->getOperation(), ['default', 'add', 'edit'])
+ ) {
+ /** @var \Drupal\social_group\JoinManagerInterface $manager */
+ $manager = \Drupal::service('plugin.manager.social_group.join');
+
+ $found = FALSE;
+ $entity_type_id = $entity->getEntityTypeId();
+
+ $bundle = $entity->getEntityType()->getBundleEntityType() === NULL
+ ? NULL : $entity->bundle();
+
+ foreach ($manager->relations() as $data) {
+ if (
+ isset($data['field'], $form[$data['field']]) &&
+ !isset($data['method']) &&
+ $data['entity_type'] === $entity_type_id &&
+ (
+ !isset($data['bundle']) &&
+ $bundle === NULL ||
+ in_array($bundle, (array) $data['bundle'])
+ )
+ ) {
+ $found = TRUE;
+ break;
+ }
+ }
- $group_form_ids = [];
- foreach ($group_types as $group_type) {
- $group_form_ids[] = 'group_' . $group_type . '_edit_form';
- $group_form_ids[] = 'group_' . $group_type . '_add_form';
- }
+ if ($found && isset($data)) {
+ $field = &$form[$data['field']]['widget'];
- if (isset($form['field_group_allowed_join_method']) && in_array($form_id, $group_form_ids)) {
- $join_method_default_value = 'added';
- // Ensure we have a better descriptive label.
- if (array_key_exists('added', $form['field_group_allowed_join_method']['widget']['#options'])) {
- $form['field_group_allowed_join_method']['widget']['#options']['added'] = t('Invite only');
- }
- if (array_key_exists('direct', $form['field_group_allowed_join_method']['widget']['#options'])) {
- $form['field_group_allowed_join_method']['widget']['#options']['direct'] = t('Open to join');
- }
- // If directly exists it's becoming the default.
- if (in_array('direct', $form['field_group_allowed_join_method']['widget']['#default_value'])) {
- $join_method_default_value = 'direct';
- }
- elseif (in_array('request', $form['field_group_allowed_join_method']['widget']['#default_value'])) {
- $join_method_default_value = 'request';
+ if ($field['#type'] === 'checkboxes') {
+ $field['#type'] = 'radios';
+ }
+
+ if ($entity->isNew()) {
+ $hook = 'social_group_join_method_info';
+ $join_methods = [];
+
+ foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) {
+ /** @var callable $function */
+ $function = $module . '_' . $hook;
+
+ foreach ($function() as $id => $join_method) {
+ $join_methods[] = [
+ 'id' => $id,
+ 'weight' => $join_method['weight'],
+ ];
+ }
+ }
+
+ usort($join_methods, [SortArray::class, 'sortByWeightElement']);
+
+ $join_methods = array_column($join_methods, 'id');
+ $join_method = array_pop($join_methods);
+ $join_methods = array_reverse($join_methods);
+
+ foreach ($join_methods as $current_join_method) {
+ if (in_array($current_join_method, $field['#default_value'])) {
+ $default_join_method = $current_join_method;
+ break;
+ }
+ }
+
+ $field['#default_value'] = $default_join_method ?? reset($join_method);
+ }
+ elseif (isset($field['#default_value']) && is_array($field['#default_value'])) {
+ $field['#default_value'] = reset($field['#default_value']);
+ }
}
- $form['field_group_allowed_join_method']['widget']['#type'] = 'radios';
- $form['field_group_allowed_join_method']['widget']['#default_value'] = $join_method_default_value;
}
// Exposed Filter block on the all-groups overview.
@@ -1049,10 +1191,31 @@ function _social_group_cross_posting_group_type_validate(array $element, FormSta
/**
* Function to validate entries against group members.
+ *
+ * @param array $element
+ * The element being processed.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ * @param array $complete_form
+ * The complete form structure.
+ *
+ * @deprecated in social:11.1.0 and is removed from social:12.0.0. Use
+ * \Drupal\social_group\Element\SocialGroupEntityAutocomplete::validateEntityAutocompleteSelect2()
+ * instead.
+ *
+ * @see https://www.drupal.org/node/3254715
*/
-function _social_group_unique_members($element, &$form_state, $complete_form) {
+function _social_group_unique_members(
+ array &$element,
+ FormStateInterface $form_state,
+ array $complete_form
+): void {
// Call the autocomplete function to make sure enrollees are unique.
- SocialGroupEntityAutocomplete::validateEntityAutocomplete($element, $form_state, $complete_form, TRUE);
+ SocialGroupEntityAutocomplete::validateEntityAutocompleteSelect2(
+ $element,
+ $form_state,
+ $complete_form,
+ );
}
/**
@@ -1508,33 +1671,36 @@ function social_group_menu_local_tasks_alter(&$data, $route_name) {
unset($data['tabs'][0]['group.edit_form']);
}
- $user = \Drupal::currentUser();
+ $account = \Drupal::currentUser();
+
// Get the Group object from the route.
$group = Drupal::routeMatch()->getParameter('group');
- if ($group instanceof GroupInterface) {
- /** @var \Drupal\group\Entity\GroupTypeInterface $group_type */
- $group_type = $group->getGroupType()->id();
+ if (
+ $group instanceof SocialGroupInterface &&
// Check if it's a closed group.
- if ($group_type === 'closed_group') {
- // And if the user is not user 1.
- if ($user->id() !== 1) {
- if ($user->hasPermission('manage all groups')) {
- return;
- }
- // If the user is not an member of this group.
- if (!$group->getMember($user)) {
- // Disable these local tasks.
- $data['tabs'][0]['group.view'] = [];
- if (!$group->hasPermission('administer members', $user)) {
- $data['tabs'][0]['group.content'] = [];
- }
- $data['tabs'][0]['social_group.events'] = [];
- $data['tabs'][0]['social_group.topics'] = [];
- }
+ $group->getGroupType()->id() === 'closed_group' &&
+ // And if the user is not user 1.
+ $account->id() !== 1
+ ) {
+ if ($account->hasPermission('manage all groups')) {
+ return;
+ }
+
+ // If the user is not an member of this group.
+ if (!$group->getMember($account)) {
+ // Disable these local tasks.
+ $data['tabs'][0]['group.view'] = [];
+
+ if (!$group->hasPermission('administer members', $account)) {
+ $data['tabs'][0]['group.content'] = [];
}
+
+ $data['tabs'][0]['social_group.events'] = [];
+ $data['tabs'][0]['social_group.topics'] = [];
}
}
+
// We don't want to display the "Nodes" tab on groups anymore. Before, we
// just disabled the view on hook update, but sometimes it is back to us,
// this is the reason why we want to hide the tab. Also, we don't want to
@@ -1598,12 +1764,9 @@ function _social_group_get_member_profile(GroupContent $group_content) {
* Gets current Group entity from the route.
*
* @param \Drupal\node\NodeInterface|null $node
- * (optional) The node object or NULL.
- *
- * @return \Drupal\group\Entity\GroupInterface|null
- * Returns the group object.
+ * (optional) The node entity object. Defaults to NULL.
*/
-function _social_group_get_current_group($node = NULL) {
+function _social_group_get_current_group($node = NULL): ?SocialGroupInterface {
$cache = &drupal_static(__FUNCTION__, []);
// For the same $node input, within the same request the return is always
@@ -1630,8 +1793,10 @@ function _social_group_get_current_group($node = NULL) {
->load($group);
}
else {
- $node = is_object($node) ? $node : \Drupal::routeMatch()
- ->getParameter('node');
+ if (!is_object($node)) {
+ $node = \Drupal::routeMatch()->getParameter('node');
+ }
+
if (is_object($node)) {
$node_entity = [
'target_type' => 'node',
@@ -1653,6 +1818,7 @@ function _social_group_get_current_group($node = NULL) {
$cache[$nid] = $group ?? FALSE;
}
+ /** @var \Drupal\social_group\SocialGroupInterface|null $group */
return $group;
}
@@ -1824,7 +1990,7 @@ function social_group_block_access(Block $block, $operation, AccountInterface $a
if ($account->hasPermission('manage all groups')) {
return AccessResult::neutral();
}
- elseif (!$group->getMember($user)) {
+ elseif (!$group->hasMember($user)) {
// If it is closed and the current user is not an member of this group,
// then it is not allowed to see these blocks.
$forbidden_blocks = [
@@ -2669,11 +2835,11 @@ function social_group_preprocess_field(&$variables) {
$formatter = $variables['element']['#formatter'];
if (in_array($formatter, ['address_plain', 'address_default'])) {
$entity = $variables['element']['#object'];
- if ($entity && $entity instanceof GroupInterface) {
+ if ($entity instanceof SocialGroupInterface) {
$social_group_settings = \Drupal::config('social_group.settings');
$address_visibility_settings = $social_group_settings->get('address_visibility_settings');
if (isset($address_visibility_settings['street_code_private']) && !empty($address_visibility_settings['street_code_private'])) {
- if (!$entity->getMember(\Drupal::currentUser())) {
+ if (!$entity->hasMember(\Drupal::currentUser())) {
switch ($formatter) {
case 'address_plain':
if (isset($variables['items'][0]['content']['#address_line1'])) {
@@ -2777,29 +2943,113 @@ function social_group_render_tooltip($field_name, TranslatableMarkup $data_title
],
];
- $variables['popover'] = $build;
-
return $build;
}
/**
- * Implements template_preprocess_form_element().
+ * Implements hook_preprocess_HOOK().
*/
-function social_group_preprocess_fieldset(&$variables) {
+function social_group_preprocess_fieldset(array &$variables): void {
// Make sure our flexible group visibility field renders a tooltip, since
// this field is rendered as fieldset with legend and radios as children
// we need to do it in this preprocess.
$element = $variables['element'];
- if (!empty($element['#field_name']) && $element['#field_name'] === 'field_content_visibility') {
+
+ if (empty($element['#field_name'])) {
+ return;
+ }
+
+ if ($element['#field_name'] === 'field_content_visibility') {
$description = '';
- foreach ($element['#options'] as $key => $label) {
+
+ /** @var string $key */
+ foreach (array_keys($element['#options']) as $key) {
$description .= social_group_allowed_visibility_description($key);
}
// Render a specific tooltip based on a field name and description.
// This is done in the fieldset, next to the <legend>.
- $variables['popover'] = social_group_render_tooltip('field_content_visibility', t('Visibility'), $description);
+ $variables['popover'] = social_group_render_tooltip(
+ 'field_content_visibility',
+ t('Visibility'),
+ $description,
+ );
+
+ return;
+ }
+
+ $parameters = \Drupal::routeMatch()->getParameters();
+
+ if ($parameters->has('entity_type_id')) {
+ $entity_type_id = $parameters->get('entity_type_id');
+
+ if ($parameters->has('bundle_parameter')) {
+ /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $type */
+ $type = $parameters->get($parameters->get('bundle_parameter'));
+
+ $bundle = $type->id();
+ }
+ }
+ else {
+ $found = FALSE;
+
+ foreach ($parameters->all() as $parameter) {
+ if ($parameter instanceof EntityInterface) {
+ $entity_type_id = $parameter->getEntityTypeId();
+
+ if ($parameter->getEntityType()->getBundleEntityType() !== NULL) {
+ $bundle = $parameter->bundle();
+ }
+
+ $found = TRUE;
+ break;
+ }
+ }
+
+ if (!$found || !isset($entity_type_id)) {
+ return;
+ }
+ }
+
+ /** @var \Drupal\social_group\JoinManagerInterface $manager */
+ $manager = \Drupal::service('plugin.manager.social_group.join');
+
+ $found = FALSE;
+
+ foreach ($manager->relations() as $data) {
+ if (
+ !isset($data['method']) &&
+ $data['entity_type'] === $entity_type_id &&
+ isset($bundle) === isset($data['bundle']) &&
+ (!isset($bundle) || $data['bundle'] === $bundle) &&
+ $data['field'] === $element['#field_name']
+ ) {
+ $found = TRUE;
+ break;
+ }
+ }
+
+ if (!$found || !isset($data)) {
+ return;
}
+
+ $description = '';
+
+ /** @var string $key */
+ foreach (array_keys($element['#options']) as $key) {
+ $description .= social_group_allowed_join_method_description(
+ $key,
+ $entity_type_id,
+ );
+ }
+
+ // Render a specific tooltip based on a field name and description.
+ // This is done in the fieldset, next to the <legend>.
+ $variables['popover'] = social_group_render_tooltip(
+ $data['field'],
+ t('Join methods'),
+ $description,
+ );
}
/**
diff --git a/modules/social_features/social_group/social_group.services.yml b/modules/social_features/social_group/social_group.services.yml
index e2490097b..620ac2947 100644
--- a/modules/social_features/social_group/social_group.services.yml
+++ b/modules/social_features/social_group/social_group.services.yml
@@ -1,4 +1,8 @@
services:
+ plugin.manager.social_group.join:
+ class: Drupal\social_group\JoinManager
+ parent: default_plugin_manager
+
social_group.route_subscriber:
class: Drupal\social_group\Routing\RouteSubscriber
tags:
diff --git a/modules/social_features/social_group/social_group.theme.inc b/modules/social_features/social_group/social_group.theme.inc
new file mode 100644
index 000000000..f556672ec
--- /dev/null
+++ b/modules/social_features/social_group/social_group.theme.inc
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Preprocess functions for the Social Group module.
+ */
+
+use Drupal\Core\Template\Attribute;
+
+/**
+ * Prepares variables for join templates.
+ *
+ * Default template: join.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - attributes: An array of HTML attributes to apply to the wrapper.
+ * - primary: The primary element.
+ * - secondaries: The secondary elements.
+ */
+function template_preprocess_join(array &$variables): void {
+ $variables['attributes'] = new Attribute($variables['attributes']);
+}
diff --git a/modules/social_features/social_group/src/Annotation/Join.php b/modules/social_features/social_group/src/Annotation/Join.php
new file mode 100644
index 000000000..d5d52241b
--- /dev/null
+++ b/modules/social_features/social_group/src/Annotation/Join.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\social_group\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Join annotation object.
+ *
+ * @ingroup social_group_api
+ *
+ * @Annotation
+ */
+class Join extends Plugin {
+
+ /**
+ * The plugin ID.
+ */
+ public string $id;
+
+ /**
+ * The entity type ID.
+ */
+ public ?string $entityTypeId = NULL;
+
+ /**
+ * The join method.
+ */
+ public ?string $method = NULL;
+
+ /**
+ * The weight.
+ */
+ public int $weight = 0;
+
+}
diff --git a/modules/social_features/social_group/src/Element/SocialGroupEntityAutocomplete.php b/modules/social_features/social_group/src/Element/SocialGroupEntityAutocomplete.php
index 2bfdce2b5..266dee54a 100644
--- a/modules/social_features/social_group/src/Element/SocialGroupEntityAutocomplete.php
+++ b/modules/social_features/social_group/src/Element/SocialGroupEntityAutocomplete.php
@@ -2,15 +2,16 @@
namespace Drupal\social_group\Element;
+use Drupal\Component\Utility\Tags;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\group\Entity\GroupContentInterface;
use Drupal\social_core\Entity\Element\EntityAutocomplete;
-use Drupal\Component\Utility\Tags;
-use Drupal\user\Entity\User;
+use Drupal\social_group\EntityMemberInterface;
+use Drupal\user\UserInterface;
/**
- * Provides an Group member autocomplete form element.
+ * Provides a Group member autocomplete form element.
*
* The #default_value accepted by this element is either an entity object or an
* array of entity objects.
@@ -21,6 +22,15 @@ class SocialGroupEntityAutocomplete extends EntityAutocomplete {
/**
* Form element validation handler for entity_autocomplete elements.
+ *
+ * @param array $element
+ * The element being processed.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ * @param array $complete_form
+ * The complete form structure.
+ * @param bool $select2
+ * (optional) TRUE if the Select2 widget is used. Defaults to FALSE.
*/
public static function validateEntityAutocomplete(
array &$element,
@@ -28,8 +38,6 @@ class SocialGroupEntityAutocomplete extends EntityAutocomplete {
array &$complete_form,
bool $select2 = FALSE
): void {
- $duplicated_values = $value = [];
-
/** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */
$form_object = $form_state->getFormObject();
@@ -38,7 +46,13 @@ class SocialGroupEntityAutocomplete extends EntityAutocomplete {
$entity = $entity->getGroup();
}
- if (!method_exists($entity, 'getMember')) {
+ // We need set "validate_reference" for element to prevent receive notice
+ // Undefined index #validate_reference.
+ if (!isset($element['#validate_reference'])) {
+ $element['#validate_reference'] = FALSE;
+ }
+
+ if (!$entity instanceof EntityMemberInterface) {
parent::validateEntityAutocomplete($element, $form_state, $complete_form);
return;
@@ -51,21 +65,27 @@ class SocialGroupEntityAutocomplete extends EntityAutocomplete {
$input_values = $element['#value'];
}
+ $duplicated_values = $value = [];
+ $storage = \Drupal::entityTypeManager()->getStorage('user');
+
+ /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $manager */
+ $manager = \Drupal::service('plugin.manager.entity_reference_selection');
+
foreach ($input_values as $input) {
- $match = static::extractEntityIdFromAutocompleteInput($input);
// If we use the select 2 widget then we already got a nice array.
- if ($select2 === TRUE) {
- $match = $input;
- }
+ $match = $select2 ? $input : static::extractEntityIdFromAutocompleteInput($input);
+
if ($match === NULL) {
$options = $element['#selection_settings'] + [
'target_type' => $element['#target_type'],
'handler' => $element['#selection_handler'],
];
- /** @var /Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler */
- $handler = \Drupal::service('plugin.manager.entity_reference_selection')->getInstance($options);
- $autocreate = (bool) $element['#autocreate'] && $handler instanceof SelectionWithAutocreateInterface;
+ /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler */
+ $handler = $manager->getInstance($options);
+
+ $autocreate = $element['#autocreate'] && $handler instanceof SelectionWithAutocreateInterface;
+
// Try to get a match from the input string when the user didn't use
// the autocomplete but filled in a value manually.
// Got this from the parent::validateEntityAutocomplete.
@@ -73,21 +93,15 @@ class SocialGroupEntityAutocomplete extends EntityAutocomplete {
}
if ($match !== NULL) {
- $value[$match] = [
- 'target_id' => $match,
- ];
+ $value[$match] = ['target_id' => $match];
+ $account = $storage->load($match);
- $account = User::load($match);
// User is already a member, add it to an array for the Form element
// to render an error after all checks are gone.
- if ($entity->getMember($account)) {
+ if ($account instanceof UserInterface && $entity->hasMember($account)) {
$duplicated_values[] = $account->getDisplayName();
}
- // We need set "validate_reference" for element to prevent
- // receive notice Undefined index #validate_reference.
- if (!isset($element['#validate_reference'])) {
- $element['#validate_reference'] = FALSE;
- }
+
// Validate input for every single user. This way we make sure that
// The element validates one, or more users added in the autocomplete.
// This is because Group doesn't allow adding multiple users at once,
@@ -99,21 +113,20 @@ class SocialGroupEntityAutocomplete extends EntityAutocomplete {
// If we have duplicates, provide an error message.
if (!empty($duplicated_values)) {
- $usernames = implode(', ', $duplicated_values);
-
$message = \Drupal::translation()->formatPlural(count($duplicated_values),
"@usernames is already member of the @type, you can't add them again",
"@usernames are already members of the @type, you can't add them again",
[
- '@usernames' => $usernames,
+ '@usernames' => implode(', ', $duplicated_values),
'@type' => $entity->getEntityType()->getSingularLabel(),
- ]
+ ],
);
// We have to kick in a form set error here, or else the
// GroupContentCardinalityValidator will kick in and show a faulty
// error message. Alter this later when Group supports multiple members.
$form_state->setError($element, $message);
+
return;
}
@@ -122,10 +135,29 @@ class SocialGroupEntityAutocomplete extends EntityAutocomplete {
// don't use it to perform the action, but we should mimic the behaviour
// as it would be without Select2.
if ($select2 === TRUE) {
- $form_state->setValue($element['#parents'], $match);
+ $form_state->setValue($element['#parents'], $match ?? NULL);
}
+
$form_state->setValue('entity_id_new', $value);
}
}
+ /**
+ * Form element validation handler for entity_autocomplete elements.
+ *
+ * @param array $element
+ * The element being processed.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ * @param array $complete_form
+ * The complete form structure.
+ */
+ public static function validateEntityAutocompleteSelect2(
+ array &$element,
+ FormStateInterface $form_state,
+ array &$complete_form
+ ): void {
+ static::validateEntityAutocomplete($element, $form_state, $complete_form, TRUE);
+ }
+
}
diff --git a/modules/social_features/social_group/src/Entity/Group.php b/modules/social_features/social_group/src/Entity/Group.php
index e16d1cb3e..406dbfe09 100644
--- a/modules/social_features/social_group/src/Entity/Group.php
+++ b/modules/social_features/social_group/src/Entity/Group.php
@@ -2,13 +2,23 @@
namespace Drupal\social_group\Entity;
-use Drupal\social_core\EntityUrlLanguageTrait;
+use Drupal\Core\Session\AccountInterface;
use Drupal\group\Entity\Group as GroupBase;
+use Drupal\social_core\EntityUrlLanguageTrait;
+use Drupal\social_group\SocialGroupInterface;
/**
- * Provides a Node entity that has links that work with different languages.
+ * Provides a Group entity that has links that work with different languages.
*/
-class Group extends GroupBase {
+class Group extends GroupBase implements SocialGroupInterface {
+
use EntityUrlLanguageTrait;
+ /**
+ * {@inheritdoc}
+ */
+ public function hasMember(AccountInterface $account): bool {
+ return $this->getMember($account) !== FALSE;
+ }
+
}
diff --git a/modules/social_features/social_group/src/EntityMemberInterface.php b/modules/social_features/social_group/src/EntityMemberInterface.php
new file mode 100644
index 000000000..5b84400fa
--- /dev/null
+++ b/modules/social_features/social_group/src/EntityMemberInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\social_group;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Defines a common interface for entities that support memberships.
+ */
+interface EntityMemberInterface extends ContentEntityInterface {
+
+ /**
+ * Checks if a user is a member.
+ *
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * The account object.
+ */
+ public function hasMember(AccountInterface $account): bool;
+
+}
diff --git a/modules/social_features/social_group/src/EventSubscriber/RedirectSubscriber.php b/modules/social_features/social_group/src/EventSubscriber/RedirectSubscriber.php
index cc08d0e2b..85d2429bf 100644
--- a/modules/social_features/social_group/src/EventSubscriber/RedirectSubscriber.php
+++ b/modules/social_features/social_group/src/EventSubscriber/RedirectSubscriber.php
@@ -34,7 +34,9 @@ class RedirectSubscriber implements EventSubscriberInterface {
*/
public function checkForRedirection(RequestEvent $event) {
// Check if there is a group object on the current route.
- $group = _social_group_get_current_group();
+ if (($group = _social_group_get_current_group()) === NULL) {
+ return;
+ }
// Get the current route name for the checks being performed below.
$routeMatch = \Drupal::routeMatch()->getRouteName();
@@ -55,13 +57,13 @@ class RedirectSubscriber implements EventSubscriberInterface {
'view.group_topics.page_group_topics',
];
// If a group is set, and the type is closed_group.
- if ($group && $group->getGroupType()->id() == 'closed_group') {
+ if ($group->getGroupType()->id() === 'closed_group') {
if ($user->id() != 1) {
if ($user->hasPermission('manage all groups')) {
return;
}
// If the user is not an member of this group.
- elseif (!$group->getMember($user) && in_array($routeMatch, $routes)) {
+ elseif (!$group->hasMember($user) && in_array($routeMatch, $routes)) {
$event->setResponse(new RedirectResponse(Url::fromRoute('view.group_information.page_group_about', ['group' => $group->id()])
->toString()));
}
diff --git a/modules/social_features/social_group/src/JoinBase.php b/modules/social_features/social_group/src/JoinBase.php
new file mode 100644
index 000000000..5b10779e3
--- /dev/null
+++ b/modules/social_features/social_group/src/JoinBase.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\social_group;
+
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a base join implementation.
+ *
+ * @ingroup social_group_api
+ */
+abstract class JoinBase extends PluginBase implements JoinPluginInterface {
+
+ use StringTranslationTrait;
+
+ /**
+ * The current active user.
+ */
+ protected AccountProxyInterface $currentUser;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(
+ array $configuration,
+ string $plugin_id,
+ $plugin_definition,
+ TranslationInterface $translation,
+ AccountProxyInterface $current_user
+ ) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+ $this->setStringTranslation($translation);
+ $this->currentUser = $current_user;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(
+ ContainerInterface $container,
+ array $configuration,
+ $plugin_id,
+ $plugin_definition
+ ): self {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('string_translation'),
+ $container->get('current_user'),
+ );
+ }
+
+}
diff --git a/modules/social_features/social_group/src/JoinManager.php b/modules/social_features/social_group/src/JoinManager.php
new file mode 100644
index 000000000..ff0660c72
--- /dev/null
+++ b/modules/social_features/social_group/src/JoinManager.php
@@ -0,0 +1,250 @@
+<?php
+
+namespace Drupal\social_group;
+
+use Drupal\Component\Utility\SortArray;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Render\RenderableInterface;
+use Drupal\Core\Template\Attribute;
+use Drupal\Core\Url;
+use Drupal\social_group\Annotation\Join;
+
+/**
+ * Defines the join manager.
+ */
+class JoinManager extends DefaultPluginManager implements JoinManagerInterface {
+
+ use ContextAwarePluginManagerTrait;
+
+ private const HOOK_JOIN_METHOD_USAGE = 'social_group_join_method_usage';
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(
+ \Traversable $namespaces,
+ CacheBackendInterface $cache_backend,
+ ModuleHandlerInterface $module_handler
+ ) {
+ parent::__construct(
+ 'Plugin/Join',
+ $namespaces,
+ $module_handler,
+ JoinPluginInterface::class,
+ Join::class,
+ );
+
+ $this->alterInfo('social_group_join_info');
+ $this->setCacheBackend($cache_backend, 'join_plugins');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function relations(): array {
+ $items = $this->moduleHandler->invokeAll(self::HOOK_JOIN_METHOD_USAGE);
+
+ $this->moduleHandler->alter(self::HOOK_JOIN_METHOD_USAGE, $items);
+
+ if ($this->moduleHandler->moduleExists('social_group_request')) {
+ $old_types = [];
+
+ foreach ($items as $item) {
+ if ($item['entity_type'] === 'group' && isset($item['bundle'])) {
+ $old_types = array_merge($old_types, (array) $item['bundle']);
+ }
+ }
+
+ $old_types = $new_types = array_unique($old_types);
+
+ $this->moduleHandler->alterDeprecated(
+ 'Deprecated in social:11.2.0 and is removed from social:12.0.0. Use hook_social_group_join_method_usage instead. See https://www.drupal.org/node/3254715',
+ 'social_group_request',
+ $new_types,
+ );
+
+ $added_types = $removed_types = [];
+
+ foreach (array_unique(array_merge($old_types, $new_types)) as $type) {
+ $is_new = in_array($type, $new_types);
+
+ if (in_array($type, $old_types) !== $is_new) {
+ if ($is_new) {
+ $added_types[] = $type;
+ }
+ else {
+ $removed_types[] = $type;
+ }
+ }
+ }
+
+ if (!empty($removed_types)) {
+ foreach ($items as $item_delta => &$item) {
+ if ($item['entity_type'] === 'group' && isset($item['bundle'])) {
+ if (is_array($item['bundle'])) {
+ foreach ($removed_types as $type) {
+ $bundle_delta = array_search($type, $item['bundle']);
+
+ if ($bundle_delta !== FALSE) {
+ unset($item['bundle'][$bundle_delta]);
+ }
+ }
+
+ if (empty($item['bundle'])) {
+ unset($items[$item_delta]);
+ }
+ }
+ elseif (in_array($item['bundle'], $removed_types)) {
+ unset($items[$item_delta]);
+ }
+ }
+ }
+ }
+
+ if (!empty($added_types)) {
+ $items[] = [
+ 'entity_type' => 'group',
+ 'bundle' => $added_types,
+ ];
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function preprocess(array &$variables, string $hook): void {
+ $entity = $variables['elements']['#' . $hook];
+
+ if (!$entity instanceof EntityMemberInterface) {
+ return;
+ }
+
+ $entity_type = $entity->getEntityType();
+ $entity_type_id = $entity->getEntityTypeId();
+ $methods = [];
+
+ foreach ($this->relations() as $data) {
+ if (
+ $data['entity_type'] === $entity_type_id &&
+ (
+ !isset($data['bundle']) &&
+ $entity_type->getBundleEntityType() === NULL ||
+ in_array($entity->bundle(), (array) $data['bundle'])
+ )
+ ) {
+ if (isset($data['field'])) {
+ if (isset($data['method'])) {
+ $field = $entity->get($data['field']);
+
+ if (!$field->isEmpty() && !empty($field->getValue()[0]['value'])) {
+ $relation_methods = (array) $data['method'];
+ }
+ else {
+ continue;
+ }
+ }
+ else {
+ $relation_methods = array_column(
+ $entity->get($data['field'])->getValue(),
+ 'value',
+ );
+ }
+ }
+ else {
+ $relation_methods = (array) $data['method'];
+ }
+
+ $methods = array_merge($methods, $relation_methods);
+ }
+ }
+
+ if (empty($methods)) {
+ return;
+ }
+
+ $definitions = array_filter(
+ $this->getDefinitions(),
+ fn (array $definition): bool =>
+ (
+ !isset($definition['entityTypeId']) ||
+ $definition['entityTypeId'] === $entity_type_id
+ ) &&
+ (
+ !isset($definition['method']) ||
+ in_array($definition['method'], $methods)
+ ),
+ );
+
+ usort($definitions, [SortArray::class, 'sortByWeightElement']);
+
+ foreach ($definitions as $definition) {
+ /** @var \Drupal\social_group\JoinPluginInterface $plugin */
+ $plugin = $this->createInstance($definition['id']);
+
+ $items = array_map(function ($item) {
+ if ($item instanceof RenderableInterface) {
+ return $item->toRenderable();
+ }
+ elseif (is_array($item)) {
+ $attributes = $item['attributes'] ?? [];
+
+ /** @var \Drupal\Core\Url $url */
+ $url = $item['url'] ?? Url::fromRoute('<none>');
+
+ $attributes['href'] = $url->toString();
+
+ if (!isset($item['title'])) {
+ $attributes['title'] = $item['label'];
+ }
+
+ if (!isset($item['url'])) {
+ $attributes['class'][] = 'disabled';
+ }
+
+ $item['attributes'] = new Attribute($attributes);
+ }
+
+ return $item;
+ }, $plugin->actions($entity, $variables));
+
+ if ($items) {
+ $variables['join'] = [
+ '#theme' => 'join',
+ '#primary' => array_shift($items),
+ '#secondaries' => $items,
+ '#entity' => $entity,
+ ];
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasMethod(string $bundle, string $method): bool {
+ foreach ($this->relations() as $relation) {
+ if (
+ $relation['entity_type'] === 'group' &&
+ isset($relation['bundle']) &&
+ in_array($bundle, (array) $relation['bundle']) &&
+ (
+ !isset($relation['method']) ||
+ in_array($method, (array) $relation['method'])
+ )
+ ) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+ }
+
+}
diff --git a/modules/social_features/social_group/src/JoinManagerInterface.php b/modules/social_features/social_group/src/JoinManagerInterface.php
new file mode 100644
index 000000000..f8024e80b
--- /dev/null
+++ b/modules/social_features/social_group/src/JoinManagerInterface.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\social_group;
+
+use Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface;
+
+/**
+ * Defines the join manager interface.
+ */
+interface JoinManagerInterface extends ContextAwarePluginManagerInterface {
+
+ /**
+ * Returns list of entity types and their bundles that support join methods.
+ */
+ public function relations(): array;
+
+ /**
+ * Preprocess theme variables for templates.
+ *
+ * @param array $variables
+ * The variables array (modify in place).
+ * @param string $hook
+ * The name of the theme hook.
+ */
+ public function preprocess(array &$variables, string $hook): void;
+
+ /**
+ * Check if specific bundle supports selected the join method.
+ *
+ * @param string $bundle
+ * The bundle.
+ * @param string $method
+ * The join method.
+ */
+ public function hasMethod(string $bundle, string $method): bool;
+
+}
diff --git a/modules/social_features/social_group/src/JoinPluginInterface.php b/modules/social_features/social_group/src/JoinPluginInterface.php
new file mode 100644
index 000000000..7b3c349df
--- /dev/null
+++ b/modules/social_features/social_group/src/JoinPluginInterface.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\social_group;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Provides an interface for join plugins.
+ */
+interface JoinPluginInterface extends ContainerFactoryPluginInterface {
+
+ /**
+ * JoinBase constructor.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin_id for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
+ * The string translation service.
+ * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+ * The current active user.
+ */
+ public function __construct(
+ array $configuration,
+ string $plugin_id,
+ $plugin_definition,
+ TranslationInterface $translation,
+ AccountProxyInterface $current_user
+ );
+
+ /**
+ * Gets a list of clickable elements.
+ *
+ * @param \Drupal\social_group\EntityMemberInterface $entity
+ * The membership entity object.
+ * @param array $variables
+ * The variables.
+ */
+ public function actions(EntityMemberInterface $entity, array &$variables): array;
+
+}
diff --git a/modules/social_features/social_group/src/Plugin/Action/AddMembersToGroup.php b/modules/social_features/social_group/src/Plugin/Action/AddMembersToGroup.php
index 66eac960f..9171a714c 100644
--- a/modules/social_features/social_group/src/Plugin/Action/AddMembersToGroup.php
+++ b/modules/social_features/social_group/src/Plugin/Action/AddMembersToGroup.php
@@ -9,6 +9,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\group\Entity\Group;
+use Drupal\social_group\SocialGroupInterface;
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\user\Entity\User;
@@ -72,14 +73,11 @@ class AddMembersToGroup extends ViewsBulkOperationsActionBase implements Contain
*/
public function execute($entity = NULL) {
// Load the Group.
- $group = Group::load($this->configuration['groups']);
-
- if (NULL !== $group) {
- // Check if user already is a member.
- $is_member = $group->getMember($entity);
+ $group = $this->storage->load($this->configuration['groups']);
+ if ($group instanceof SocialGroupInterface) {
// If that is not the case we can add it to the group.
- if (!$is_member) {
+ if (!$group->hasMember($entity)) {
$group->addMember($entity);
return $this->t('Amount of users added to group');
diff --git a/modules/social_features/social_group/src/Plugin/Join/SocialGroupAlreadyJoin.php b/modules/social_features/social_group/src/Plugin/Join/SocialGroupAlreadyJoin.php
new file mode 100644
index 000000000..a9c196911
--- /dev/null
+++ b/modules/social_features/social_group/src/Plugin/Join/SocialGroupAlreadyJoin.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\social_group\Plugin\Join;
+
+use Drupal\Core\Link;
+use Drupal\social_group\EntityMemberInterface;
+use Drupal\social_group\JoinBase;
+
+/**
+ * Provides a join plugin instance for members.
+ *
+ * @Join(
+ * id = "social_group_already_join",
+ * )
+ */
+class SocialGroupAlreadyJoin extends JoinBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function actions(EntityMemberInterface $entity, array &$variables): array {
+ $items = [];
+
+ if (!$entity->hasMember($this->currentUser)) {
+ return $items;
+ }
+
+ $items[] = $this->t('Joined', [], ['context' => 'Is a member']);
+
+ $entity_type_id = $entity->getEntityTypeId();
+
+ $items[] = Link::createFromRoute(
+ $this->t(
+ 'Leave @entity_type_id',
+ ['@entity_type_id' => $entity->getEntityType()->getSingularLabel()],
+ ),
+ 'entity.' . $entity_type_id . '.leave',
+ [$entity_type_id => $entity->id()],
+ );
+
+ $variables['joined'] = TRUE;
+
+ return $items;
+ }
+
+}
diff --git a/modules/social_features/social_group/src/Plugin/Join/SocialGroupDirectJoin.php b/modules/social_features/social_group/src/Plugin/Join/SocialGroupDirectJoin.php
new file mode 100644
index 000000000..a97771e3f
--- /dev/null
+++ b/modules/social_features/social_group/src/Plugin/Join/SocialGroupDirectJoin.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\social_group\Plugin\Join;
+
+use Drupal\Core\Url;
+use Drupal\social_group\EntityMemberInterface;
+use Drupal\social_group\JoinBase;
+
+/**
+ * Provides a join plugin instance for joining directly.
+ *
+ * @Join(
+ * id = "social_group_direct_join",
+ * entityTypeId = "group",
+ * method = "direct",
+ * weight = 10,
+ * )
+ */
+class SocialGroupDirectJoin extends JoinBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function actions(EntityMemberInterface $entity, array &$variables): array {
+ $items = [];
+
+ if (!$this->access($entity)) {
+ return $items;
+ }
+
+ $entity_type_id = $entity->getEntityTypeId();
+
+ $url = Url::fromRoute(
+ 'entity.' . $entity_type_id . '.join',
+ [$entity_type_id => $entity->id()],
+ );
+
+ $items[] = [
+ 'label' => $this->t('Join'),
+ 'url' => $url,
+ 'attributes' => [
+ 'class' => ['btn-accent'],
+ ],
+ ];
+
+ $variables['group_operations_url'] = $url;
+
+ return $items;
+ }
+
+ /**
+ * Check if a user can join directly.
+ *
+ * @param \Drupal\social_group\EntityMemberInterface $entity
+ * The membership entity object.
+ */
+ protected function access(EntityMemberInterface $entity): bool {
+ /** @var \Drupal\social_group\SocialGroupInterface $entity */
+ return $entity->hasPermission('join group', $this->currentUser) ||
+ $this->currentUser->isAnonymous() &&
+ in_array($entity->bundle(), ['flexible_group', 'public_group']);
+ }
+
+}
diff --git a/modules/social_features/social_group/src/Plugin/views/field/SocialGroupViewsBulkOperationsBulkForm.php b/modules/social_features/social_group/src/Plugin/views/field/SocialGroupViewsBulkOperationsBulkForm.php
index e0ec29939..b8f9a8f71 100644
--- a/modules/social_features/social_group/src/Plugin/views/field/SocialGroupViewsBulkOperationsBulkForm.php
+++ b/modules/social_features/social_group/src/Plugin/views/field/SocialGroupViewsBulkOperationsBulkForm.php
@@ -268,8 +268,10 @@ class SocialGroupViewsBulkOperationsBulkForm extends ViewsBulkOperationsBulkForm
if ($url->getRouteName() === 'views_bulk_operations.execute_configurable') {
$parameters = $url->getRouteParameters();
- if (empty($parameters['group'])) {
- $group = _social_group_get_current_group();
+ if (
+ empty($parameters['group']) &&
+ ($group = _social_group_get_current_group()) !== NULL
+ ) {
$parameters['group'] = $group->id();
}
diff --git a/modules/social_features/social_group/src/SocialGroupHelperService.php b/modules/social_features/social_group/src/SocialGroupHelperService.php
index 99bb6c2a9..1681d5864 100644
--- a/modules/social_features/social_group/src/SocialGroupHelperService.php
+++ b/modules/social_features/social_group/src/SocialGroupHelperService.php
@@ -15,6 +15,7 @@ use Drupal\group\Entity\GroupContent;
use Drupal\group\Entity\GroupContentInterface;
use Drupal\group\Entity\GroupContentType;
use Drupal\group\Entity\GroupInterface;
+use Drupal\social_group\Element\SocialGroupEntityAutocomplete;
use Drupal\social_post\Entity\PostInterface;
/**
@@ -307,7 +308,7 @@ class SocialGroupHelperService implements SocialGroupHelperServiceInterface {
/**
* {@inheritdoc}
*/
- public function addMemberFormField() {
+ public function addMemberFormField(): array {
return [
'#title' => $this->t('Find people by name or email address'),
'#type' => 'select2',
@@ -320,7 +321,12 @@ class SocialGroupHelperService implements SocialGroupHelperServiceInterface {
],
'#selection_handler' => 'social',
'#target_type' => 'user',
- '#element_validate' => ['_social_group_unique_members'],
+ '#element_validate' => [
+ [
+ SocialGroupEntityAutocomplete::class,
+ 'validateEntityAutocompleteSelect2',
+ ],
+ ],
];
}
diff --git a/modules/social_features/social_group/src/SocialGroupHelperServiceInterface.php b/modules/social_features/social_group/src/SocialGroupHelperServiceInterface.php
index 87dd057a0..44f28c61d 100644
--- a/modules/social_features/social_group/src/SocialGroupHelperServiceInterface.php
+++ b/modules/social_features/social_group/src/SocialGroupHelperServiceInterface.php
@@ -108,10 +108,7 @@ interface SocialGroupHelperServiceInterface {
/**
* Provides a field for potential members.
- *
- * @return array
- * The renderable field.
*/
- public function addMemberFormField();
+ public function addMemberFormField(): array;
}
diff --git a/modules/social_features/social_group/src/SocialGroupInterface.php b/modules/social_features/social_group/src/SocialGroupInterface.php
new file mode 100644
index 000000000..47469b612
--- /dev/null
+++ b/modules/social_features/social_group/src/SocialGroupInterface.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Drupal\social_group;
+
+use Drupal\group\Entity\GroupInterface;
+
+/**
+ * Provides an interface defining a Group entity.
+ */
+interface SocialGroupInterface extends EntityMemberInterface, GroupInterface {}
diff --git a/modules/social_features/social_group/templates/join.html.twig b/modules/social_features/social_group/templates/join.html.twig
new file mode 100644
index 000000000..5f61655d3
--- /dev/null
+++ b/modules/social_features/social_group/templates/join.html.twig
@@ -0,0 +1,47 @@
+{#
+/**
+ * @file
+ * Default theme implementation for join actions.
+ *
+ * Available variables:
+ * - attributes: HTML attributes to apply to the wrapper.
+ * - primary: The primary element.
+ * - secondaries: The secondary elements.
+ *
+ * @see template_preprocess_join()
+ *
+ * @ingroup themeable
+ */
+#}
+{% if primary %}
+ <div class="hero-footer__cta">
+ <div{{ attributes }}>
+ {% if secondaries %}
+ {% if primary.label %}
+ <a{{ primary.attributes.addClass('form-item', 'btn', 'btn-accent', 'btn-lg', 'btn-raised') }}>{{ primary.label }}</a>
+ <button type="button" autocomplete="off" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-accent btn-lg btn-raised dropdown-toggle waves-effect waves-btn waves-light margin-left-m"><span class="caret"></span></button>
+ {% else %}
+ <button type="button" autocomplete="off" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-accent btn-lg btn-raised dropdown-toggle btn-block">{{ primary }}<span class="caret"></span></button>
+ {% endif %}
+ <ul class="dropdown-menu dropdown-menu-right">
+ {% for secondary in secondaries %}
+ <li>{{ secondary }}</li>
+ {% endfor %}
+ </ul>
+ {% else %}
+ <a{{ primary.attributes.addClass('btn', 'btn-block') }}>{{ primary.label }}</a>
+ {% endif %}
+ </div>
+
+ {% if invite_widget %}
+ <div class="btn-group">
+ <button type="button" autocomplete="off" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-default btn-block dropdown-toggle">
+ {% trans %}Invite{% endtrans %}<span class="caret"></span>
+ </button>
+ <div class="dropdown-menu dropdown-menu--invite">
+ {{ invite_widget }}
+ </div>
+ </div>
+ {% endif %}
+ </div>
+{% endif %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment