Browse Source

Upload form (#754)

* Upload form

* Missing var doc comment

* Manage Collections Page

* Getting rid of stuff in other PRs

* Adapting form to be in line with proposed modeling changes

* Cleaning up settings form schema

* Removing duplicate helper function

* Update ManageMembersController.php

* Coding standards, copy/paste detector (embarassing...)

* Apparantly i don't know how pass-by-reference works

* Incorrectly named config key.  Sadly confusing :(

* s/field_tags/field_model

* Fixing phpunit annotations that point to non-existant classes

* Foiled by a newline

* Wrong class for access function

* Renaming links to say 'Batch Upload'

* Letting user select media type instead of guessing
pull/837/head
dannylamb 4 years ago committed by GitHub
parent
commit
5127e7f3ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      config/schema/islandora.schema.yml
  2. 16
      islandora.links.action.yml
  3. 21
      islandora.routing.yml
  4. 9
      src/Controller/ManageMediaController.php
  5. 26
      src/Controller/ManageMembersController.php
  6. 246
      src/Form/AddChildrenForm.php
  7. 368
      src/Form/AddMediaForm.php
  8. 38
      src/Form/IslandoraSettingsForm.php
  9. 60
      src/IslandoraUtils.php
  10. 60
      tests/src/Functional/AddChildTest.php
  11. 46
      tests/src/Functional/AddMediaTest.php
  12. 2
      tests/src/Functional/GenerateDerivativeTestBase.php
  13. 4
      tests/src/Functional/IslandoraFunctionalTestBase.php
  14. 4
      tests/src/Functional/LinkHeaderTest.php
  15. 17
      tests/src/Functional/NodeHasTermTest.php

6
config/schema/islandora.schema.yml

@ -14,6 +14,12 @@ islandora.settings:
jwt_expiry: jwt_expiry:
type: string type: string
label: 'How long JWTs should last before expiring.' label: 'How long JWTs should last before expiring.'
upload_form_location:
type: string
label: 'Upload Form Location'
upload_form_allowed_mimetypes:
type: string
label: 'Upload Form Allowed Extensions'
gemini_url: gemini_url:
type: uri type: uri
label: 'Url to Gemini microservice' label: 'Url to Gemini microservice'

16
islandora.links.action.yml

@ -1,12 +1,24 @@
islandora.upload_media:
route_name: islandora.upload_media
title: Batch Upload Media
appears_on:
- view.media_of.page_1
islandora.add_media_to_node: islandora.add_media_to_node:
route_name: islandora.add_media_to_node_page route_name: islandora.add_media_to_node_page
title: Add media title: Add Media
appears_on: appears_on:
- view.media_of.page_1 - view.media_of.page_1
islandora.upload_children:
route_name: islandora.upload_children
title: Batch Upload Children
appears_on:
- view.manage_members.page_1
islandora.add_member_to_node: islandora.add_member_to_node:
route_name: islandora.add_member_to_node_page route_name: islandora.add_member_to_node_page
title: Add child title: Add Child
appears_on: appears_on:
- view.manage_members.page_1 - view.manage_members.page_1

21
islandora.routing.yml

@ -36,6 +36,17 @@ islandora.add_member_to_node_page:
requirements: requirements:
_entity_create_any_access: 'node' _entity_create_any_access: 'node'
islandora.upload_children:
path: '/node/{node}/members/upload'
defaults:
_form: '\Drupal\islandora\Form\AddChildrenForm'
_title: 'Upload Children'
options:
_admin_route: 'TRUE'
requirements:
_access: 'TRUE'
#_permssion: 'create node,create media'
islandora.add_media_to_node_page: islandora.add_media_to_node_page:
path: '/node/{node}/media/add' path: '/node/{node}/media/add'
defaults: defaults:
@ -47,6 +58,16 @@ islandora.add_media_to_node_page:
requirements: requirements:
_entity_create_any_access: 'media' _entity_create_any_access: 'media'
islandora.upload_media:
path: '/node/{node}/media/upload'
defaults:
_form: '\Drupal\islandora\Form\AddMediaForm'
_title: 'Add media'
options:
_admin_route: 'TRUE'
requirements:
_custom_access: '\Drupal\islandora\Form\AddMediaForm::access'
islandora.media_source_update: islandora.media_source_update:
path: '/media/{media}/source' path: '/media/{media}/source'
defaults: defaults:

9
src/Controller/ManageMediaController.php

@ -2,6 +2,7 @@
namespace Drupal\islandora\Controller; namespace Drupal\islandora\Controller;
use Drupal\islandora\IslandoraUtils;
use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Routing\RouteMatch;
use Drupal\node\Entity\Node; use Drupal\node\Entity\Node;
@ -22,13 +23,15 @@ class ManageMediaController extends ManageMembersController {
* Array of media types to add. * Array of media types to add.
*/ */
public function addToNodePage(NodeInterface $node) { public function addToNodePage(NodeInterface $node) {
$field = IslandoraUtils::MEDIA_OF_FIELD;
return $this->generateTypeList( return $this->generateTypeList(
'media', 'media',
'media_type', 'media_type',
'entity.media.add_form', 'entity.media.add_form',
'entity.media_type.add_form', 'entity.media_type.add_form',
$node, $field,
'field_media_of' ['query' => ["edit[$field][widget][0][target_id]" => $node->id()]]
); );
} }
@ -47,7 +50,7 @@ class ManageMediaController extends ManageMembersController {
if (!$node instanceof NodeInterface) { if (!$node instanceof NodeInterface) {
$node = Node::load($node); $node = Node::load($node);
} }
if ($node->hasField('field_model') && $node->hasField('field_member_of')) { if ($this->utils->isIslandoraType($node->getEntityTypeId(), $node->bundle())) {
return AccessResult::allowed(); return AccessResult::allowed();
} }
} }

26
src/Controller/ManageMembersController.php

@ -7,6 +7,7 @@ use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Entity\Controller\EntityController; use Drupal\Core\Entity\Controller\EntityController;
use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Link; use Drupal\Core\Link;
use Drupal\islandora\IslandoraUtils;
use Drupal\node\NodeInterface; use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
@ -36,6 +37,13 @@ class ManageMembersController extends EntityController {
*/ */
protected $renderer; protected $renderer;
/**
* Islandora Utils.
*
* @var \Drupal\islandora\IslandoraUtils
*/
protected $utils;
/** /**
* Constructor. * Constructor.
* *
@ -45,15 +53,19 @@ class ManageMembersController extends EntityController {
* The entity field manager. * The entity field manager.
* @param \Drupal\Core\Render\RendererInterface $renderer * @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer. * The renderer.
* @param \Drupal\islandora\IslandoraUtils $utils
* Islandora utils.
*/ */
public function __construct( public function __construct(
EntityTypeManagerInterface $entity_type_manager, EntityTypeManagerInterface $entity_type_manager,
EntityFieldManagerInterface $entity_field_manager, EntityFieldManagerInterface $entity_field_manager,
RendererInterface $renderer RendererInterface $renderer,
IslandoraUtils $utils
) { ) {
$this->entityTypeManager = $entity_type_manager; $this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager; $this->entityFieldManager = $entity_field_manager;
$this->renderer = $renderer; $this->renderer = $renderer;
$this->utils = $utils;
} }
/** /**
@ -63,7 +75,8 @@ class ManageMembersController extends EntityController {
return new static( return new static(
$container->get('entity_type.manager'), $container->get('entity_type.manager'),
$container->get('entity_field.manager'), $container->get('entity_field.manager'),
$container->get('renderer') $container->get('renderer'),
$container->get('islandora.utils')
); );
} }
@ -74,20 +87,21 @@ class ManageMembersController extends EntityController {
* Node you want to add a member to. * Node you want to add a member to.
*/ */
public function addToNodePage(NodeInterface $node) { public function addToNodePage(NodeInterface $node) {
$field = IslandoraUtils::MEMBER_OF_FIELD;
return $this->generateTypeList( return $this->generateTypeList(
'node', 'node',
'node_type', 'node_type',
'node.add', 'node.add',
'node.type_add', 'node.type_add',
$node, $field,
'field_member_of' ['query' => ["edit[$field][widget][0][target_id]" => $node->id()]]
); );
} }
/** /**
* Renders a list of content types to add as members. * Renders a list of content types to add as members.
*/ */
protected function generateTypeList($entity_type, $bundle_type, $entity_add_form, $bundle_add_form, NodeInterface $node, $field) { protected function generateTypeList($entity_type, $bundle_type, $entity_add_form, $bundle_add_form, $field, array $url_options) {
$type_definition = $this->entityTypeManager->getDefinition($bundle_type); $type_definition = $this->entityTypeManager->getDefinition($bundle_type);
$build = [ $build = [
@ -115,7 +129,7 @@ class ManageMembersController extends EntityController {
$bundle->label(), $bundle->label(),
$entity_add_form, $entity_add_form,
[$bundle_type => $bundle->id()], [$bundle_type => $bundle->id()],
['query' => ["edit[$field][widget][0][target_id]" => $node->id()]] $url_options
), ),
]; ];
} }

246
src/Form/AddChildrenForm.php

@ -0,0 +1,246 @@
<?php
namespace Drupal\islandora\Form;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Url;
use Drupal\islandora\IslandoraUtils;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Form that lets users upload one or more files as children to a resource node.
*/
class AddChildrenForm extends AddMediaForm {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'add_children_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$upload_pattern = $this->config->get(IslandoraSettingsForm::UPLOAD_FORM_LOCATION);
$upload_location = $this->token->replace($upload_pattern);
$valid_extensions = $this->config->get(IslandoraSettingsForm::UPLOAD_FORM_ALLOWED_MIMETYPES);
$this->parentId = $this->routeMatch->getParameter('node');
$parent = $this->entityTypeManager->getStorage('node')->load($this->parentId);
// File upload widget.
$form['upload'] = [
'#type' => 'managed_file',
'#title' => $this->t('Files'),
'#description' => $this->t("Upload one or more files to add children to @title", ['@title' => $parent->getTitle()]),
'#upload_location' => $upload_location,
'#upload_validators' => [
'file_validate_extensions' => [$valid_extensions],
],
'#multiple' => TRUE,
];
// Drop down to select content type.
$options = [];
foreach ($this->entityTypeBundleInfo->getBundleInfo('node') as $bundle_id => $bundle) {
$options[$bundle_id] = $bundle['label'];
};
$form['bundle'] = [
'#type' => 'select',
'#title' => $this->t('Content type'),
'#description' => $this->t('Each child created will have this content type.'),
'#options' => $options,
'#required' => TRUE,
];
// Find bundles that don't have field_model.
$bundles_with_model = [];
foreach (array_keys($options) as $bundle) {
$fields = $this->entityFieldManager->getFieldDefinitions('node', $bundle);
if (isset($fields[IslandoraUtils::MODEL_FIELD])) {
$bundles_with_model[] = $bundle;
}
}
// Model drop down.
// Only shows up if the selected bundle has field_model.
$options = [];
foreach ($this->entityTypeManager->getStorage('taxonomy_term')->loadTree('islandora_models', 0, NULL, TRUE) as $term) {
$options[$term->id()] = $term->getName();
};
$form['model'] = [
'#type' => 'select',
'#title' => $this->t('Model'),
'#description' => $this->t('Each child will be tagged with this model.'),
'#options' => $options,
'#states' => [
'visible' => [],
'required' => [],
],
];
if (!empty($bundles_with_model)) {
foreach ($bundles_with_model as $bundle) {
$form['model']['#states']['visible'][] = [':input[name="bundle"]' => ['value' => $bundle]];
$form['model']['#states']['required'][] = [':input[name="bundle"]' => ['value' => $bundle]];
}
}
$this->addMediaType($form);
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Get the parent.
$parent_id = $this->routeMatch->getParameter('node');
$parent = $this->entityTypeManager->getStorage('node')->load($parent_id);
// Hack values out of the form.
$fids = $form_state->getValue('upload');
$bundle = $form_state->getValue('bundle');
$model_tid = $form_state->getValue('model');
$media_type = $form_state->getValue('media_type');
$use_tids = $form_state->getValue('use');
// Create an operation for each uploaded file.
$operations = [];
foreach ($fids as $fid) {
$operations[] = [
[$this, 'buildNodeForFile'],
[$fid, $parent_id, $bundle, $model_tid, $media_type, $use_tids],
];
}
// Set up and trigger the batch.
$batch = [
'title' => $this->t("Uploading Children for @title", ['@title' => $parent->getTitle()]),
'operations' => $operations,
'progress_message' => t('Processed @current out of @total. Estimated time: @estimate.'),
'error_message' => t('The process has encountered an error.'),
'finished' => [$this, 'buildNodeFinished'],
];
batch_set($batch);
}
/**
* Wires up a file/media/node combo for a file upload.
*
* @param int $fid
* Uploaded file id.
* @param int $parent_id
* Id of the parent node.
* @param string $bundle
* Content type to create.
* @param int|null $model_tid
* Id of the Model term.
* @param string $media_type
* Media type to create.
* @param int[] $use_tids
* Ids of the Media Use terms.
* @param array $context
* Batch context.
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function buildNodeForFile($fid, $parent_id, $bundle, $model_tid, $media_type, array $use_tids, array &$context) {
// Since we make 3 different entities, do this in a transaction.
$transaction = $this->database->startTransaction();
try {
// Set the file to permanent.
$file = $this->entityTypeManager->getStorage('file')->load($fid);
$file->setPermanent();
$file->save();
// Make the resource node.
$parent = $this->entityTypeManager->getStorage('node')->load($parent_id);
$source_field = $this->mediaSource->getSourceFieldName($media_type);
$node = $this->entityTypeManager->getStorage('node')->create([
'type' => $bundle,
'title' => $file->getFileName(),
IslandoraUtils::MEMBER_OF_FIELD => $parent,
'uid' => $this->account->id(),
'status' => 1,
]);
if ($model_tid) {
$node->set(
IslandoraUtils::MODEL_FIELD,
$this->entityTypeManager->getStorage('taxonomy_term')->load($model_tid)
);
}
$node->save();
// Make a media for the uploaded file and assign it to the resource node.
$media = $this->entityTypeManager->getStorage('media')->create([
'bundle' => $media_type,
$source_field => $fid,
'name' => $file->getFileName(),
IslandoraUtils::MEDIA_OF_FIELD => $node,
]);
if (!empty($use_tids)) {
$media->set(IslandoraUtils::MEDIA_USAGE_FIELD, $this->entityTypeManager->getStorage('taxonomy_term')->loadMultiple($use_tids));
}
$media->save();
}
catch (HttpException $e) {
$transaction->rollBack();
throw $e;
}
catch (\Exception $e) {
$transaction->rollBack();
throw new HttpException(500, $e->getMessage());
}
}
/**
* Batch finished callback.
*
* $success bool
* Success status
* $results mixed
* The 'results' from the batch context.
* $operations array
* Remaining operations.
*/
public function buildNodeFinished($success, $results, $operations) {
return new RedirectResponse(
Url::fromRoute('view.manage_members.page_1', ['node' => $this->parentId])->toString()
);
}
/**
* Check if the user can create any "Islandora" nodes and media.
*
* @param \Drupal\Core\Routing\RouteMatch $route_match
* The current routing match.
*
* @return \Drupal\Core\Access\AccessResultAllowed|\Drupal\Core\Access\AccessResultForbidden
* Whether we can or can't show the "thing".
*/
public function access(RouteMatch $route_match) {
$can_create_media = $this->utils->canCreateIslandoraEntity('media', 'media_type');
$can_create_node = $this->utils->canCreateIslandoraEntity('node', 'node_type');
if ($can_create_media && $can_create_node) {
return AccessResult::allowed();
}
return AccessResult::forbidden();
}
}

368
src/Form/AddMediaForm.php

@ -0,0 +1,368 @@
<?php
namespace Drupal\islandora\Form;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Database\Connection;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\Token;
use Drupal\islandora\IslandoraUtils;
use Drupal\islandora\MediaSource\MediaSourceService;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Form that lets users upload one or more files as children to a resource node.
*/
class AddMediaForm extends FormBase {
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* Media source service.
*
* @var \Drupal\islandora\MediaSource\MediaSourceService
*/
protected $mediaSource;
/**
* Islandora utils.
*
* @var \Drupal\islandora\IslandoraUtils
*/
protected $utils;
/**
* Islandora settings.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $config;
/**
* Token service.
*
* @var \Drupal\Core\Utility\Token
*/
protected $token;
/**
* Current user account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* Parent ID, cached to survive between batch operations.
*
* @var int
*/
protected $parentId;
/**
* The route match object.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* To list the available bundle types.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfo
*/
protected $entityTypeBundleInfo;
/**
* Constructs a new IslandoraUploadForm object.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
EntityFieldManagerInterface $entity_field_manager,
IslandoraUtils $utils,
MediaSourceService $media_source,
ImmutableConfig $config,
Token $token,
AccountInterface $account,
RouteMatchInterface $route_match,
Connection $database,
EntityTypeBundleInfoInterface $entity_type_bundle_info
) {
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->utils = $utils;
$this->mediaSource = $media_source;
$this->config = $config;
$this->token = $token;
$this->account = $account;
$this->routeMatch = $route_match;
$this->database = $database;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('entity_field.manager'),
$container->get('islandora.utils'),
$container->get('islandora.media_source_service'),
$container->get('config.factory')->get('islandora.settings'),
$container->get('token'),
$container->get('current_user'),
$container->get('current_route_match'),
$container->get('database'),
$container->get('entity_type.bundle.info')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'add_media_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$upload_pattern = $this->config->get(IslandoraSettingsForm::UPLOAD_FORM_LOCATION);
$upload_location = $this->token->replace($upload_pattern);
$valid_extensions = $this->config->get(IslandoraSettingsForm::UPLOAD_FORM_ALLOWED_MIMETYPES);
$this->parentId = $this->routeMatch->getParameter('node');
$parent = $this->entityTypeManager->getStorage('node')->load($this->parentId);
// File upload widget.
$form['upload'] = [
'#type' => 'managed_file',
'#title' => $this->t('File'),
'#description' => $this->t("Upload one or more files to create media for @title", ['@title' => $parent->getTitle()]),
'#upload_location' => $upload_location,
'#upload_validators' => [
'file_validate_extensions' => [$valid_extensions],
],
'#required' => TRUE,
'#multiple' => TRUE,
];
$this->addMediaType($form);
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
];
return $form;
}
/**
* Helper function to add media use checkboxes to the form.
*
* @param array $form
* Form array.
*/
protected function addMediaType(array &$form) {
// Drop down to select media type.
$options = [];
foreach ($this->entityTypeBundleInfo->getBundleInfo('media') as $bundle_id => $bundle) {
$options[$bundle_id] = $bundle['label'];
};
$form['media_type'] = [
'#type' => 'select',
'#title' => $this->t('Media type'),
'#description' => $this->t('Each media created will have this type.'),
'#options' => $options,
'#required' => TRUE,
];
// Find bundles that don't have field_media_use.
$bundles_with_media_use = [];
foreach (array_keys($options) as $bundle) {
$fields = $this->entityFieldManager->getFieldDefinitions('media', $bundle);
if (isset($fields[IslandoraUtils::MEDIA_USAGE_FIELD])) {
$bundles_with_media_use[] = $bundle;
}
}
// Media use drop down.
// Only shows up if the selected bundle has field_media_use.
$options = [];
$terms = $this->entityTypeManager->getStorage('taxonomy_term')->loadTree('islandora_media_use', 0, NULL, TRUE);
foreach ($terms as $term) {
$options[$term->id()] = $term->getName();
};
$form['use'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Usage'),
'#description' => $this->t("Defined by Portland Common Data Model: Use Extension https://pcdm.org/2015/05/12/use. ''Original File'' will trigger creation of derivatives."),
'#options' => $options,
'#states' => [
'visible' => [],
'required' => [],
],
];
if (!empty($bundles_with_media_use)) {
foreach ($bundles_with_media_use as $bundle) {
$form['use']['#states']['visible'][] = [':input[name="media_type"]' => ['value' => $bundle]];
$form['use']['#states']['required'][] = [':input[name="media_type"]' => ['value' => $bundle]];
}
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Get the parent.
$parent_id = $this->routeMatch->getParameter('node');
$parent = $this->entityTypeManager->getStorage('node')->load($parent_id);
// Hack values out of the form.
$fids = $form_state->getValue('upload');
$media_type = $form_state->getValue('media_type');
$tids = $form_state->getValue('use');
// Create an operation for each uploaded file.
$operations = [];
foreach ($fids as $fid) {
$operations[] = [
[$this, 'buildMediaForFile'],
[$fid, $parent_id, $media_type, $tids],
];
}
// Set up and trigger the batch.
$batch = [
'title' => $this->t("Creating Media for @title", ['@title' => $parent->getTitle()]),
'operations' => $operations,
'progress_message' => t('Processed @current out of @total. Estimated time: @estimate.'),
'error_message' => t('The process has encountered an error.'),
'finished' => [$this, 'buildMediaFinished'],
];
batch_set($batch);
}
/**
* Wires up a file/media combo for a file upload.
*
* @param int $fid
* Uploaded file id.
* @param int $parent_id
* Id of the parent node.
* @param string $media_type
* Meida type for the new media.
* @param int[] $tids
* Array of Media Use term ids.
* @param array $context
* Batch context.
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function buildMediaForFile($fid, $parent_id, $media_type, array $tids, array &$context) {
// Since we make 2 different entities, do this in a transaction.
$transaction = $this->database->startTransaction();
try {
// Set the file to permanent.
$file = $this->entityTypeManager->getStorage('file')->load($fid);
$file->setPermanent();
$file->save();
// Make the media and assign it to the parent resource node.
$parent = $this->entityTypeManager->getStorage('node')->load($parent_id);
$source_field = $this->mediaSource->getSourceFieldName($media_type);
$terms = $this->entityTypeManager->getStorage('taxonomy_term')->loadMultiple($tids);
$media = $this->entityTypeManager->getStorage('media')->create([
'bundle' => $media_type,
'uid' => $this->account->id(),
$source_field => $fid,
'name' => $file->getFileName(),
IslandoraUtils::MEDIA_OF_FIELD => $parent,
]);
if ($media->hasField(IslandoraUtils::MEDIA_USAGE_FIELD)) {
$media->set(IslandoraUtils::MEDIA_USAGE_FIELD, $terms);
}
$media->save();
}
catch (HttpException $e) {
$transaction->rollBack();
throw $e;
}
catch (\Exception $e) {
$transaction->rollBack();
throw new HttpException(500, $e->getMessage());
}
}
/**
* Batch finished callback.
*
* $success bool
* Success status
* $results mixed
* The 'results' from the batch context.
* $operations array
* Remaining operations.
*/
public function buildMediaFinished($success, $results, $operations) {
return new RedirectResponse(
Url::fromRoute('view.media_of.page_1', ['node' => $this->parentId])->toString()
);
}
/**
* Check if the user can create any "Islandora" media.
*
* @param \Drupal\Core\Routing\RouteMatch $route_match
* The current routing match.
*
* @return \Drupal\Core\Access\AccessResultAllowed|\Drupal\Core\Access\AccessResultForbidden
* Whether we can or can't show the "thing".
*/
public function access(RouteMatch $route_match) {
if ($this->utils->canCreateIslandoraEntity('media', 'media_type')) {
return AccessResult::allowed();
}
return AccessResult::forbidden();
}
}

38
src/Form/IslandoraSettingsForm.php

@ -23,6 +23,9 @@ class IslandoraSettingsForm extends ConfigFormBase {
const BROKER_USER = 'broker_user'; const BROKER_USER = 'broker_user';
const BROKER_PASSWORD = 'broker_password'; const BROKER_PASSWORD = 'broker_password';
const JWT_EXPIRY = 'jwt_expiry'; const JWT_EXPIRY = 'jwt_expiry';
const UPLOAD_FORM = 'upload_form';
const UPLOAD_FORM_LOCATION = 'upload_form_location';
const UPLOAD_FORM_ALLOWED_MIMETYPES = 'upload_form_allowed_mimetypes';
const GEMINI_PSEUDO = 'gemini_pseudo_bundles'; const GEMINI_PSEUDO = 'gemini_pseudo_bundles';
const FEDORA_URL = 'fedora_url'; const FEDORA_URL = 'fedora_url';
const TIME_INTERVALS = [ const TIME_INTERVALS = [
@ -58,7 +61,10 @@ class IslandoraSettingsForm extends ConfigFormBase {
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The EntityTypeBundleInfo service. * The EntityTypeBundleInfo service.
*/ */
public function __construct(ConfigFactoryInterface $config_factory, EntityTypeBundleInfoInterface $entity_type_bundle_info) { public function __construct(
ConfigFactoryInterface $config_factory,
EntityTypeBundleInfoInterface $entity_type_bundle_info
) {
$this->setConfigFactory($config_factory); $this->setConfigFactory($config_factory);
$this->entityTypeBundleInfo = $entity_type_bundle_info; $this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->brokerPassword = $this->config(self::CONFIG_NAME)->get(self::BROKER_PASSWORD); $this->brokerPassword = $this->config(self::CONFIG_NAME)->get(self::BROKER_PASSWORD);
@ -70,7 +76,7 @@ class IslandoraSettingsForm extends ConfigFormBase {
public static function create(ContainerInterface $container) { public static function create(ContainerInterface $container) {
return new static( return new static(
$container->get('config.factory'), $container->get('config.factory'),
$container->get('entity_type.bundle.info') $container->get('entity_type.bundle.info'),
); );
} }
@ -145,6 +151,32 @@ class IslandoraSettingsForm extends ConfigFormBase {
), ),
]; ];
$form[self::UPLOAD_FORM] = [
'#type' => 'fieldset',
'#title' => $this->t('Add Children / Media Form'),
];
$form[self::UPLOAD_FORM][self::UPLOAD_FORM_LOCATION] = [
'#type' => 'textfield',
'#title' => $this->t('Upload location'),
'#description' => $this->t('Tokenized URI pattern where the uploaded file should go. You may use tokens to provide a pattern (e.g. "fedora://[current-date:custom:Y]-[current-date:custom:m]")'),
'#default_value' => $config->get(self::UPLOAD_FORM_LOCATION),
'#element_validate' => ['token_element_validate'],
'#token_types' => ['system'],
];
$form[self::UPLOAD_FORM]['TOKEN_HELP'] = [
'#theme' => 'token_tree_link',
'#token_type' => ['system'],
];
$form[self::UPLOAD_FORM][self::UPLOAD_FORM_ALLOWED_MIMETYPES] = [
'#type' => 'textarea',
'#title' => $this->t('Allowed Mimetypes'),
'#description' => $this->t('Add mimetypes as a space delimited list with no periods before the extension.'),
'#default_value' => $config->get(self::UPLOAD_FORM_ALLOWED_MIMETYPES),
];
$flysystem_config = Settings::get('flysystem'); $flysystem_config = Settings::get('flysystem');
if ($flysystem_config != NULL) { if ($flysystem_config != NULL) {
$fedora_url = $flysystem_config['fedora']['config']['root']; $fedora_url = $flysystem_config['fedora']['config']['root'];
@ -296,6 +328,8 @@ class IslandoraSettingsForm extends ConfigFormBase {
$config $config
->set(self::BROKER_URL, $form_state->getValue(self::BROKER_URL)) ->set(self::BROKER_URL, $form_state->getValue(self::BROKER_URL))
->set(self::JWT_EXPIRY, $form_state->getValue(self::JWT_EXPIRY)) ->set(self::JWT_EXPIRY, $form_state->getValue(self::JWT_EXPIRY))
->set(self::UPLOAD_FORM_LOCATION, $form_state->getValue(self::UPLOAD_FORM_LOCATION))
->set(self::UPLOAD_FORM_ALLOWED_MIMETYPES, $form_state->getValue(self::UPLOAD_FORM_ALLOWED_MIMETYPES))
->set(self::GEMINI_PSEUDO, $pseudo_types) ->set(self::GEMINI_PSEUDO, $pseudo_types)
->save(); ->save();

60
src/IslandoraUtils.php

@ -32,6 +32,8 @@ class IslandoraUtils {
const MEDIA_OF_FIELD = 'field_media_of'; const MEDIA_OF_FIELD = 'field_media_of';
const MEDIA_USAGE_FIELD = 'field_media_use'; const MEDIA_USAGE_FIELD = 'field_media_use';
const MEMBER_OF_FIELD = 'field_member_of';
const MODEL_FIELD = 'field_model';
/** /**
* The entity type manager. * The entity type manager.
@ -612,4 +614,62 @@ class IslandoraUtils {
return $rest_url; return $rest_url;
} }
/**
* Determines if an entity type and bundle make an 'Islandora' type entity.
*
* @param string $entity_type
* The entity type ('node', 'media', etc...).
* @param string $bundle
* Entity bundle ('article', 'page', etc...).
*
* @return bool
* TRUE if the bundle has the correct fields to be an 'Islandora' type.
*/
public function isIslandoraType($entity_type, $bundle) {
$fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
switch ($entity_type) {
case 'media':
return isset($fields[self::MEDIA_OF_FIELD]) && isset($fields[self::MEDIA_USAGE_FIELD]);
case 'taxonomy_term':
return isset($fields[self::EXTERNAL_URI_FIELD]);
default:
return isset($fields[self::MEMBER_OF_FIELD]);
}
}
/**
* Util function for access handlers .
*
* @param string $entity_type
* Entity type such as 'node', 'media', 'taxonomy_term', etc..
* @param string $bundle_type
* Bundle type such as 'node_type', 'media_type', 'vocabulary', etc...
*
* @return bool
* If user can create _at least one_ of the 'Islandora' types requested.
*/
public function canCreateIslandoraEntity($entity_type, $bundle_type) {
$bundles = $this->entityTypeManager->getStorage($bundle_type)->loadMultiple();
$access_control_handler = $this->entityTypeManager->getAccessControlHandler($entity_type);
$allowed = [];
foreach (array_keys($bundles) as $bundle) {
// Skip bundles that aren't 'Islandora' types.
if (!$this->isIslandoraType($entity_type, $bundle)) {
continue;
}
$access = $access_control_handler->createAccess($bundle, NULL, [], TRUE);
if (!$access->isAllowed()) {
continue;
}
return TRUE;
}
return FALSE;
}
} }

60
tests/src/Functional/AddChildTest.php

@ -0,0 +1,60 @@
<?php
namespace Drupal\Tests\islandora\Functional;
/**
* Tests the ManageMembersController.
*
* @group islandora
*/
class AddChildTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->parent =
$this->collectionTerm = $this->container->get('entity_type.manager')->getStorage('taxonomy_term')->create([
'name' => 'Collection',
'vid' => $this->testVocabulary->id(),
'field_external_uri' => [['uri' => "http://purl.org/dc/dcmitype/Collection"]],
]);
$this->collectionTerm->save();
}
/**
* @covers \Drupal\islandora\Controller\ManageMembersController::addToNodePage
* @covers \Drupal\islandora\Controller\ManageMediaController::access
* @covers \Drupal\islandora\IslandoraUtils::isIslandoraType
*/
public function testAddChild() {
$account = $this->drupalCreateUser([
'bypass node access',
]);
$this->drupalLogin($account);
$parent = $this->container->get('entity_type.manager')->getStorage('node')->create([
'type' => 'test_type',
'title' => 'Parent',
]);
$parent->save();
// Visit the add member page.
$this->drupalGet("/node/{$parent->id()}/members/add");
// Assert that test_type is on the list.
$this->assertSession()->pageTextContains($this->testType->label());
$this->clickLink($this->testType->label());
$url = $this->getUrl();
// Assert that the link creates the correct prepopulate query param.
$substring = 'node/add/test_type?edit%5Bfield_member_of%5D%5Bwidget%5D%5B0%5D%5Btarget_id%5D=1';
$this->assertTrue(
strpos($url, $substring) !== FALSE,
"Malformed URL, could not find $substring in $url."
);
}
}

46
tests/src/Functional/AddMediaTest.php

@ -0,0 +1,46 @@
<?php
namespace Drupal\Tests\islandora\Functional;
/**
* Tests the ManageMembersController.
*
* @group islandora
*/
class AddMediaTest extends IslandoraFunctionalTestBase {
/**
* @covers \Drupal\islandora\Controller\ManageMediaController::addToNodePage
* @covers \Drupal\islandora\Controller\ManageMediaController::access
* @covers \Drupal\islandora\IslandoraUtils::isIslandoraType
*/
public function testAddMedia() {
$account = $this->drupalCreateUser([
'bypass node access',
'create media',
]);
$this->drupalLogin($account);
$parent = $this->container->get('entity_type.manager')->getStorage('node')->create([
'type' => 'test_type',
'title' => 'Parent',
]);
$parent->save();
// Visit the add media page.
$this->drupalGet("/node/{$parent->id()}/media/add");
// Assert that test_meida_type is on the list.
$this->assertSession()->pageTextContains($this->testMediaType->label());
$this->clickLink($this->testMediaType->label());
$url = $this->getUrl();
// Assert that the link creates the correct prepopulate query param.
$substring = 'media/add/test_media_type?edit%5Bfield_media_of%5D%5Bwidget%5D%5B0%5D%5Btarget_id%5D=1';
$this->assertTrue(
strpos($url, $substring) !== FALSE,
"Malformed URL, could not find $substring in $url."
);
}
}

2
tests/src/Functional/GenerateDerivativeTestBase.php

@ -48,7 +48,7 @@ abstract class GenerateDerivativeTestBase extends IslandoraFunctionalTestBase {
$this->node = $this->container->get('entity_type.manager')->getStorage('node')->create([ $this->node = $this->container->get('entity_type.manager')->getStorage('node')->create([
'type' => $this->testType->id(), 'type' => $this->testType->id(),
'title' => 'Test Node', 'title' => 'Test Node',
'field_tags' => [$this->imageTerm->id()], 'field_model' => [$this->imageTerm->id()],
]); ]);
$this->node->save(); $this->node->save();
} }

4
tests/src/Functional/IslandoraFunctionalTestBase.php

@ -204,13 +204,13 @@ EOD;
]); ]);
$this->testType->save(); $this->testType->save();
$this->createEntityReferenceField('node', 'test_type', 'field_member_of', 'Member Of', 'node', 'default', [], 2); $this->createEntityReferenceField('node', 'test_type', 'field_member_of', 'Member Of', 'node', 'default', [], 2);
$this->createEntityReferenceField('node', 'test_type', 'field_tags', 'Tags', 'taxonomy_term', 'default', [], 2); $this->createEntityReferenceField('node', 'test_type', 'field_model', 'Model', 'taxonomy_term', 'default', [], 2);
// Create a media type. // Create a media type.
$this->testMediaType = $this->createMediaType('file', ['id' => 'test_media_type']); $this->testMediaType = $this->createMediaType('file', ['id' => 'test_media_type']);
$this->testMediaType->save(); $this->testMediaType->save();
$this->createEntityReferenceField('media', $this->testMediaType->id(), 'field_media_of', 'Media Of', 'node', 'default', [], 2); $this->createEntityReferenceField('media', $this->testMediaType->id(), 'field_media_of', 'Media Of', 'node', 'default', [], 2);
$this->createEntityReferenceField('media', $this->testMediaType->id(), 'field_tags', 'Tags', 'taxonomy_term', 'default', [], 2); $this->createEntityReferenceField('media', $this->testMediaType->id(), 'field_media_use', 'Tags', 'taxonomy_term', 'default', [], 2);
// Copy over the rest of the config from yml files. // Copy over the rest of the config from yml files.
$source = new FileStorage(__DIR__ . '/../../fixtures/config'); $source = new FileStorage(__DIR__ . '/../../fixtures/config');

4
tests/src/Functional/LinkHeaderTest.php

@ -61,13 +61,13 @@ class LinkHeaderTest extends IslandoraFunctionalTestBase {
'type' => $this->testType->id(), 'type' => $this->testType->id(),
'title' => 'Referencer', 'title' => 'Referencer',
'field_member_of' => [$this->referenced->id()], 'field_member_of' => [$this->referenced->id()],
'field_tags' => [$this->imageTerm->id()], 'field_model' => [$this->imageTerm->id()],
]); ]);
$this->referencer->save(); $this->referencer->save();
list($this->file, $this->media) = $this->makeMediaAndFile($account); list($this->file, $this->media) = $this->makeMediaAndFile($account);
$this->media->set('field_media_of', $this->referencer); $this->media->set('field_media_of', $this->referencer);
$this->media->set('field_tags', $this->preservationMasterTerm); $this->media->set('field_media_use', $this->preservationMasterTerm);
$this->media->save(); $this->media->save();
} }

17
tests/src/Functional/NodeHasTermTest.php

@ -35,7 +35,7 @@ class NodeHasTermTest extends IslandoraFunctionalTestBase {
->create([ ->create([
'type' => 'test_type', 'type' => 'test_type',
'title' => 'Test Node', 'title' => 'Test Node',
'field_tags' => [$this->imageTerm->id()], 'field_model' => [$this->imageTerm->id()],
]); ]);
// Create and execute the condition. // Create and execute the condition.
@ -66,23 +66,12 @@ class NodeHasTermTest extends IslandoraFunctionalTestBase {
->create([ ->create([
'type' => 'test_type', 'type' => 'test_type',
'title' => 'Test Node', 'title' => 'Test Node',
'field_tags' => [$this->preservationMasterTerm->id()], 'field_model' => [$this->preservationMasterTerm->id()],
]); ]);
$condition->setContextValue('node', $node); $condition->setContextValue('node', $node);
$this->assertFalse($condition->execute(), "Condition should fail if the node has terms, but not the one we want."); $this->assertFalse($condition->execute(), "Condition should fail if the node has terms, but not the one we want.");
// Check for two tags this time.
// Node still only has one.
$condition = $condition_manager->createInstance(
'node_has_term',
[
'uri' => 'http://purl.org/coar/resource_type/c_c513,http://pcdm.org/use#PreservationMasterFile',
]
);
$condition->setContextValue('node', $node);
$this->assertFalse($condition->execute(), "Condition should fail if node does not have both terms");
// Check for two tags this time. // Check for two tags this time.
// Node still only has one. // Node still only has one.
$condition = $condition_manager->createInstance( $condition = $condition_manager->createInstance(
@ -101,7 +90,7 @@ class NodeHasTermTest extends IslandoraFunctionalTestBase {
->create([ ->create([
'type' => 'test_type', 'type' => 'test_type',
'title' => 'Test Node', 'title' => 'Test Node',
'field_tags' => [ 'field_model' => [
$this->imageTerm->id(), $this->imageTerm->id(),
$this->preservationMasterTerm->id(), $this->preservationMasterTerm->id(),
], ],

Loading…
Cancel
Save