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