From 7aa40a4fe00e5d36c87ea63f1ac3bd6d29074a35 Mon Sep 17 00:00:00 2001
From: Dalibor Matura <dalibor.matura@gmail.com>
Date: Mon, 4 Dec 2023 13:45:09 +0800
Subject: [PATCH 1/4] Extended 3280964 with support for Group 2.x.

---
 modules/access_unpublished_group/README.txt   | 15 ++++
 ...ss_unpublished_group.group.permissions.yml |  2 +
 .../access_unpublished_group.info.yml         |  8 ++
 .../access_unpublished_group.install          | 24 ++++++
 .../AccessUnpublishedGroupPermissions.php     | 86 +++++++++++++++++++
 .../AccessUnpublishedGroupServiceProvider.php | 57 ++++++++++++
 .../AccessUnpublishedGroupAccessControl.php   | 72 ++++++++++++++++
 7 files changed, 264 insertions(+)
 create mode 100644 modules/access_unpublished_group/README.txt
 create mode 100644 modules/access_unpublished_group/access_unpublished_group.group.permissions.yml
 create mode 100644 modules/access_unpublished_group/access_unpublished_group.info.yml
 create mode 100644 modules/access_unpublished_group/access_unpublished_group.install
 create mode 100644 modules/access_unpublished_group/src/Access/AccessUnpublishedGroupPermissions.php
 create mode 100644 modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php
 create mode 100644 modules/access_unpublished_group/src/Plugin/Group/RelationHandler/AccessUnpublishedGroupAccessControl.php

diff --git a/modules/access_unpublished_group/README.txt b/modules/access_unpublished_group/README.txt
new file mode 100644
index 0000000..9db9ea6
--- /dev/null
+++ b/modules/access_unpublished_group/README.txt
@@ -0,0 +1,15 @@
+ACCESS UNPUBLISHED GROUP
+
+Description:
+------------
+Extends Access Unpublished module to support content belonging to
+Groups 2.x.
+
+Usage:
+------
+After installing and activating the 'Access unpublished group' module,
+you need to enable permissions per each group type.  Go to
+  admin/group/types
+and select Edit Permissions for the group types of your choice.
+You can enable the Access Unpublished Group permission, typically for
+anonymous users, for the content types of your choice.
diff --git a/modules/access_unpublished_group/access_unpublished_group.group.permissions.yml b/modules/access_unpublished_group/access_unpublished_group.group.permissions.yml
new file mode 100644
index 0000000..98e8d2e
--- /dev/null
+++ b/modules/access_unpublished_group/access_unpublished_group.group.permissions.yml
@@ -0,0 +1,2 @@
+permission_callbacks:
+  - '\Drupal\access_unpublished_group\Access\AccessUnpublishedGroupPermissions::groupPermissions'
diff --git a/modules/access_unpublished_group/access_unpublished_group.info.yml b/modules/access_unpublished_group/access_unpublished_group.info.yml
new file mode 100644
index 0000000..34f6a9e
--- /dev/null
+++ b/modules/access_unpublished_group/access_unpublished_group.info.yml
@@ -0,0 +1,8 @@
+name: 'Access Unpublished Group'
+type: module
+description: 'Supports using Access Unpublished with Group content.'
+package: Content
+core_version_requirement: ^9 || ^10
+dependencies:
+  - 'access_unpublished:access_unpublished'
+  - 'group:group'
diff --git a/modules/access_unpublished_group/access_unpublished_group.install b/modules/access_unpublished_group/access_unpublished_group.install
new file mode 100644
index 0000000..dc447e6
--- /dev/null
+++ b/modules/access_unpublished_group/access_unpublished_group.install
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Install, update, uninstall functions for the access_unpublished_group module.
+ */
+
+use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\group\Entity\GroupRelationship;
+use Drupal\group\Entity\GroupRelationshipType;
+use Drupal\group\Entity\GroupTypeInterface;
+use Drupal\group\PermissionScopeInterface;
+use Drupal\user\RoleInterface;
+
+/**
+ * Implements hook_install().
+ */
+function access_unpublished_group_install() {
+  // Setting weight higher than the access_unpublished to make changes in
+  // ServiceProvider after the access_unpublished does.
+  module_set_weight('access_unpublished_group', 10);
+}
diff --git a/modules/access_unpublished_group/src/Access/AccessUnpublishedGroupPermissions.php b/modules/access_unpublished_group/src/Access/AccessUnpublishedGroupPermissions.php
new file mode 100644
index 0000000..5480814
--- /dev/null
+++ b/modules/access_unpublished_group/src/Access/AccessUnpublishedGroupPermissions.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\access_unpublished_group\Access;
+
+use Drupal\group\Plugin\Group\Relation\GroupRelationInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\group\Plugin\Group\Relation\GroupRelationTypeManagerInterface;
+
+/**
+ * Provides dynamic permissions for groups of different types.
+ */
+class AccessUnpublishedGroupPermissions implements ContainerInjectionInterface {
+
+  /**
+   * The route match.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * The group content enabler plugin manager.
+   *
+   * @var \Drupal\group\Plugin\Group\Relation\GroupRelationTypeManagerInterface
+   */
+  protected $groupRelationTypeManager;
+
+  /**
+   * Constructs the AccessUnpublishedGroupPermissions object.
+   */
+  public function __construct(RouteMatchInterface $route_match, GroupRelationTypeManagerInterface $group_relation_type_manager) {
+    $this->routeMatch = $route_match;
+    $this->groupRelationTypeManager = $group_relation_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('current_route_match'),
+      $container->get('group_relation_type.manager')
+    );
+  }
+
+  /**
+   * Returns an array of group permissions.
+   *
+   * @return array
+   *   The group permissions.
+   *   @see \Drupal\user\PermissionHandlerInterface::getPermissions()
+   */
+  public function groupPermissions() {
+    $perms = [];
+    if (is_null($this->groupRelationTypeManager)) {
+      return [];
+    }
+    if ($group_type = $this->routeMatch->getParameter('group_type')) {
+      $plugins = $this->groupRelationTypeManager->getInstalled($group_type);
+      foreach ($plugins as $plugin) {
+        $perms += $this->buildPermissions($plugin);
+      }
+    }
+    return $perms;
+  }
+
+  /**
+   * Returns list of group permissions for a given group content enabler plugin.
+   *
+   * @param \Drupal\group\Plugin\Group\Relation\GroupRelationInterface $plugin
+   *   The group content enabler plugin.
+   *
+   * @return array
+   *   An associative array of permission names and descriptions.
+   */
+  protected function buildPermissions(GroupRelationInterface $plugin) {
+    $permissions = [];
+    $permissions['access_unpublished_group_' . $plugin->getPluginId()] = [
+      'title' => 'Access unpublished ' . $plugin->getPluginDefinition()->get('label') ?? '',
+    ];
+    return $permissions;
+  }
+
+}
diff --git a/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php b/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php
new file mode 100644
index 0000000..6b9be1f
--- /dev/null
+++ b/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\access_unpublished_group;
+
+use Drupal\access_unpublished_group\Plugin\Group\RelationHandler\AccessUnpublishedGroupAccessControl;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceProviderBase;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\group\Plugin\Group\Relation\GroupRelationTypeInterface;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Decorates the group.relation_handler.access_control.* services.
+ */
+class AccessUnpublishedGroupServiceProvider extends ServiceProviderBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @see \Drupal\group\GroupServiceProvider::alter()
+   * @see \Drupal\access_unpublished\AccessUnpublishedServiceProvider::alter()
+   */
+  public function alter(ContainerBuilder $container) {
+    // Prepare group relations discovery.
+    $modules = $container->getParameter('container.modules');
+    $discovery = new AnnotatedClassDiscovery(
+      'Plugin/Group/Relation',
+      $container->get('container.namespaces'),
+      'Drupal\group\Annotation\GroupRelationType',
+      []
+    );
+
+    // Iterate through all group relations and decorate their access_control
+    // handler.
+    foreach ($discovery->getDefinitions() as $group_relation_type_id => $group_relation_type) {
+      assert($group_relation_type instanceof GroupRelationTypeInterface);
+      // Skip plugins whose provider is not installed.
+      if (!isset($modules[$group_relation_type->getProvider()])) {
+        continue;
+      }
+
+      $existing_service_name = "group.relation_handler.access_control.$group_relation_type_id";
+      $new_service_name = "access_unpublished_group.relation_handler.access_control.$group_relation_type_id";
+
+      // Decorate the service if it exists.
+      if ($container->hasDefinition($existing_service_name)) {
+        $container->register($new_service_name, AccessUnpublishedGroupAccessControl::class)
+          ->setDecoratedService($existing_service_name)
+          ->addArgument(new Reference("$new_service_name.inner"))
+          ->setPublic(TRUE)
+          ->setShared(FALSE);
+      }
+    }
+  }
+
+}
diff --git a/modules/access_unpublished_group/src/Plugin/Group/RelationHandler/AccessUnpublishedGroupAccessControl.php b/modules/access_unpublished_group/src/Plugin/Group/RelationHandler/AccessUnpublishedGroupAccessControl.php
new file mode 100644
index 0000000..29d32b3
--- /dev/null
+++ b/modules/access_unpublished_group/src/Plugin/Group/RelationHandler/AccessUnpublishedGroupAccessControl.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\access_unpublished_group\Plugin\Group\RelationHandler;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\group\Plugin\Group\RelationHandler\AccessControlInterface;
+use Drupal\group\Plugin\Group\RelationHandler\AccessControlTrait;
+
+/**
+ * Provides an access control handler to support Access Unpublished.
+ */
+class AccessUnpublishedGroupAccessControl implements AccessControlInterface {
+
+  use AccessControlTrait;
+
+  /**
+   * Constructs a new AccessUnpublishedGroupAccessControl.
+   *
+   * @param \Drupal\group\Plugin\Group\RelationHandler\AccessControlInterface $parent
+   *   The parent access control handler.
+   */
+  public function __construct(AccessControlInterface $parent) {
+    $this->parent = $parent;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function entityAccess(EntityInterface $entity, $operation, AccountInterface $account, $return_as_object = FALSE) {
+    $result = $this->parent->entityAccess($entity, $operation, $account, $return_as_object);
+
+    if ($operation == 'view' && $result->isForbidden()) {
+      /** @var \Drupal\group\Entity\Storage\GroupRelationshipStorageInterface $storage */
+      $storage = $this->entityTypeManager->getStorage('group_content');
+      $group_relationships = $storage->loadByEntity($entity);
+
+      // Find content that uses this plugin and has the group permission.
+      $has_group_permission = FALSE;
+      foreach ($group_relationships as $group_relationship) {
+        $plugin_id = $group_relationship->getPlugin()->getRelationTypeId();
+        if ($plugin_id === $this->pluginId) {
+          $group = $group_relationship->getGroup();
+          $permission_id = 'access_unpublished_group_' . $plugin_id;
+          if ($group->hasPermission($permission_id, $account)) {
+            $has_group_permission = TRUE;
+            break;
+          }
+        }
+      }
+
+      if ($has_group_permission) {
+        // Check the access_unpublished_entity_access() once when the user has
+        // the group permission.
+        $au_access = access_unpublished_entity_access($entity, $operation, $account);
+        if ($au_access->isAllowed()) {
+          // Add the cache dependencies from the parent result.
+          $au_access->addCacheableDependency($result);
+          $result = $au_access;
+        }
+        else {
+          // Add the access_unpublished_entity_access cache dependencies to the
+          // returned result.
+          $result->addCacheableDependency($au_access);
+        }
+      }
+    }
+
+    return $return_as_object ? $result : $result->isAllowed();
+  }
+
+}
-- 
GitLab


From 03b0b23391a21ce579b7944f7a0141bbb86b3e81 Mon Sep 17 00:00:00 2001
From: Dalibor Matura <dalibor.matura@gmail.com>
Date: Mon, 4 Dec 2023 14:15:13 +0800
Subject: [PATCH 2/4] Removed unused use statements.

---
 .../access_unpublished_group.install                     | 9 ---------
 .../src/AccessUnpublishedGroupServiceProvider.php        | 1 -
 2 files changed, 10 deletions(-)

diff --git a/modules/access_unpublished_group/access_unpublished_group.install b/modules/access_unpublished_group/access_unpublished_group.install
index dc447e6..82e7d45 100644
--- a/modules/access_unpublished_group/access_unpublished_group.install
+++ b/modules/access_unpublished_group/access_unpublished_group.install
@@ -5,15 +5,6 @@
  * Install, update, uninstall functions for the access_unpublished_group module.
  */
 
-use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Field\BaseFieldDefinition;
-use Drupal\group\Entity\GroupRelationship;
-use Drupal\group\Entity\GroupRelationshipType;
-use Drupal\group\Entity\GroupTypeInterface;
-use Drupal\group\PermissionScopeInterface;
-use Drupal\user\RoleInterface;
-
 /**
  * Implements hook_install().
  */
diff --git a/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php b/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php
index 6b9be1f..8d39323 100644
--- a/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php
+++ b/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php
@@ -7,7 +7,6 @@ use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DependencyInjection\ServiceProviderBase;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
 use Drupal\group\Plugin\Group\Relation\GroupRelationTypeInterface;
-use Symfony\Component\DependencyInjection\Definition;
 use Symfony\Component\DependencyInjection\Reference;
 
 /**
-- 
GitLab


From cbb42ec35446a0002f5d20823e4f70f8a9747608 Mon Sep 17 00:00:00 2001
From: Dalibor Matura <dalibor.matura@gmail.com>
Date: Wed, 19 Feb 2025 14:14:07 +0800
Subject: [PATCH 3/4] Issue #3405874: Fixed after Group 2.3.1 update.

---
 .../AccessUnpublishedGroupServiceProvider.php | 51 ++++++++++++++++---
 1 file changed, 44 insertions(+), 7 deletions(-)

diff --git a/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php b/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php
index 8d39323..f11470f 100644
--- a/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php
+++ b/modules/access_unpublished_group/src/AccessUnpublishedGroupServiceProvider.php
@@ -6,6 +6,8 @@ use Drupal\access_unpublished_group\Plugin\Group\RelationHandler\AccessUnpublish
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DependencyInjection\ServiceProviderBase;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\AttributeDiscoveryWithAnnotations;
+use Drupal\group\Plugin\Attribute\GroupRelationType;
 use Drupal\group\Plugin\Group\Relation\GroupRelationTypeInterface;
 use Symfony\Component\DependencyInjection\Reference;
 
@@ -17,18 +19,28 @@ class AccessUnpublishedGroupServiceProvider extends ServiceProviderBase {
   /**
    * {@inheritdoc}
    *
+   * This implementation alters existing services through use of
+   * `ServiceProvider::alter()` method, see related Drupal API documentation:
+   * https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/altering-existing-services-providing-dynamic-services
+   *
+   * The services being altered are first defined (altered) in Group's contrib
+   * module implementation of `ServiceProvider::alter()`, implemented in
+   * `\Drupal\group\GroupServiceProvider`.
+   *
+   * If Group contrib module is updated and the Access Unpublished functionality
+   * stops working for Group content, this is the first place to look into as
+   * the `\Drupal\group\GroupServiceProvider::alter()` method might have been
+   * changed. If so, the changes must be reflected in this method.
+   *
    * @see \Drupal\group\GroupServiceProvider::alter()
    * @see \Drupal\access_unpublished\AccessUnpublishedServiceProvider::alter()
+   * @see https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/altering-existing-services-providing-dynamic-services
    */
   public function alter(ContainerBuilder $container) {
-    // Prepare group relations discovery.
+    // Get list of all modules (custom and contrib).
     $modules = $container->getParameter('container.modules');
-    $discovery = new AnnotatedClassDiscovery(
-      'Plugin/Group/Relation',
-      $container->get('container.namespaces'),
-      'Drupal\group\Annotation\GroupRelationType',
-      []
-    );
+    // Prepare discovery.
+    $discovery = $this->prepareGroupRelationsDiscovery($container);
 
     // Iterate through all group relations and decorate their access_control
     // handler.
@@ -53,4 +65,29 @@ class AccessUnpublishedGroupServiceProvider extends ServiceProviderBase {
     }
   }
 
+  /**
+   * Prepare group relations discovery.
+   *
+   * The discovery changed between Group contrib versions 2.2.0 and 2.3.1 and
+   * as a consequence it had to be copied from
+   * `\Drupal\group\GroupServiceProvider::alter()`.
+   *
+   * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
+   *   The ContainerBuilder whose service definitions can be altered.
+   *
+   * @return \Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery
+   *   The discovery of group relations.
+   *
+   * @see \Drupal\group\GroupServiceProvider::alter()
+   */
+  protected function prepareGroupRelationsDiscovery(ContainerBuilder $container) {
+    return new AttributeDiscoveryWithAnnotations(
+      'Plugin/Group/Relation',
+      $container->get('container.namespaces'),
+      GroupRelationType::class,
+      'Drupal\group\Annotation\GroupRelationType',
+      []
+    );
+  }
+
 }
-- 
GitLab


From 46385e5e02241771066f11589b03f8c8adb24cb5 Mon Sep 17 00:00:00 2001
From: Dalibor Matura <dalibor.matura@gmail.com>
Date: Fri, 7 Mar 2025 14:38:00 +0800
Subject: [PATCH 4/4] Added Drupal 11 compatibility fixes.

---
 .../access_unpublished_group/access_unpublished_group.info.yml  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/modules/access_unpublished_group/access_unpublished_group.info.yml b/modules/access_unpublished_group/access_unpublished_group.info.yml
index 34f6a9e..cff067b 100644
--- a/modules/access_unpublished_group/access_unpublished_group.info.yml
+++ b/modules/access_unpublished_group/access_unpublished_group.info.yml
@@ -2,7 +2,7 @@ name: 'Access Unpublished Group'
 type: module
 description: 'Supports using Access Unpublished with Group content.'
 package: Content
-core_version_requirement: ^9 || ^10
+core_version_requirement: ^9.2 || ^10 || ^11
 dependencies:
   - 'access_unpublished:access_unpublished'
   - 'group:group'
-- 
GitLab