This repository has been archived by the owner on Aug 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 132
Group content entity operations ~~hook~~ event #684
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
ccf35d4
Allow modules to alter access to group content entity operations.
pfrenssen 48688c5
Merge branch '8.x-1.x' into group-content-entity-operations-hook
pfrenssen f8e2743
Merge branch '8.x-1.x' into group-content-entity-operations-hook
pfrenssen a02029a
Document access hooks with examples.
pfrenssen a2b7552
Address review remarks.
pfrenssen 433113a
Merge remote-tracking branch 'origin/8.x-1.x' into group-content-enti…
pfrenssen 6b99ae8
If in PHP objects are truly passed by reference then this test should…
pfrenssen b6ff921
Revert pass by reference
MPParsley d839572
Apply suggestions from code review
pfrenssen 47f1967
Convert the hook to alter access event for group content entity opera…
pfrenssen 8d03abc
Adhere to coding standards.
pfrenssen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?php | ||
|
||
/** | ||
* @file | ||
* Hooks provided by the Organic Groups module. | ||
*/ | ||
|
||
declare(strict_types = 1); | ||
|
||
use Drupal\Core\Cache\CacheableMetadata; | ||
use Drupal\Core\Entity\EntityPublishedInterface; | ||
use Drupal\og\OgAccess; | ||
|
||
/** | ||
* @addtogroup hooks | ||
* @{ | ||
*/ | ||
|
||
/** | ||
* Allows modules to alter group level permissions. | ||
* | ||
* @param array $permissions | ||
* The list of group level permissions, passed by reference. | ||
* @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata | ||
* The cache metadata. | ||
* @param array $context | ||
* An associative array containing contextual information, with keys: | ||
* - 'permission': The group level permission being checked, as a string. | ||
* - 'group': The group entity on which the permission applies. | ||
* - 'user': The user account for which access is being determined. | ||
*/ | ||
function hook_og_user_access_alter(array &$permissions, CacheableMetadata $cacheable_metadata, array $context): void { | ||
// This example implements a use case where a custom module allows site | ||
// builders to toggle a configuration setting that will prevent groups to be | ||
// deleted if they are published. | ||
// Retrieve the module configuration. | ||
$config = \Drupal::config('mymodule.settings'); | ||
|
||
// Check if the site is configured to allow deletion of published groups. | ||
$published_groups_can_be_deleted = $config->get('delete_published_groups'); | ||
|
||
// If deletion is not allowed and the group is published, revoke the | ||
// permission. | ||
$group = $context['group']; | ||
if ($group instanceof EntityPublishedInterface && !$group->isPublished() && !$published_groups_can_be_deleted) { | ||
$key = array_search(OgAccess::DELETE_GROUP_PERMISSION, $permissions); | ||
if ($key !== FALSE) { | ||
unset($permissions[$key]); | ||
} | ||
} | ||
|
||
// Since our access result depends on our custom module configuration, we need | ||
// to add it to the cache metadata. | ||
$cacheable_metadata->addCacheableDependency($config); | ||
} | ||
|
||
/** | ||
* @} End of "addtogroup hooks". | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Drupal\og\Event; | ||
|
||
use Drupal\Core\Access\AccessResult; | ||
use Drupal\Core\Access\AccessResultInterface; | ||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface; | ||
use Drupal\Core\Cache\RefinableCacheableDependencyTrait; | ||
use Drupal\Core\Entity\ContentEntityInterface; | ||
use Drupal\Core\Session\AccountInterface; | ||
use Symfony\Component\EventDispatcher\Event; | ||
|
||
/** | ||
* Base class for OG access events. | ||
*/ | ||
class AccessEventBase extends Event implements AccessEventInterface { | ||
|
||
use RefinableCacheableDependencyTrait; | ||
|
||
/** | ||
* The access result. | ||
* | ||
* @var \Drupal\Core\Access\AccessResultInterface | ||
*/ | ||
protected $access; | ||
|
||
/** | ||
* The group that provides the context for the access check. | ||
* | ||
* @var \Drupal\Core\Entity\ContentEntityInterface | ||
*/ | ||
protected $group; | ||
|
||
/** | ||
* The user for which to check access. | ||
* | ||
* @var \Drupal\Core\Session\AccountInterface | ||
*/ | ||
protected $user; | ||
|
||
/** | ||
* Constructs an AccessEventBase event. | ||
* | ||
* @param \Drupal\Core\Entity\ContentEntityInterface $group | ||
* The group that provides the context in which to perform the access check. | ||
* @param \Drupal\Core\Session\AccountInterface $user | ||
* The user for which to check access. | ||
*/ | ||
public function __construct(ContentEntityInterface $group, AccountInterface $user) { | ||
$this->group = $group; | ||
$this->user = $user; | ||
$this->access = AccessResult::neutral(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function grantAccess(): void { | ||
$this->access = $this->access->orIf(AccessResult::allowed()); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function denyAccess(): void { | ||
$this->access = $this->access->orIf(AccessResult::forbidden()); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getGroup(): ContentEntityInterface { | ||
return $this->group; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getUser(): AccountInterface { | ||
return $this->user; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getAccessResult(): AccessResultInterface { | ||
$access = $this->access; | ||
|
||
if ($access instanceof RefinableCacheableDependencyInterface) { | ||
$access->addCacheableDependency($this); | ||
} | ||
|
||
return $access; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Drupal\og\Event; | ||
|
||
use Drupal\Core\Access\AccessResultInterface; | ||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface; | ||
use Drupal\Core\Entity\ContentEntityInterface; | ||
use Drupal\Core\Session\AccountInterface; | ||
|
||
/** | ||
* Interface for events that determine access in Organic Groups. | ||
*/ | ||
interface AccessEventInterface extends RefinableCacheableDependencyInterface { | ||
|
||
/** | ||
* Declare that access is being granted. | ||
* | ||
* Calling this method will cause access to be granted for the action that is | ||
* being checked, unless another event listener denies access. | ||
*/ | ||
public function grantAccess(): void; | ||
|
||
/** | ||
* Declare that access is being denied. | ||
* | ||
* Calling this method will cause access to be denied for the action that is | ||
* being checked. This takes precedence over any other event listeners that | ||
* might grant access. | ||
*/ | ||
public function denyAccess(): void; | ||
|
||
/** | ||
* Returns the group that provides the context for the access check. | ||
* | ||
* @return \Drupal\Core\Entity\ContentEntityInterface | ||
* The group entity. | ||
*/ | ||
public function getGroup(): ContentEntityInterface; | ||
|
||
/** | ||
* Returns the user for which access is being determined. | ||
* | ||
* @return \Drupal\Core\Session\AccountInterface | ||
* The user. | ||
*/ | ||
public function getUser(): AccountInterface; | ||
|
||
/** | ||
* Returns the current access result object. | ||
* | ||
* @return \Drupal\Core\Access\AccessResultInterface | ||
* The access result object. | ||
*/ | ||
public function getAccessResult(): AccessResultInterface; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<?php | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Drupal\og\Event; | ||
|
||
use Drupal\Core\Entity\ContentEntityInterface; | ||
use Drupal\Core\Session\AccountInterface; | ||
|
||
/** | ||
* Event that determines access to group content entity operations. | ||
*/ | ||
class GroupContentEntityOperationAccessEvent extends AccessEventBase implements GroupContentEntityOperationAccessEventInterface { | ||
|
||
/** | ||
* The entity operation being performed. | ||
* | ||
* @var string | ||
*/ | ||
protected $operation; | ||
|
||
/** | ||
* The group content entity upon which the operation is being performed. | ||
* | ||
* @var \Drupal\Core\Entity\ContentEntityInterface | ||
*/ | ||
protected $groupContent; | ||
|
||
/** | ||
* Constructs a GroupContentEntityOperationAccessEvent. | ||
* | ||
* @param string $operation | ||
* The entity operation, such as "create", "update" or "delete". | ||
* @param \Drupal\Core\Entity\ContentEntityInterface $group | ||
* The group in scope of which the access check is being performed. | ||
* @param \Drupal\Core\Entity\ContentEntityInterface $groupContent | ||
* The group content upon which the entity operation is performed. | ||
* @param \Drupal\Core\Session\AccountInterface $user | ||
* The user for which to check access. | ||
*/ | ||
public function __construct(string $operation, ContentEntityInterface $group, ContentEntityInterface $groupContent, AccountInterface $user) { | ||
parent::__construct($group, $user); | ||
$this->operation = $operation; | ||
$this->groupContent = $groupContent; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getOperation(): string { | ||
return $this->operation; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getGroupContent(): ContentEntityInterface { | ||
return $this->groupContent; | ||
} | ||
|
||
} |
35 changes: 35 additions & 0 deletions
35
src/Event/GroupContentEntityOperationAccessEventInterface.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Drupal\og\Event; | ||
|
||
use Drupal\Core\Entity\ContentEntityInterface; | ||
|
||
/** | ||
* Interface for events that provide access to group content entity operations. | ||
*/ | ||
interface GroupContentEntityOperationAccessEventInterface extends AccessEventInterface { | ||
|
||
/** | ||
* The event name. | ||
*/ | ||
const EVENT_NAME = 'og.group_content_entity_operation_access'; | ||
|
||
/** | ||
* Returns the entity operation being performed. | ||
* | ||
* @return string | ||
* The entity operation, such as 'create', 'update' or 'delete'. | ||
*/ | ||
public function getOperation(): string; | ||
|
||
/** | ||
* Returns the group content entity upon which the operation is performed. | ||
* | ||
* @return \Drupal\Core\Entity\ContentEntityInterface | ||
* The group content entity. | ||
*/ | ||
public function getGroupContent(): ContentEntityInterface; | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe worth adding a comment - I'm not clear on this part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ensures that our code adheres correctly to our interface and avoids a potential fatal error.
We are returning an
AccessResultInterface
object, and this doesn't offer::addCacheableDependency()
so we are not supposed to call this method. But in reality all access objects in Drupal core are extendingAccessResult
, which does have this method because it also implementsRefinableCacheableDependencyInterface
. So in typical Drupal code cache metadata is supported and we can call this method. Drupal is full of these kind of discrepancies, and often developers ignore these cases where a method is part of a different interface.It is not reliable though. It is always possible someone will implement their own
AccessResultInterface
object which does not implementRefinableCacheableDependencyInterface
and then we get a fatal error. Just putting this simpleif
statement here makes sure we are never going to throw a fatal error if somebody is using a custom implementation.We in fact are offering extra functionality (cache metadata support) in case the object we are dealing with supports it. This is commonly called "type widening" in PHP. Modern IDE's like PHPStorm have become very good at pointing out these kind of errors.
I created a PR to document this section: #697