Skip to content

Commit

Permalink
Merge pull request #8 from digitalutsc/ContextIntegration
Browse files Browse the repository at this point in the history
Integrated Context Reaction with FITS Actions
  • Loading branch information
kylehuynh205 authored Sep 8, 2024
2 parents 15c7481 + e6cf3d1 commit 3391e94
Show file tree
Hide file tree
Showing 10 changed files with 446 additions and 11 deletions.
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

0 comments on commit 3391e94

Please sign in to comment.