The MediaAccess custom processor plugin is a testament to Drupal's flexibility, offering enhanced media access controls during the indexing and querying processes through the Search API and Permissions by Term Module
<?php
namespace Drupal\custom_module\Plugin\search_api\processor;
use Drupal\Core\Database\Connection;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\LoggerTrait;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Drupal\search_api\Processor\ProcessorProperty;
use Drupal\search_api\Query\QueryInterface;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Adds media access checks for media
*
* @SearchApiProcessor(
* id = "media_access",
* label = @Translation("Media access"),
* description = @Translation("Adds media access checks for media type"),
* stages = {
* "add_properties" = 0,
* "pre_index_save" = -10,
* "preprocess_query" = -30,
* },
* )
*/
class MediaAccess extends ProcessorPluginBase {
use LoggerTrait;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection|null
*/
protected $database;
/**
* The current_user service used by this plugin.
*
* @var \Drupal\Core\Session\AccountProxyInterface|null
*/
protected $currentUser;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
/** @var static $processor */
$processor = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$processor->setLogger($container->get('logger.channel.search_api'));
$processor->setDatabase($container->get('database'));
$processor->setCurrentUser($container->get('current_user'));
return $processor;
}
/**
* Retrieves the database connection.
*
* @return \Drupal\Core\Database\Connection
* The database connection.
*/
public function getDatabase() {
return $this->database ?: \Drupal::database();
}
/**
* Sets the database connection.
*
* @param \Drupal\Core\Database\Connection $database
* The new database connection.
*
* @return $this
*/
public function setDatabase(Connection $database) {
$this->database = $database;
return $this;
}
/**
* Retrieves the current user.
*
* @return \Drupal\Core\Session\AccountProxyInterface
* The current user.
*/
public function getCurrentUser() {
return $this->currentUser ?: \Drupal::currentUser();
}
/**
* Sets the current user.
*
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The current user.
*
* @return $this
*/
public function setCurrentUser(AccountProxyInterface $current_user) {
$this->currentUser = $current_user;
return $this;
}
/**
* {@inheritdoc}
*/
public static function supportsIndex(IndexInterface $index) {
foreach ($index->getDatasources() as $datasource) {
if (in_array($datasource->getEntityTypeId(), ['media'])) {
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
$properties = [];
if (!$datasource) {
$definition = [
'label' => $this->t('Media access information'),
'description' => $this->t('Data needed to apply media access.'),
'type' => 'string',
'processor_id' => $this->getPluginId(),
'hidden' => TRUE,
'is_list' => TRUE,
];
$properties['search_api_media_grants'] = new ProcessorProperty($definition);
}
return $properties;
}
/**
* {@inheritdoc}
*/
public function addFieldValues(ItemInterface $item) {
static $anonymous_user;
if (!isset($anonymous_user)) {
// Load the anonymous user.
$anonymous_user = new AnonymousUserSession();
}
// Only run for media items.
$entity_type_id = $item->getDatasource()->getEntityTypeId();
if (!in_array($entity_type_id, ['media'])) {
return;
}
// Get the media object.
$media = $this->getMedia($item->getOriginalObject());
if (!$media) {
// Apparently we were active for a wrong item.
return;
}
$fields = $item->getFields();
$fields = $this->getFieldsHelper()
->filterForPropertyPath($fields, NULL, 'search_api_media_grants');
$media_access_terms = [];
$media_access_terms = $media->field_media_access_terms->referencedEntities();
$field = reset($fields);
if (!empty($media_access_terms)) {
foreach ($media_access_terms as $term) {
$field->addValue('media_access_' . $term->id());
}
} else {
// Add the generic pseudo view grant if we are not using media access.
$field->addValue('media_access__all');
}
}
/**
* {@inheritdoc}
*/
public function preIndexSave() {
foreach ($this->index->getDatasources() as $datasource_id => $datasource) {
$entity_type = $datasource->getEntityTypeId();
if (in_array($entity_type, ['media'])) {
$this->ensureField($datasource_id, 'status', 'boolean');
}
}
$field = $this->ensureField(NULL, 'search_api_media_grants', 'string');
$field->setHidden();
}
/**
* Retrieves the media related to an indexed search object.
*
* Will be the media itself.
*
* @param \Drupal\Core\TypedData\ComplexDataInterface $item
* A search object that is being indexed.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The media related to that search object.
*/
protected function getMedia(ComplexDataInterface $item) {
$item = $item->getValue();
if ($item instanceof EntityInterface) {
return $item;
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function preprocessSearchQuery(QueryInterface $query) {
if (!$query->getOption('search_api_bypass_access')) {
$account = $query->getOption('search_api_access_account', $this->getCurrentUser());
if (is_numeric($account)) {
$account = User::load($account);
}
if ($account instanceof AccountInterface) {
$this->addMediaAccess($query, $account);
} else {
$account = $query->getOption('search_api_access_account', $this->getCurrentUser());
if ($account instanceof AccountInterface) {
$account = $account->id();
}
if (!is_scalar($account)) {
$account = var_export($account, TRUE);
}
$this->getLogger()->warning('An illegal user UID was given for media access: @uid.', ['@uid' => $account]);
}
}
}
/**
* Adds a Media access filter to a search query, if applicable.
*
* @param \Drupal\search_api\Query\QueryInterface $query
* The query to which a media access filter should be added, if applicable.
* @param \Drupal\Core\Session\AccountInterface $account
* The user for whom the search is executed.
*/
protected function addMediaAccess(QueryInterface $query, AccountInterface $account) {
// Don't do anything if the user can access all content.
if ($account->hasPermission('bypass media access')) {
return;
}
$access_conditions = $query;
$access_storage = \Drupal::service('permissions_by_term.access_storage');
$user_media_access_terms = $access_storage->getPermittedTids($account->id(), $account->getRoles());
// Filter by the user's media access grants.
$media_grants_field = $this->findField(NULL, 'search_api_media_grants', 'string');
if (!$media_grants_field) {
return;
}
$media_grants_field_id = $media_grants_field->getFieldIdentifier();
$grants_conditions = $query->createConditionGroup('OR', ['media_access_grants']);
if (isset($user_media_access_terms)) {
foreach ($user_media_access_terms as $term) {
$grants_conditions->addCondition($media_grants_field_id, 'media_access' . $term;
}
}
// Also add items that are accessible for everyone by checking the "access all" pseudo grant.
$grants_conditions->addCondition($media_grants_field_id, 'media_access__all');
$access_conditions->addConditionGroup($grants_conditions);
}
}
References: The content access processor plugin from the search API contrib module.
1. web/modules/contrib/search_api/src/Plugin/search_api/processor/ContentAccess.php
Pre-requisites: Knowledge of Drupal Framework and its Components.
Audience: Drupal Developers