diff --git a/islandora.info.yml b/islandora.info.yml
index 0e0a265e..bf13796e 100644
--- a/islandora.info.yml
+++ b/islandora.info.yml
@@ -31,3 +31,4 @@ dependencies:
- content_translation
- flysystem
- token
+ - hook_post_action
diff --git a/islandora.module b/islandora.module
index 3d415094..c61becb3 100644
--- a/islandora.module
+++ b/islandora.module
@@ -106,7 +106,6 @@ function islandora_node_delete(NodeInterface $node) {
*/
function islandora_media_insert(MediaInterface $media) {
$utils = \Drupal::service('islandora.utils');
-
// Execute index reactions.
$utils->executeMediaReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $media);
@@ -136,7 +135,6 @@ function islandora_media_update(MediaInterface $media) {
// Execute index reactions.
$utils->executeMediaReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $media);
-
// Does it have a source field?
$source_field = $media_source_service->getSourceFieldName($media->bundle());
if (empty($source_field)) {
@@ -147,7 +145,6 @@ function islandora_media_update(MediaInterface $media) {
if ($media->get($source_field)->equals($media->original->get($source_field))) {
return;
}
-
// If it has a parent node...
$node = $utils->getParentNode($media);
if ($node) {
@@ -157,6 +154,7 @@ function islandora_media_update(MediaInterface $media) {
$node,
$media
);
+ $utils->executeMediaReactions('\Drupal\islandora\Plugin\ContextReaction\DerivativeFileReaction', $media);
}
}
@@ -170,6 +168,19 @@ function islandora_media_delete(MediaInterface $media) {
$utils->executeMediaReactions('\Drupal\islandora\Plugin\ContextReaction\DeleteReaction', $media);
}
+/**
+ * Implements hook_ENTITYTYPE_postsave().
+ */
+function islandora_media_postsave(EntityInterface $media, $op) {
+
+ $utils = \Drupal::service('islandora.utils');
+ // Add derived file to the media.
+ if ($op == 'insert') {
+ $utils->executeMediaReactions('\Drupal\islandora\Plugin\ContextReaction\DerivativeFileReaction', $media);
+ }
+
+}
+
/**
* Implements hook_file_insert().
*/
diff --git a/islandora.permissions.yml b/islandora.permissions.yml
index 3ea34595..e69de29b 100644
--- a/islandora.permissions.yml
+++ b/islandora.permissions.yml
@@ -1,12 +0,0 @@
-
-view checksums:
- title: 'View Checksums'
- description: 'Allows access to viewing file checksums'
-
-manage members:
- title: 'Manage Members'
- description: 'Allows access to managing members for content'
-
-manage media:
- title: 'Manage Media'
- description: 'Allows access to managing media for content'
diff --git a/islandora.routing.yml b/islandora.routing.yml
index 58dea1a2..ddfd93f1 100644
--- a/islandora.routing.yml
+++ b/islandora.routing.yml
@@ -57,3 +57,17 @@ islandora.media_source_put_to_node:
_custom_access: '\Drupal\islandora\Controller\MediaSourceController::putToNodeAccess'
options:
_auth: ['basic_auth', 'cookie', 'jwt_auth']
+
+islandora.attach_file_to_media:
+ path: '/media/add_derivative/{media}/{destination_field}'
+ defaults:
+ _controller: '\Drupal\islandora\Controller\MediaSourceController::attachToMedia'
+ methods: [GET, PUT]
+ requirements:
+ _custom_access: '\Drupal\islandora\Controller\MediaSourceController::attachToMediaAccess'
+ options:
+ _auth: ['basic_auth', 'cookie', 'jwt_auth']
+ no_cache: 'TRUE'
+ parameters:
+ media:
+ type: entity:media
diff --git a/modules/islandora_audio/config/schema/islandora_audio.info.yml b/modules/islandora_audio/config/schema/islandora_audio.info.yml
new file mode 100644
index 00000000..47d08ee3
--- /dev/null
+++ b/modules/islandora_audio/config/schema/islandora_audio.info.yml
@@ -0,0 +1,28 @@
+action.configuration.generate_audio_derivative:
+ type: mapping
+ label: 'Generate a audio derivative...'
+ mapping:
+ queue:
+ type: text
+ label: 'Queue'
+ event:
+ type: text
+ label: 'Event Type'
+ source_term_uri:
+ type: text
+ label: 'Source term uri'
+ derivative_term_uri:
+ type: text
+ label: 'Destination term uri'
+ mimetype:
+ type: text
+ label: 'Audio Mimetype'
+ args:
+ type: text
+ label: 'FFMpeg Arguments'
+ scheme:
+ type: text
+ label: 'Flysystem scheme'
+ path:
+ type: text
+ label: 'File path with extension'
diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml b/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml
index 58e3c959..e69de29b 100644
--- a/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml
+++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml
@@ -1,6 +0,0 @@
-services:
- islandora_breadcrumbs.breadcrumb:
- class: Drupal\islandora_breadcrumbs\IslandoraBreadcrumbBuilder
- arguments: ['@entity_type.manager', '@config.factory']
- tags:
- - { name: breadcrumb_builder, priority: 100 }
diff --git a/modules/islandora_image/config/schema/islandora_image.info.yml b/modules/islandora_image/config/schema/islandora_image.info.yml
new file mode 100644
index 00000000..350516d0
--- /dev/null
+++ b/modules/islandora_image/config/schema/islandora_image.info.yml
@@ -0,0 +1,28 @@
+action.configuration.generate_image_derivative:
+ type: mapping
+ label: 'Generate an image derivative...'
+ mapping:
+ queue:
+ type: text
+ label: 'Queue'
+ event:
+ type: text
+ label: 'Event Type'
+ source_term_uri:
+ type: text
+ label: 'Source term uri'
+ derivative_term_uri:
+ type: text
+ label: 'Destination term uri'
+ mimetype:
+ type: text
+ label: 'Image Mimetype'
+ args:
+ type: text
+ label: 'Convert Arguments'
+ scheme:
+ type: text
+ label: 'Flysystem scheme'
+ path:
+ type: text
+ label: 'File path with extension'
diff --git a/modules/islandora_image/src/Plugin/Action/GenerateImageDerivativeFile.php b/modules/islandora_image/src/Plugin/Action/GenerateImageDerivativeFile.php
new file mode 100644
index 00000000..b73e163e
--- /dev/null
+++ b/modules/islandora_image/src/Plugin/Action/GenerateImageDerivativeFile.php
@@ -0,0 +1,43 @@
+getSettings();
+ $extensions = $fieldSettings['file_extensions'];
+ if (!strpos($extensions, 'txt')) {
+ $fieldSettings['file_extensions'] .= ' txt';
+ $field->set('settings', $fieldSettings);
+ $field->save();
+ }
+}
diff --git a/modules/islandora_text_extraction/islandora_text_extraction.routing.yml b/modules/islandora_text_extraction/islandora_text_extraction.routing.yml
new file mode 100644
index 00000000..e28a056b
--- /dev/null
+++ b/modules/islandora_text_extraction/islandora_text_extraction.routing.yml
@@ -0,0 +1,13 @@
+islandora_text_extraction.attach_file_to_media:
+ path: '/media/add_ocr/{media}/{destination_field}/{destination_text_field}'
+ defaults:
+ _controller: '\Drupal\islandora_text_extraction\Controller\MediaSourceController::attachToMedia'
+ methods: [GET, PUT]
+ requirements:
+ _custom_access: '\Drupal\islandora\Controller\MediaSourceController::attachToMediaAccess'
+ options:
+ _auth: ['basic_auth', 'cookie', 'jwt_auth']
+ no_cache: 'TRUE'
+ parameters:
+ media:
+ type: entity:media
diff --git a/modules/islandora_text_extraction/src/Controller/MediaSourceController.php b/modules/islandora_text_extraction/src/Controller/MediaSourceController.php
new file mode 100644
index 00000000..efa0a557
--- /dev/null
+++ b/modules/islandora_text_extraction/src/Controller/MediaSourceController.php
@@ -0,0 +1,54 @@
+headers->get('Content-Location', "");
+ $contents = $request->getContent();
+
+ if ($contents) {
+ \Drupal::logger('Alan_dev')->warning("Content location is $content_location");
+ $file = file_save_data($contents, $content_location, FILE_EXISTS_REPLACE);
+ $media->{$destination_field}->setValue([
+ 'target_id' => $file->id(),
+ ]);
+ $media->{$destination_text_field}->setValue(nl2br($contents));
+ $media->save();
+
+ return new Response("
Complete
");
+ }
+ }
+
+}
diff --git a/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivativeFile.php b/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivativeFile.php
new file mode 100644
index 00000000..2c5cbcce
--- /dev/null
+++ b/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivativeFile.php
@@ -0,0 +1,103 @@
+entityFieldManager->getFieldMapByFieldType('text_long');
+ $file_fields = $map['media'];
+ $field_options = array_combine(array_keys($file_fields), array_keys($file_fields));
+ $form = parent::buildConfigurationForm($form, $form_state);
+ $form['mimetype']['#description'] = t('Mimetype to convert to (e.g. application/xml, etc...)');
+ $form['mimetype']['#value'] = 'text/plain';
+ $form['mimetype']['#type'] = 'hidden';
+ $position = array_search('destination_field_name', array_keys($form));
+ $first = array_slice($form, 0, $position);
+ $last = array_slice($form, count($form) - $position + 1);
+
+ $middle['destination_text_field_name'] = [
+ '#required' => TRUE,
+ '#type' => 'select',
+ '#options' => $field_options,
+ '#title' => $this->t('Destination Text field Name'),
+ '#default_value' => $this->configuration['destination_text_field_name'],
+ '#description' => $this->t('Text field on Media Type to hold extracted text.'),
+ ];
+ $form = array_merge($first, $middle, $last);
+
+ unset($form['args']);
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+ parent::validateConfigurationForm($form, $form_state);
+ $exploded_mime = explode('/', $form_state->getValue('mimetype'));
+ if ($exploded_mime[0] != 'text') {
+ $form_state->setErrorByName(
+ 'mimetype',
+ t('Please enter file mimetype (e.g. application/xml.)')
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ parent::submitConfigurationForm($form, $form_state);
+ $this->configuration['destination_text_field_name'] = $form_state->getValue('destination_text_field_name');
+ }
+
+ /**
+ * Override this to return arbitrary data as an array to be json encoded.
+ */
+ protected function generateData(EntityInterface $entity) {
+ $data = parent::generateData($entity);
+ $route_params = [
+ 'media' => $entity->id(),
+ 'destination_field' => $this->configuration['destination_field_name'],
+ 'destination_text_field' => $this->configuration['destination_text_field_name'],
+ ];
+ $data['destination_uri'] = Url::fromRoute('islandora_text_extraction.attach_file_to_media', $route_params)
+ ->setAbsolute()
+ ->toString();
+
+ return $data;
+ }
+
+}
diff --git a/modules/islandora_video/config/schema/islandora_video.info.yml b/modules/islandora_video/config/schema/islandora_video.info.yml
new file mode 100644
index 00000000..2d54e4b0
--- /dev/null
+++ b/modules/islandora_video/config/schema/islandora_video.info.yml
@@ -0,0 +1,28 @@
+action.configuration.generate_video_derivative:
+ type: mapping
+ label: 'Generate a video derivative...'
+ mapping:
+ queue:
+ type: text
+ label: 'Queue'
+ event:
+ type: text
+ label: 'Event Type'
+ source_term_uri:
+ type: text
+ label: 'Source term uri'
+ derivative_term_uri:
+ type: text
+ label: 'Destination term uri'
+ mimetype:
+ type: text
+ label: 'Video Mimetype'
+ args:
+ type: text
+ label: 'FFMpeg Arguments'
+ scheme:
+ type: text
+ label: 'Flysystem scheme'
+ path:
+ type: text
+ label: 'File path with extension'
diff --git a/src/Controller/MediaSourceController.php b/src/Controller/MediaSourceController.php
index 2e5b4b26..269b56a6 100644
--- a/src/Controller/MediaSourceController.php
+++ b/src/Controller/MediaSourceController.php
@@ -18,6 +18,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
+use Drupal\media\Entity\Media;
/**
* Class MediaSourceController.
@@ -210,4 +211,53 @@ class MediaSourceController extends ControllerBase {
return AccessResult::allowedIf($node->access('update', $account) && $account->hasPermission('create media'));
}
+ /**
+ * Adds file to existing media.
+ *
+ * @param \Drupal\media\Entity\Media $media
+ * The media to which file is added.
+ * @param string $destination_field
+ * The name of the media field to add file reference.
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The request object.
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ * 201 on success with a Location link header.
+ *
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ * @throws \Drupal\Core\TypedData\Exception\ReadOnlyException
+ */
+ public function attachToMedia(
+ Media $media,
+ string $destination_field,
+ Request $request) {
+ $content_location = $request->headers->get('Content-Location', "");
+ $contents = $request->getContent();
+ if ($contents) {
+ $file = file_save_data($contents, $content_location, FILE_EXISTS_REPLACE);
+ $media->{$destination_field}->setValue([
+ 'target_id' => $file->id(),
+ ]);
+ $media->save();
+ }
+ return new Response("Complete
");
+ }
+
+ /**
+ * Checks for permissions to update a node and update media.
+ *
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * Account for user making the request.
+ * @param \Drupal\Core\Routing\RouteMatch $route_match
+ * Route match to get Node from url params.
+ *
+ * @return \Drupal\Core\Access\AccessResultInterface
+ * Access result.
+ */
+ public function attachToMediaAccess(AccountInterface $account, RouteMatch $route_match) {
+ $media = $route_match->getParameter('media');
+ $node = $this->utils->getParentNode($media);
+ return AccessResult::allowedIf($node->access('update', $account) && $account->hasPermission('create media'));
+ }
+
}
diff --git a/src/EventGenerator/EventGenerator.php b/src/EventGenerator/EventGenerator.php
index 3175c224..ecc9fee9 100644
--- a/src/EventGenerator/EventGenerator.php
+++ b/src/EventGenerator/EventGenerator.php
@@ -156,7 +156,6 @@ class EventGenerator implements EventGeneratorInterface {
"mediaType" => "application/json",
];
}
-
return json_encode($event);
}
diff --git a/src/Plugin/Action/AbstractGenerateDerivativeMediaFile.php b/src/Plugin/Action/AbstractGenerateDerivativeMediaFile.php
new file mode 100644
index 00000000..9b20eb1e
--- /dev/null
+++ b/src/Plugin/Action/AbstractGenerateDerivativeMediaFile.php
@@ -0,0 +1,318 @@
+utils = $utils;
+ $this->mediaSource = $media_source;
+ $this->token = $token;
+ $this->entityFieldManager = $entity_field_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('current_user'),
+ $container->get('entity_type.manager'),
+ $container->get('islandora.eventgenerator'),
+ $container->get('islandora.stomp'),
+ $container->get('jwt.authentication.jwt'),
+ $container->get('islandora.utils'),
+ $container->get('islandora.media_source_service'),
+ $container->get('token'),
+ $container->get('entity_field.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ $uri = 'http://pcdm.org/use#OriginalFile';
+ return [
+ 'queue' => 'islandora-connector-houdini',
+ 'event' => 'Generate Derivative',
+ 'source_term_uri' => $uri,
+ 'mimetype' => '',
+ 'args' => '',
+ 'scheme' => file_default_scheme(),
+ 'path' => '[date:custom:Y]-[date:custom:m]/[media:mid].bin',
+ 'source_field_name' => 'field_media_file',
+ 'destination_field_name' => '',
+ ];
+ }
+
+ /**
+ * Override this to return arbitrary data as an array to be json encoded.
+ */
+ protected function generateData(EntityInterface $entity) {
+ $data = parent::generateData($entity);
+ if (get_class($entity) != 'Drupal\media\Entity\Media') {
+ return;
+ }
+ $source_file = $this->mediaSource->getSourceFile($entity);
+ if (!$source_file) {
+ throw new \RuntimeException("Could not locate source file for media {$entity->id()}", 500);
+ }
+ $data['source_uri'] = $this->utils->getDownloadUrl($source_file);
+
+ $route_params = [
+ 'media' => $entity->id(),
+ 'destination_field' => $this->configuration['destination_field_name'],
+ ];
+ $data['destination_uri'] = Url::fromRoute('islandora.attach_file_to_media', $route_params)
+ ->setAbsolute()
+ ->toString();
+
+ $token_data = [
+ 'media' => $entity,
+ ];
+ $path = $this->token->replace($data['path'], $token_data);
+ $data['file_upload_uri'] = $data['scheme'] . '://' . $path;
+ $allowed = ['queue',
+ 'event',
+ 'args',
+ 'source_uri',
+ 'destination_uri',
+ 'file_upload_uri',
+ 'mimetype',
+ ];
+ foreach ($data as $key => $value) {
+ if (!in_array($key, $allowed)) {
+ unset($data[$key]);
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $schemes = $this->utils->getFilesystemSchemes();
+ $scheme_options = array_combine($schemes, $schemes);
+ $form = parent::buildConfigurationForm($form, $form_state);
+ $map = $this->entityFieldManager->getFieldMapByFieldType('file');
+ $file_fields = $map['media'];
+ $file_options = array_combine(array_keys($file_fields), array_keys($file_fields));
+ $file_options = array_merge(['' => ''], $file_options);
+ $form['event']['#disabled'] = 'disabled';
+
+ $form['destination_field_name'] = [
+ '#required' => TRUE,
+ '#type' => 'select',
+ '#options' => $file_options,
+ '#title' => $this->t('Destination File field Name'),
+ '#default_value' => $this->configuration['destination_field_name'],
+ '#description' => $this->t('File field on Media Type to hold derivative. Cannot be the same as source'),
+ ];
+ $form['mimetype'] = [
+ '#type' => 'textfield',
+ '#title' => t('Mimetype'),
+ '#default_value' => $this->configuration['mimetype'],
+ '#required' => TRUE,
+ '#rows' => '8',
+ '#description' => t('Mimetype to convert to (e.g. image/jpeg, video/mp4, etc...)'),
+ ];
+ $form['args'] = [
+ '#type' => 'textfield',
+ '#title' => t('Additional arguments'),
+ '#default_value' => $this->configuration['args'],
+ '#rows' => '8',
+ '#description' => t('Additional command line arguments'),
+ ];
+ $form['scheme'] = [
+ '#type' => 'select',
+ '#title' => t('File system'),
+ '#options' => $scheme_options,
+ '#default_value' => $this->configuration['scheme'],
+ '#required' => TRUE,
+ ];
+ $form['path'] = [
+ '#type' => 'textfield',
+ '#title' => t('File path'),
+ '#default_value' => $this->configuration['path'],
+ '#description' => t('Path within the upload destination where files will be stored. Includes the filename and optional extension.'),
+ ];
+ $form['queue'] = [
+ '#type' => 'textfield',
+ '#title' => t('Queue name'),
+ '#default_value' => $this->configuration['queue'],
+ '#description' => t('Queue name to send along to help routing events, CHANGE WITH CARE. Defaults to :queue', [
+ ':queue' => $this->defaultConfiguration()['queue'],
+ ]),
+ ];
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+ parent::validateConfigurationForm($form, $form_state);
+
+ $exploded_mime = explode('/', $form_state->getValue('mimetype'));
+
+ if (count($exploded_mime) != 2) {
+ $form_state->setErrorByName(
+ 'mimetype',
+ t('Please enter a mimetype (e.g. image/jpeg, video/mp4, audio/mp3, etc...)')
+ );
+ }
+
+ if (empty($exploded_mime[1])) {
+ $form_state->setErrorByName(
+ 'mimetype',
+ t('Please enter a mimetype (e.g. image/jpeg, video/mp4, audio/mp3, etc...)')
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ parent::submitConfigurationForm($form, $form_state);
+ $this->configuration['mimetype'] = $form_state->getValue('mimetype');
+ $this->configuration['args'] = $form_state->getValue('args');
+ $this->configuration['scheme'] = $form_state->getValue('scheme');
+ $this->configuration['path'] = trim($form_state->getValue('path'), '\\/');
+ $this->configuration['destination_field_name'] = $form_state->getValue('destination_field_name');
+ }
+
+ /**
+ * Find a media_type by id and return it or nothing.
+ *
+ * @param string $entity_id
+ * The media type.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface|string
+ * Return the loaded entity or nothing.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ * Thrown by getStorage() if the entity type doesn't exist.
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * Thrown by getStorage() if the storage handler couldn't be loaded.
+ */
+ protected function getEntityById($entity_id) {
+ $entity_ids = $this->entityTypeManager->getStorage('media_type')
+ ->getQuery()->condition('id', $entity_id)->execute();
+
+ $id = reset($entity_ids);
+ if ($id !== FALSE) {
+ return $this->entityTypeManager->getStorage('media_type')->load($id);
+ }
+ return '';
+ }
+
+}
diff --git a/src/Plugin/Action/DeleteMediaAndFile.php b/src/Plugin/Action/DeleteMediaAndFile.php
index 2e9d1df1..eeea0cb1 100644
--- a/src/Plugin/Action/DeleteMediaAndFile.php
+++ b/src/Plugin/Action/DeleteMediaAndFile.php
@@ -9,6 +9,7 @@ use Drupal\Core\Session\AccountInterface;
use Drupal\islandora\MediaSource\MediaSourceService;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\file\Entity\File;
/**
* Deletes a media and its source file.
@@ -101,6 +102,15 @@ class DeleteMediaAndFile extends ActionBase implements ContainerFactoryPluginInt
$source_field = $this->mediaSourceService->getSourceFieldName($entity->bundle());
foreach ($entity->get($source_field)->referencedEntities() as $file) {
$file->delete();
+ // Check for non-source fields;
+ foreach ($entity->getFieldDefinitions() as $field) {
+ if ($field->getType() == 'file') {
+ $fid = $entity->get($field->getName())->getValue()[0]['target_id'];
+ if ($fid) {
+ File::load($fid)->delete();
+ }
+ }
+ }
}
$entity->delete();
}
diff --git a/src/Plugin/ContextReaction/DerivativeFileReaction.php b/src/Plugin/ContextReaction/DerivativeFileReaction.php
new file mode 100644
index 00000000..c0562fa6
--- /dev/null
+++ b/src/Plugin/ContextReaction/DerivativeFileReaction.php
@@ -0,0 +1,58 @@
+actionStorage->loadByProperties(['type' => 'media']);
+
+ foreach ($actions as $action) {
+ $plugin = $action->getPlugin();
+ if ($plugin instanceof AbstractGenerateDerivativeMediaFile) {
+ $options[ucfirst($action->getType())][$action->id()] = $action->label();
+ }
+ }
+ $config = $this->getConfiguration();
+ $form['actions'] = [
+ '#title' => $this->t('Actions'),
+ '#description' => $this->t('Pre-configured actions to execute. Multiple actions may be selected by shift or ctrl clicking.'),
+ '#type' => 'select',
+ '#multiple' => TRUE,
+ '#options' => $options,
+ '#default_value' => isset($config['actions']) ? $config['actions'] : '',
+ '#size' => 15,
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute(EntityInterface $entity = NULL) {
+ $config = $this->getConfiguration();
+ $action_ids = $config['actions'];
+ foreach ($action_ids as $action_id) {
+ $action = $this->actionStorage->load($action_id);
+ $action->execute([$entity]);
+ }
+ }
+
+}
diff --git a/src/Plugin/ContextReaction/DerivativeReaction.php b/src/Plugin/ContextReaction/DerivativeReaction.php
index 7ddaed4f..d6d50988 100644
--- a/src/Plugin/ContextReaction/DerivativeReaction.php
+++ b/src/Plugin/ContextReaction/DerivativeReaction.php
@@ -2,6 +2,8 @@
namespace Drupal\islandora\Plugin\ContextReaction;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\islandora\Plugin\Action\AbstractGenerateDerivative;
use Drupal\islandora\PresetReaction\PresetReaction;
/**
@@ -12,4 +14,32 @@ use Drupal\islandora\PresetReaction\PresetReaction;
* label = @Translation("Derivative")
* )
*/
-class DerivativeReaction extends PresetReaction {}
+class DerivativeReaction extends PresetReaction {
+ /**
+ * {@inheritdoc}
+ */
+
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $actions = $this->actionStorage->loadMultiple();
+ $options = [];
+ foreach ($actions as $action) {
+ $plugin = $action->getPlugin();
+ if ($plugin instanceof AbstractGenerateDerivative) {
+ $options[ucfirst($action->getType())][$action->id()] = $action->label();
+ }
+ }
+ $config = $this->getConfiguration();
+ $form['actions'] = [
+ '#title' => $this->t('Actions'),
+ '#description' => $this->t('Pre-configured actions to execute. Multiple actions may be selected by shift or ctrl clicking.'),
+ '#type' => 'select',
+ '#multiple' => TRUE,
+ '#options' => $options,
+ '#default_value' => isset($config['actions']) ? $config['actions'] : '',
+ '#size' => 15,
+ ];
+
+ return $form;
+ }
+
+}
diff --git a/src/Plugin/ContextReaction/MappingUriPredicateReaction.php b/src/Plugin/ContextReaction/MappingUriPredicateReaction.php
new file mode 100644
index 00000000..05f2f8c0
--- /dev/null
+++ b/src/Plugin/ContextReaction/MappingUriPredicateReaction.php
@@ -0,0 +1,174 @@
+mediaSource = $media_source;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('config.factory'),
+ $container->get('islandora.utils'),
+ $container->get('islandora.media_source_service')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function summary() {
+ return $this->t('Map Drupal URI to configured predicate.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute(EntityInterface $entity = NULL, array &$normalized = NULL, array $context = NULL) {
+ $config = $this->getConfiguration();
+ $drupal_predicate = $config[self::URI_PREDICATE];
+ if (!is_null($drupal_predicate) && !empty($drupal_predicate)) {
+ $url = $this->getSubjectUrl($entity);
+ if ($context['needs_jsonldcontext'] === FALSE) {
+ $drupal_predicate = NormalizerBase::escapePrefix($drupal_predicate, $context['namespaces']);
+ }
+ if (isset($normalized['@graph']) && is_array($normalized['@graph'])) {
+ foreach ($normalized['@graph'] as &$graph) {
+ if (isset($graph['@id']) && $graph['@id'] == $url) {
+ // Swap media and file urls.
+ if ($entity instanceof MediaInterface) {
+ $file = $this->mediaSource->getSourceFile($entity);
+ $graph['@id'] = $this->utils->getDownloadUrl($file);
+ }
+ if (isset($graph[$drupal_predicate])) {
+ if (!is_array($graph[$drupal_predicate])) {
+ if ($graph[$drupal_predicate] == $url) {
+ // Don't add it if it already exists.
+ return;
+ }
+ $tmp = $graph[$drupal_predicate];
+ $graph[$drupal_predicate] = [$tmp];
+ }
+ elseif (array_search($url, array_column($graph[$drupal_predicate], '@id'))) {
+ // Don't add it if it already exists.
+ return;
+ }
+ }
+ else {
+ $graph[$drupal_predicate] = [];
+ }
+ $graph[$drupal_predicate][] = ["@id" => $url];
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $config = $this->getConfiguration();
+ $form[self::URI_PREDICATE] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Drupal URI predicate'),
+ '#description' => $this->t("The Drupal object's URI will be added to the resource with this predicate. Must use a defined prefix."),
+ '#default_value' => isset($config[self::URI_PREDICATE]) ? $config[self::URI_PREDICATE] : '',
+ '#size' => 35,
+ ];
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+ $drupal_predicate = $form_state->getValue(self::URI_PREDICATE);
+ if (!is_null($drupal_predicate) and !empty($drupal_predicate)) {
+ if (preg_match('/^https?:\/\//', $drupal_predicate)) {
+ // Can't validate all URIs so we have to trust them.
+ return;
+ }
+ elseif (preg_match('/^([^\s:]+):/', $drupal_predicate, $matches)) {
+ $predicate_prefix = $matches[1];
+ $rdf = rdf_get_namespaces();
+ $rdf_prefixes = array_keys($rdf);
+ if (!in_array($predicate_prefix, $rdf_prefixes)) {
+ $form_state->setErrorByName(
+ self::URI_PREDICATE,
+ $this->t('Namespace prefix @prefix is not registered.',
+ ['@prefix' => $predicate_prefix]
+ )
+ );
+ }
+ }
+ else {
+ $form_state->setErrorByName(
+ self::URI_PREDICATE,
+ $this->t('Predicate must use a defined prefix or be a full URI')
+ );
+ }
+ }
+ parent::validateConfigurationForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ $this->setConfiguration([self::URI_PREDICATE => $form_state->getValue(self::URI_PREDICATE)]);
+ }
+
+}
diff --git a/tests/src/Functional/MappingUriPredicateReactionTest.php b/tests/src/Functional/MappingUriPredicateReactionTest.php
new file mode 100644
index 00000000..bf7bff6e
--- /dev/null
+++ b/tests/src/Functional/MappingUriPredicateReactionTest.php
@@ -0,0 +1,195 @@
+ ['schema:dateCreated'],
+ 'datatype' => 'xsd:dateTime',
+ 'datatype_callback' => ['callable' => 'Drupal\rdf\CommonDataConverter::dateIso8601Value'],
+ ];
+
+ // Save bundle mapping config.
+ $this->rdfMapping = rdf_get_mapping('node', 'test_type')
+ ->setBundleMapping(['types' => $types])
+ ->setFieldMapping('created', $created_mapping)
+ ->setFieldMapping('title', [
+ 'properties' => ['dc:title'],
+ 'datatype' => 'xsd:string',
+ ])
+ ->save();
+
+ $this->container->get('router.builder')->rebuildIfNeeded();
+ }
+
+ /**
+ * @covers \Drupal\islandora\Plugin\ContextReaction\MappingUriPredicateReaction
+ */
+ public function testMappingReaction() {
+ $account = $this->drupalCreateUser([
+ 'bypass node access',
+ 'administer contexts',
+ ]);
+ $this->drupalLogin($account);
+
+ $context_name = 'test';
+ $reaction_id = 'islandora_map_uri_predicate';
+
+ $this->postNodeAddForm('test_type',
+ ['title[0][value]' => 'Test Node'],
+ t('Save'));
+ $this->assertSession()->pageTextContains("Test Node");
+ $url = $this->getUrl();
+
+ // Make sure the node exists.
+ $this->drupalGet($url);
+ $this->assertSession()->statusCodeEquals(200);
+
+ $contents = $this->drupalGet($url . '?_format=jsonld');
+ $this->assertSession()->statusCodeEquals(200);
+ $json = \GuzzleHttp\json_decode($contents, TRUE);
+ $this->assertArrayHasKey('http://purl.org/dc/terms/title',
+ $json['@graph'][0], 'Missing dcterms:title key');
+ $this->assertEquals(
+ 'Test Node',
+ $json['@graph'][0]['http://purl.org/dc/terms/title'][0]['@value'],
+ 'Missing title value'
+ );
+ $this->assertArrayNotHasKey('http://www.w3.org/2002/07/owl#sameAs',
+ $json['@graph'][0], 'Has predicate when not configured');
+
+ $this->createContext('Test', $context_name);
+ $this->drupalGet("admin/structure/context/$context_name/reaction/add/$reaction_id");
+ $this->assertSession()->statusCodeEquals(200);
+
+ $this->drupalGet("admin/structure/context/$context_name");
+ // Can't use an undefined prefix.
+ $this->getSession()->getPage()
+ ->fillField("Drupal URI predicate", "bob:smith");
+ $this->getSession()->getPage()->pressButton("Save and continue");
+ $this->assertSession()
+ ->pageTextContains("Namespace prefix bob is not registered");
+
+ // Can't use a straight string.
+ $this->getSession()->getPage()
+ ->fillField("Drupal URI predicate", "woohoo");
+ $this->getSession()->getPage()->pressButton("Save and continue");
+ $this->assertSession()
+ ->pageTextContains("Predicate must use a defined prefix or be a full URI");
+
+ // Use an existing prefix.
+ $this->getSession()->getPage()
+ ->fillField("Drupal URI predicate", "owl:sameAs");
+ $this->getSession()->getPage()->pressButton("Save and continue");
+ $this->assertSession()
+ ->pageTextContains("The context $context_name has been saved");
+
+ $new_contents = $this->drupalGet($url . '?_format=jsonld');
+ $json = \GuzzleHttp\json_decode($new_contents, TRUE);
+ $this->assertEquals(
+ 'Test Node',
+ $json['@graph'][0]['http://purl.org/dc/terms/title'][0]['@value'],
+ 'Missing title value'
+ );
+ $this->assertEquals(
+ "$url?_format=jsonld",
+ $json['@graph'][0]['http://www.w3.org/2002/07/owl#sameAs'][0]['@id'],
+ 'Missing alter added predicate.'
+ );
+
+ $this->drupalGet("admin/structure/context/$context_name");
+ // Change to a random URL.
+ $this->getSession()->getPage()
+ ->fillField("Drupal URI predicate", "http://example.org/first/second");
+ $this->getSession()->getPage()->pressButton("Save and continue");
+ $this->assertSession()
+ ->pageTextContains("The context $context_name has been saved");
+ $new_contents = $this->drupalGet($url . '?_format=jsonld');
+ $json = \GuzzleHttp\json_decode($new_contents, TRUE);
+ $this->assertEquals(
+ 'Test Node',
+ $json['@graph'][0]['http://purl.org/dc/terms/title'][0]['@value'],
+ 'Missing title value'
+ );
+ $this->assertArrayNotHasKey('http://www.w3.org/2002/07/owl#sameAs',
+ $json['@graph'][0], 'Still has old predicate');
+ $this->assertEquals(
+ "$url?_format=jsonld",
+ $json['@graph'][0]['http://example.org/first/second'][0]['@id'],
+ 'Missing alter added predicate.'
+ );
+ }
+
+ /**
+ * @covers \Drupal\islandora\Plugin\ContextReaction\MappingUriPredicateReaction
+ */
+ public function testMappingReactionForMedia() {
+ $account = $this->drupalCreateUser([
+ 'create media',
+ 'view media',
+ 'administer contexts',
+ ]);
+ $this->drupalLogin($account);
+
+ $context_name = 'test';
+ $reaction_id = 'islandora_map_uri_predicate';
+
+ list($file, $media) = $this->makeMediaAndFile($account);
+ $media_url = $media->url('canonical', ['absolute' => TRUE]);
+ $file_url = $file->url('canonical', ['absolute' => TRUE]);
+
+ $this->drupalGet($media_url);
+ $this->assertSession()->statusCodeEquals(200);
+
+ $contents = $this->drupalGet($media_url . '?_format=jsonld');
+ $this->assertSession()->statusCodeEquals(200);
+ $json = \GuzzleHttp\json_decode($contents, TRUE);
+ $this->assertEquals(
+ "$media_url?_format=jsonld",
+ $json['@graph'][0]['@id'],
+ 'Swapped file and media urls when not configured'
+ );
+ $this->assertArrayNotHasKey('http://www.iana.org/assignments/relation/describedby',
+ $json['@graph'][0], 'Has predicate when not configured');
+
+ $this->createContext('Test', $context_name);
+ $this->drupalGet("admin/structure/context/$context_name/reaction/add/$reaction_id");
+ $this->assertSession()->statusCodeEquals(200);
+
+ // Use an existing prefix.
+ $this->getSession()->getPage()
+ ->fillField("Drupal URI predicate", "iana:describedby");
+ $this->getSession()->getPage()->pressButton("Save and continue");
+ $this->assertSession()
+ ->pageTextContains("The context $context_name has been saved");
+
+ $new_contents = $this->drupalGet($media_url . '?_format=jsonld');
+ $json = \GuzzleHttp\json_decode($new_contents, TRUE);
+ $this->assertEquals(
+ "$media_url?_format=jsonld",
+ $json['@graph'][0]['http://www.iana.org/assignments/relation/describedby'][0]['@id'],
+ 'Missing alter added predicate.'
+ );
+ $this->assertEquals(
+ $file_url,
+ $json['@graph'][0]['@id'],
+ 'Alter did not swap "@id" of media with file url.'
+ );
+
+ }
+
+}