Skip to content

Commit

Permalink
Rework to differently relate the embargoes to the affected entities.
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-vessey committed Mar 1, 2024
1 parent b5f9ffa commit 69a2df3
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 236 deletions.
34 changes: 34 additions & 0 deletions embargo.module
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\embargo\EmbargoInterface;
use Drupal\embargo\EmbargoItemList;
use Drupal\embargo\EmbargoStorage;

/**
Expand Down Expand Up @@ -97,3 +102,32 @@ function embargo_theme($existing, $type, $theme, $path) {
],
];
}

/**
* Implements hook_entity_base_field_info().
*/
function embargo_entity_base_field_info(EntityTypeInterface $entity_type) {
if (!in_array($entity_type->id(), ['file', 'media', 'node'])) {
return [];
}

$fields = [];

$fields['embargo'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Embargoes'))
->setDescription(t('Embargoes affecting this item.'))
->setTranslatable(FALSE)
->setRevisionable(FALSE)
->setRequired(FALSE)
->setDisplayConfigurable('view', FALSE)
->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
->setComputed(TRUE)
->setClass(EmbargoItemList::class)
->setSetting('target_type', 'embargo')
->setSetting('embargo_types', match($entity_type->id()) {
'file', 'media' => [EmbargoInterface::EMBARGO_TYPE_NODE, EmbargoInterface::EMBARGO_TYPE_FILE],
'node' => [EmbargoInterface::EMBARGO_TYPE_NODE],
});

return $fields;
}
44 changes: 44 additions & 0 deletions src/EmbargoItemList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Drupal\embargo;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\EntityReferenceFieldItemList;
use Drupal\Core\TypedData\ComputedItemListTrait;

/**
* Track embargo items.
*/
class EmbargoItemList extends EntityReferenceFieldItemList {

use ComputedItemListTrait;

/**
* {@inheritDoc}
*/
protected function computeValue() {
$entity = $this->getEntity();
/** @var \Drupal\embargo\EmbargoStorageInterface $embargo_storage */
$embargo_storage = $this->getEntityTypeManager()->getStorage('embargo');
$this->setValue(array_filter($embargo_storage->getApplicableEmbargoes($entity), function (EmbargoInterface $embargo) {
return in_array($embargo->getEmbargoType(), $this->getSetting('embargo_types'));
}));
}

/**
* Helper; get the entity type manager service.
*
* XXX: Dependency injection does not presently appear to be possible in typed
* data.
*
* @see https://www.drupal.org/node/2053415
* @see https://www.drupal.org/project/drupal/issues/3294266
*
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
* The entity type manager service.
*/
protected function getEntityTypeManager() : EntityTypeManagerInterface {
return \Drupal::entityTypeManager();
}

}
208 changes: 62 additions & 146 deletions src/Plugin/search_api/processor/EmbargoProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@

namespace Drupal\embargo\Plugin\search_api\processor;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
use Drupal\embargo\EmbargoInterface;
use Drupal\embargo\Plugin\search_api\processor\Property\EmbargoInfoProperty;
use Drupal\islandora\IslandoraUtils;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Drupal\search_api\Query\ConditionGroupInterface;
use Drupal\search_api\Query\QueryInterface;
use Drupal\search_api\SearchApiException;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;

Expand All @@ -24,9 +20,9 @@
* @SearchApiProcessor(
* id = "embargo_processor",
* label = @Translation("Embargo access"),
* description = @Translation("Add information regarding embargo access constraints."),
* description = @Translation("Add information regarding embargo access
* constraints."),
* stages = {
* "add_properties" = 20,
* "pre_index_save" = 20,
* "preprocess_query" = 20,
* },
Expand All @@ -45,16 +41,6 @@ class EmbargoProcessor extends ProcessorPluginBase implements ContainerFactoryPl
*/
protected EntityTypeManagerInterface $entityTypeManager;

/**
* Islandora utils helper service.
*
* XXX: Ideally, this would reference an interface; however, such does not
* exist.
*
* @var \Drupal\islandora\IslandoraUtils
*/
protected IslandoraUtils $islandoraUtils;

/**
* The request stack.
*
Expand All @@ -69,6 +55,13 @@ class EmbargoProcessor extends ProcessorPluginBase implements ContainerFactoryPl
*/
protected AccountProxyInterface $currentUser;

/**
* Drupal's time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected TimeInterface $time;

/**
* {@inheritdoc}
*/
Expand All @@ -77,125 +70,29 @@ public static function create(ContainerInterface $container, array $configuratio

$instance->requestStack = $container->get('request_stack');
$instance->entityTypeManager = $container->get('entity_type.manager');
$instance->islandoraUtils = $container->get('islandora.utils');
$instance->currentUser = $container->get('current_user');
$instance->time = $container->get('datetime.time');

return $instance;
}

/**
* {@inheritdoc}
*/
public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) : array {
if ($datasource !== NULL) {
return [];
}

return [
'embargo_info' => new EmbargoInfoProperty([
'label' => $this->t('Embargo info'),
'description' => $this->t('Aggregated embargo info'),
'processor_id' => $this->getPluginId(),
'is_list' => TRUE,
'computed' => TRUE,
]),
];
}

/**
* Get the embargo(es) associated with the given index item.
*
* @param \Drupal\search_api\Item\ItemInterface $item
* The index item to consider.
*
* @return iterable
* A generated sequence of applicable embargoes.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\TypedData\Exception\MissingDataException
* @throws \Drupal\search_api\SearchApiException
*/
protected function getEmbargoes(ItemInterface $item) : iterable {
try {
foreach ($this->entityTypeManager->getStorage('embargo')
->getApplicableEmbargoes($item->getOriginalObject()->getValue()) as $embargo) {
yield $embargo;
}
}
catch (SearchApiException) {
// No-op; object probably did not exist?
}
}

/**
* {@inheritDoc}
*/
public function preIndexSave() {
parent::preIndexSave();

foreach ($this->getPropertyDefinitions() as $base => $def) {
if ($def instanceof ComplexDataDefinitionInterface) {
foreach (array_keys($def->getPropertyDefinitions()) as $name) {
$this->ensureField(NULL, "{$base}:{$name}");
}
}
}
}

/**
* {@inheritdoc}
*/
public function addFieldValues(ItemInterface $item) : void {
$source_type_id = $item->getDatasource()->getEntityTypeId();
if (!in_array($source_type_id, static::ENTITY_TYPES)) {
return;
}

$info = [
'total_count' => 0,
'indefinite_count' => 0,
'scheduled_timestamps' => [],
'exempt_users' => [],
'exempt_ip_ranges' => [],
];

// Get Embargo details and prepare to pass it to index field.
foreach ($this->getEmbargoes($item) as $embargo) {
if ($embargo->getEmbargoType() === EmbargoInterface::EMBARGO_TYPE_FILE && $source_type_id === 'node') {
foreach ($this->index->getDatasources() as $datasource_id => $datasource) {
if (!in_array($datasource->getEntityTypeId(), static::ENTITY_TYPES)) {
continue;
}

$info['total_count']++;
if ($embargo->getExpirationType() === EmbargoInterface::EXPIRATION_TYPE_INDEFINITE) {
$info['indefinite_count']++;
}
else {
$info['scheduled_timestamps'][] = $embargo->getExpirationDate()->getTimestamp();
}

$info['exempt_users'] = array_merge(
$info['exempt_users'],
array_map(function (UserInterface $user) {
return $user->id();
}, $embargo->getExemptUsers()),
);
if ($range_id = $embargo->getExemptIps()?->id()) {
$info['exempt_ip_ranges'][] = $range_id;
}
}

foreach (['scheduled_timestamps', 'exempt_users', 'exempt_ip_ranges'] as $key) {
$info[$key] = array_unique($info[$key]);
}

foreach ($info as $key => $val) {
if ($field = $this->findField(NULL, "embargo_info:{$key}")) {
$item_field = $item->getField($field->getFieldIdentifier(), FALSE);
foreach ((is_array($val) ? $val : [$val]) as $value) {
$item_field->addValue($value);
}
}
$this->ensureField($datasource_id, 'embargo:entity:id', 'integer');
$this->ensureField($datasource_id, 'embargo:entity:embargo_type', 'integer');
$this->ensureField($datasource_id, 'embargo:entity:expiration_date', 'date');
$this->ensureField($datasource_id, 'embargo:entity:expiration_type', 'integer');
$this->ensureField($datasource_id, 'embargo:entity:exempt_ips:entity:id', 'integer');
$this->ensureField($datasource_id, 'embargo:entity:exempt_users:entity:uid', 'integer');
}
}

Expand All @@ -216,41 +113,62 @@ public function preprocessSearchQuery(QueryInterface $query) : void {
return;
}

$and_group = $query->createAndAddConditionGroup(tags: ['embargo_processor', 'embargo_access']);
foreach (array_keys($applicable_datasources) as $datasource_id) {
if ($filter = $this->addEmbargoFilters($datasource_id, $query)) {
$and_group->addConditionGroup($filter);
}
}
}

/**
* Add embargo filters to the given query, for the given datasource.
*
* @param string $datasource_id
* The ID of the datasource for which to add filters.
* @param \Drupal\search_api\Query\QueryInterface $query
* The query to which to add filters.
*/
protected function addEmbargoFilters(string $datasource_id, QueryInterface $query) : ?ConditionGroupInterface {
$or_group = $query->createConditionGroup('OR', [
'embargo_processor',
'embargo_access',
"embargo:$datasource_id",
]);

// No embargo.
if ($field = $this->findField(NULL, 'embargo_info:total_count')) {
$or_group->addCondition($field->getFieldIdentifier(), 0);
if ($field = $this->findField($datasource_id, 'embargo:entity:id')) {
$or_group->addCondition($field->getFieldIdentifier(), NULL);
}

// Embargo durations.
// No indefinite embargo.
if ($indefinite_field = $this->findField(NULL, 'embargo_info:indefinite_count')) {
$scheduled_group = $query->createConditionGroup(tags: [
'embargo_scheduled',
]);
$scheduled_group->addCondition($indefinite_field->getFieldIdentifier(), 0);

// No scheduled embargo in the future.
// XXX: Might not quite work? If there's a single scheduled embargo
// lesser, would it open it?
if ($scheduled_field = $this->findField(NULL, 'embargo_info:scheduled_timestamps')) {
$scheduled_group->addCondition($scheduled_field->getFieldIdentifier(), [
0 => time() + 1,
1 => PHP_INT_MAX,
if ($expiration_type_field = $this->findField($datasource_id, 'embargo:entity:expiration_type')) {
$or_group->addCondition($expiration_type_field->getFieldIdentifier(), EmbargoInterface::EXPIRATION_TYPE_INDEFINITE, '<>');

// Scheduled embargo in the past and none in the future.
if ($scheduled_field = $this->findField($datasource_id, 'embargo:entity:expiration_date')) {
$schedule_group = $query->createConditionGroup(tags: ['embargo_scheduled']);

$schedule_group->addCondition($expiration_type_field->getFieldIdentifier(), EmbargoInterface::EXPIRATION_TYPE_SCHEDULED);
// Embargo in the past.
$schedule_group->addCondition($scheduled_field->getFieldIdentifier(), date('Y-m-d', $this->time->getRequestTime()), '<=');
// No embargo in the future.
$schedule_group->addCondition($scheduled_field->getFieldIdentifier(), [
0 => date('Y-m-d', strtotime('+1 DAY', $this->time->getRequestTime())),
1 => date('Y-m-d', PHP_INT_MAX),
], 'NOT BETWEEN');

$or_group->addConditionGroup($schedule_group);
}
$or_group->addConditionGroup($scheduled_group);
}

if (!$this->currentUser->isAnonymous() && ($field = $this->findField(NULL, 'embargo_info:exempt_users'))) {
if (
!$this->currentUser->isAnonymous() &&
($field = $this->findField($datasource_id, 'embargo:entity:exempt_users:entity:uid'))
) {
$or_group->addCondition($field->getFieldIdentifier(), $this->currentUser->id());
}

if ($field = $this->findField(NULL, 'embargo_info:exempt_ip_ranges')) {
if ($field = $this->findField($datasource_id, 'embargo:entity:exempt_ips:entity:id')) {
/** @var \Drupal\embargo\IpRangeStorageInterface $ip_range_storage */
$ip_range_storage = $this->entityTypeManager->getStorage('embargo_ip_range');
foreach ($ip_range_storage->getApplicableIpRanges($this->requestStack->getCurrentRequest()
Expand All @@ -259,9 +177,7 @@ public function preprocessSearchQuery(QueryInterface $query) : void {
}
}

if (count($or_group->getConditions()) > 0) {
$query->addConditionGroup($or_group);
}
return (count($or_group->getConditions()) > 0) ? $or_group : NULL;
}

}
Loading

0 comments on commit 69a2df3

Please sign in to comment.