Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated Context Reaction with FITS Actions #8

Merged
merged 3 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ This Drupal 8/9 module consumes File Information Tool Set (Fits) to retrieve and

![Jmespath config](https://www.drupal.org/files/project-images/Screen%20Shot%202021-06-23%20at%2011.54.52%20PM.png)

# Enabling FITS generation
- To have FITS generate metadata info, you must make use of the Context module
- Go to `Structure > Context`
- Create a Context and choose the Conditions that should be true for the FITS action to proceed
- Under `Reaction` add a Reaction and pick `File Extract Metadata Reaction (FITS)`
- In the Action form that shows up for the Reaction, just pick `FITS - Generate and Extract Technical metadata for File`
- Ensure the context is enabled and then save

## Usage

- Upload a file at `/file/add` or add a Media at `/media/add`.
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"drupal/json_field": "^1.2",
"drupal/file_entity": "^2.0@RC",
"drupal/field_permissions": "^1.2",
"drupal/advancedqueue": "^1.0@RC"
"drupal/advancedqueue": "^1.0@RC",
"drupal/context":"^5.0@RC"
},
"require-dev": {
"phpunit/phpunit": "^8",
Expand Down
18 changes: 18 additions & 0 deletions config/optional/context.context.fits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
langcode: en
status: true
dependencies:
module:
- fits
label: Fits
name: fits
group: 'Technical Metadata'
description: 'Generate metadata for Files'
requireAllConditions: false
disabled: false
conditions: { }
reactions:
fits_reaction:
id: fits_reaction
actions: fits_action
saved: false
weight: 0
14 changes: 4 additions & 10 deletions fits.module
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ function fits_theme() {
}

/**
* Implement fits_entity_insert().
* Implement fits_file_insert().
*/
function fits_entity_insert(EntityInterface $entity) {
function fits_file_insert(EntityInterface $entity) {
// Only extract Fits for File level only.
if ('File' === $entity->getEntityType()->getLabel()->getUntranslatedString() && 1 === \Drupal::config('fits.fitsconfig')->get('fits-extract-ingesting')) {
execute_fits_action($entity);
Expand All @@ -59,14 +59,8 @@ function execute_fits_action(EntityInterface $entity) {
return;
}
$file = File::load($entity->id());

// Trigger generate Fits action.
$action = \Drupal::entityTypeManager()
->getStorage('action')
->load('fits_action');
if ($action) {
$action->execute([$file]);
}
$utils = \Drupal::service('fits.context_utils');
$utils->executeFileReactions('\Drupal\fits\Plugin\ContextReaction\FitsReaction', $file);
}

/**
Expand Down
9 changes: 9 additions & 0 deletions fits.services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
fits.context_utils:
class: Drupal\fits\FitsContextUtils
arguments: ['@entity_type.manager', '@context.repository', '@context.handler', '@entity.form_builder', '@theme.manager', '@current_route_match']
fits.file_route_context_provider:
class: Drupal\fits\ContextProvider\FileRouteContextProvider
arguments: ['@current_route_match']
tags:
- { name: 'context_provider' }
50 changes: 50 additions & 0 deletions src/ContextProvider/FileContextProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Drupal\fits\ContextProvider;

use Drupal\Core\Plugin\Context\ContextProviderInterface;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\file\FileInterface;

/**
* Sets the provided media as a context.
*/
class FileContextProvider implements ContextProviderInterface {

use StringTranslationTrait;

/**
* File to provide in a context.
*
* @var \Drupal\file\FileInterface
*/
protected $file;

/**
* Constructs a new FileContextProvider.
*
* @var \Drupal\file\FileInterface $file
* The file to provide in a context.
*/
public function __construct(FileInterface $file) {
$this->file = $file;
}

/**
* {@inheritdoc}
*/
public function getRuntimeContexts(array $unqualified_context_ids) {
$context = EntityContext::fromEntity($this->file);
return ['@fits.file_route_context_provider:file' => $context];
}

/**
* {@inheritdoc}
*/
public function getAvailableContexts() {
$context = EntityContext::fromEntityTypeId('file', $this->t('File from entity hook'));
return ['@fits.file_route_context_provider:file' => $context];
}

}
72 changes: 72 additions & 0 deletions src/ContextProvider/FileRouteContextProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Drupal\fits\ContextProvider;

use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextProviderInterface;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Plugin\Context\EntityContextDefinition;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;

/**
* Sets the current file as a context on file routes.
*/
class FileRouteContextProvider implements ContextProviderInterface {

use StringTranslationTrait;

/**
* The route match object.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;

/**
* Constructs a new FileRouteContextProvider.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match object.
*/
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}

/**
* {@inheritdoc}
*/
public function getRuntimeContexts(array $unqualified_context_ids) {
$context_definition = EntityContextDefinition::fromEntityTypeId('file')->setLabel(NULL)->setRequired(FALSE);

$value = NULL;

$route_object = $this->routeMatch->getRouteObject();
if ($route_object) {
$route_contexts = $route_object->getOption('parameters');
if ($route_contexts && isset($route_contexts['file'])) {
$file = $this->routeMatch->getParameter('file');
if ($file) {
$value = $file;
}
}
}

$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['route']);

$context = new Context($context_definition, $value);
$context->addCacheableDependency($cacheability);
return ['file' => $context];
}

/**
* {@inheritdoc}
*/
public function getAvailableContexts() {
$context = EntityContext::fromEntityTypeId('file', $this->t('File from URL'));
return ['file' => $context];
}

}
101 changes: 101 additions & 0 deletions src/FitsContextManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace Drupal\fits;

use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\context\ContextInterface;
use Drupal\context\ContextManager;
use Drupal\Core\Condition\ConditionPluginCollection;
use Drupal\Core\Plugin\ContextAwarePluginInterface;

/**
* Provide additional bits to assist Contexts managing.
*/
class FitsContextManager extends ContextManager {

/**
* Evaluate all context conditions.
*
* @param \Drupal\Core\Plugin\Context\Context[] $provided
* Additional provided (core) contexts to apply to Conditions.
*/
public function evaluateContexts(array $provided = []) {
$this->activeContexts = [];
if (!empty($provided)) {
$this->contexts = [];
$this->contextConditionsEvaluated = FALSE;
}
foreach ($this->getContexts() as $context) {
if ($this->evaluateContextConditions($context, $provided) && !$context->disabled()) {
$this->activeContexts[$context->id()] = $context;
}
}
$this->contextConditionsEvaluated = TRUE;
}

/**
* Evaluate a contexts conditions.
*
* @param \Drupal\context\ContextInterface $context
* The context to evaluate conditions for.
* @param \Drupal\Core\Plugin\Context\Context[] $provided
* Additional provided (core) contexts to apply to Conditions.
*
* @return bool
* TRUE if conditions pass
*/
public function evaluateContextConditions(ContextInterface $context, array $provided = []) {
$conditions = $context->getConditions();
if (!$this->applyContexts($conditions, $provided)) {
return FALSE;
}

$logic = $context->requiresAllConditions()
? 'and'
: 'or';

if (!count($conditions)) {
$logic = 'and';
}

return $this->resolveConditions($conditions, $logic);
}

/**
* Apply context to all the context aware conditions in the collection.
*
* @param \Drupal\Core\Condition\ConditionPluginCollection $conditions
* A collection of conditions to apply context to.
* @param \Drupal\Core\Plugin\Context\Context[] $provided
* Additional provided (core) contexts to apply to Conditions.
*
* @return bool
* TRUE if conditions pass
*/
protected function applyContexts(ConditionPluginCollection &$conditions, array $provided = []) {
if (count($conditions) == 0) {
return TRUE;
}
$passed = FALSE;
foreach ($conditions as $condition) {
if ($condition instanceof ContextAwarePluginInterface) {
try {
if (empty($provided)) {
$contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping()));
}
else {
$contexts = $provided;
}
$this->contextHandler->applyContextMapping($condition, $contexts);
$passed = TRUE;
}
catch (ContextException $e) {
continue;
}
}
}

return $passed;
}

}
77 changes: 77 additions & 0 deletions src/FitsContextUtils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Drupal\fits;

use Drupal\Core\Entity\EntityFormBuilderInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\file\FileInterface;
use Drupal\fits\ContextProvider\FileContextProvider;
use Drupal\fits\FitsContextManager;

/**
* Utility functions for firing off context reactions.
*/
class FitsContextUtils {
/**
* Context manager.
*
* @var \Drupal\fits\FitsContextManager
*/
protected $contextManager;

/**
* Constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
* @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $contextRepository
* Context repository.
* @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $contextHandler
* Context handler.
* @param \Drupal\Core\Entity\EntityFormBuilderInterface $entityFormBuilder
* Entity Form Builder.
* @param \Drupal\Core\Theme\ThemeManagerInterface $themeManager
* Theme manager.
* @param \Drupal\Core\Routing\RouteMatchInterface $currentRouteMatch
* Route match.
*/
public function __construct(
EntityTypeManagerInterface $entityTypeManager,
ContextRepositoryInterface $contextRepository,
ContextHandlerInterface $contextHandler,
EntityFormBuilderInterface $entityFormBuilder,
ThemeManagerInterface $themeManager,
RouteMatchInterface $currentRouteMatch
) {
$this->contextManager = new FitsContextManager(
$entityTypeManager,
$contextRepository,
$contextHandler,
$entityFormBuilder,
$themeManager,
$currentRouteMatch
);
}

/**
* Executes context reactions for a File.
*
* @param string $reaction_type
* Reaction type.
* @param \Drupal\node\FileInterface $file
* File to evaluate contexts and pass to reaction.
*/
public function executeFileReactions($reaction_type, FileInterface $file) {
$provider = new FileContextProvider($file);
$provided = $provider->getRuntimeContexts([]);
$this->contextManager->evaluateContexts($provided);
foreach ($this->contextManager->getActiveReactions($reaction_type) as $reaction) {
$reaction->execute($file);
}
}

}
Loading
Loading