From this moment, OS doesn't allow adding or using any hook_node_grants() to control access to nodes.
There are a few reasons why we should get rid of node grants:
- The patch is a hard dependency for
gnodemodule - Some node grants providers are very heavy (for example,
entity_access_by_field_node_grants_alter()) - Uncachable pages (
user.node_grants:viewcache context is unique per user (the main reason isentity_access_by_field_node_grants()))
If hook_node_grants() is added to the code, the appropriate notification will be highlighted on Status page, and (what is more important to know ☝️) the results of any query for nodes will be broken.
We added the alternative approach with query builders. In OS all related hook_node_grants() hooks were replaced by event subscribers. The node query building flow looks like

So, what was changed in general?
- All
hook_node_grants()and related were removed \Drupal\social_node\QueryAccess\NodeEntityQueryAlter::alterQuery()was added as the entry point for altering the node query-
Event subscribers added to determine the access to nodes:
\Drupal\social_course\EventSubscriber\NodeQueryAccessAlterSubscriber::alterQueryAccess()\Drupal\social_event_managers\EventSubscriber\NodeQueryAccessAlterSubscriber::alterQueryAccess()\Drupal\social_group\EventSubscriber\NodeQueryAccessAlterSubscriber::alterQueryAccess()\Drupal\social_node\EventSubscriber\NodeQueryAccessAlterSubscriber::alterQueryAccess()
-
entity_access_by_fieldandcontent_accesssearch API processors removed from views \Drupal\social_search\Plugin\search_api\processor\TaggingQueryadded allows altering search queries-
A list of new search API processors added to alter search queries for nodes:
\Drupal\social_event_managers\Plugin\search_api\processor\SearchApiQueryAlter::preprocessSearchQuery()\Drupal\social_group\Plugin\search_api\processor\SearchApiQueryAlter::preprocessSearchQuery()\Drupal\social_node\Plugin\search_api\processor\SearchApiQueryAlter::preprocessSearchQuery()
What should I do if I have custom hooks with node grants?
First, replace each hook with an appropriate event subscriber. We recommend to use \Drupal\social_node\SocialNodeQueryAccessAlterInterface
A very common implementation could look like:
<?php
declare(strict_types=1);
namespace Drupal\my_module\EventSubscriber;
use Drupal\social_node\Event\NodeQueryAccessEvent;
use Drupal\social_node\Event\SocialNodeEvents;
use Drupal\social_node\SocialNodeQueryAccessAlterInterface;
class NodeQueryAccessAlterSubscriber implements SocialNodeQueryAccessAlterInterface {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
if (!class_exists(SocialNodeEvents::class)) {
return [];
}
$events[SocialNodeEvents::NODE_QUERY_ACCESS_ALTER][] = ['alterQueryAccess'];
return $events;
}
/**
* {@inheritdoc}
*/
public function alterQueryAccess(NodeQueryAccessEvent $event): void {
$account = $event->account();
$or = $event->getConditions();
// Make sure a user is a moderator.
$moderators_table = $event->ensureNodeFieldTableJoin('field_moderators');
$or->condition("$moderators_table.field_moderators_target_id", $account->id());
}
}
In case I'm using search API need to create a new search API processor. We don't have the interface for it but in general, the implementation could look like
<?php
declare(strict_types=1);
namespace Drupal\my_module\Plugin\search_api\processor;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\search_api\LoggerTrait;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Drupal\search_api\Query\ConditionGroupInterface;
use Drupal\search_api\Query\QueryInterface;
use Drupal\social_search\Plugin\search_api\SocialSearchSearchApiProcessorTrait;
use Drupal\social_search\Utility\SocialSearchApi;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @SearchApiProcessor(
* id = "my_module_query_alter",
* stages = {
* "pre_index_save" = 0,
* "preprocess_query" = 100,
* },
* locked = true,
* hidden = true,
* )
*/
class SearchApiQueryAlter extends ProcessorPluginBase {
use LoggerTrait;
use SocialSearchSearchApiProcessorTrait;
/**
* {@inheritdoc}
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
protected EntityFieldManagerInterface $entityFieldManager,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_field.manager'),
);
}
/**
* {@inheritdoc}
*/
public static function getIndexData(): array {
return [
'node' => [
'field_moderators' => ['type' => 'integer'],
],
];
}
/**
* {@inheritdoc}
*/
public function preprocessSearchQuery(QueryInterface $query): void {
/* @see \Drupal\social_search\Plugin\search_api\processor\TaggingQuery::preprocessSearchQuery() */
$or = SocialSearchApi::findTaggedQueryConditionsGroup('social_entity_type_node_access', $query->getConditionGroup());
if (!$or instanceof ConditionGroupInterface) {
return;
}
$account = $query->getOption('social_search_access_account');
$field_event_managers = $this->findField('entity:node', 'field_moderators');
if (!$field_event_managers) {
// The field doesn't exist in the index.
return;
}
$or->addCondition($field_event_managers->getFieldIdentifier(), $account->id());
}
}
| Attachment | Size |
|---|---|
| Screenshot 2025-02-19 at 13.46.30.png | 137.62 KB |