From 5127e7f3abf3cfc70918f4636d12385f1938bf4e Mon Sep 17 00:00:00 2001 From: dannylamb Date: Wed, 5 May 2021 14:30:54 -0300 Subject: [PATCH] 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 --- config/schema/islandora.schema.yml | 6 + islandora.links.action.yml | 16 +- islandora.routing.yml | 21 + src/Controller/ManageMediaController.php | 9 +- src/Controller/ManageMembersController.php | 26 +- src/Form/AddChildrenForm.php | 246 ++++++++++++ src/Form/AddMediaForm.php | 368 ++++++++++++++++++ src/Form/IslandoraSettingsForm.php | 38 +- src/IslandoraUtils.php | 60 +++ tests/src/Functional/AddChildTest.php | 60 +++ tests/src/Functional/AddMediaTest.php | 46 +++ .../Functional/GenerateDerivativeTestBase.php | 2 +- .../IslandoraFunctionalTestBase.php | 4 +- tests/src/Functional/LinkHeaderTest.php | 4 +- tests/src/Functional/NodeHasTermTest.php | 17 +- 15 files changed, 891 insertions(+), 32 deletions(-) create mode 100644 src/Form/AddChildrenForm.php create mode 100644 src/Form/AddMediaForm.php create mode 100644 tests/src/Functional/AddChildTest.php create mode 100644 tests/src/Functional/AddMediaTest.php diff --git a/config/schema/islandora.schema.yml b/config/schema/islandora.schema.yml index f63e4341..de7a3e46 100644 --- a/config/schema/islandora.schema.yml +++ b/config/schema/islandora.schema.yml @@ -14,6 +14,12 @@ islandora.settings: jwt_expiry: type: string 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: type: uri label: 'Url to Gemini microservice' diff --git a/islandora.links.action.yml b/islandora.links.action.yml index f8bf7d2b..385955b7 100644 --- a/islandora.links.action.yml +++ b/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: route_name: islandora.add_media_to_node_page - title: Add media + title: Add Media appears_on: - 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: route_name: islandora.add_member_to_node_page - title: Add child + title: Add Child appears_on: - view.manage_members.page_1 diff --git a/islandora.routing.yml b/islandora.routing.yml index b91c1051..6fea02e9 100644 --- a/islandora.routing.yml +++ b/islandora.routing.yml @@ -36,6 +36,17 @@ islandora.add_member_to_node_page: requirements: _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: path: '/node/{node}/media/add' defaults: @@ -47,6 +58,16 @@ islandora.add_media_to_node_page: requirements: _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: path: '/media/{media}/source' defaults: diff --git a/src/Controller/ManageMediaController.php b/src/Controller/ManageMediaController.php index 776a6808..56e0b5c5 100644 --- a/src/Controller/ManageMediaController.php +++ b/src/Controller/ManageMediaController.php @@ -2,6 +2,7 @@ namespace Drupal\islandora\Controller; +use Drupal\islandora\IslandoraUtils; use Drupal\Core\Access\AccessResult; use Drupal\Core\Routing\RouteMatch; use Drupal\node\Entity\Node; @@ -22,13 +23,15 @@ class ManageMediaController extends ManageMembersController { * Array of media types to add. */ public function addToNodePage(NodeInterface $node) { + $field = IslandoraUtils::MEDIA_OF_FIELD; + return $this->generateTypeList( 'media', 'media_type', 'entity.media.add_form', 'entity.media_type.add_form', - $node, - 'field_media_of' + $field, + ['query' => ["edit[$field][widget][0][target_id]" => $node->id()]] ); } @@ -47,7 +50,7 @@ class ManageMediaController extends ManageMembersController { if (!$node instanceof NodeInterface) { $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(); } } diff --git a/src/Controller/ManageMembersController.php b/src/Controller/ManageMembersController.php index 9197811b..7f480fb3 100644 --- a/src/Controller/ManageMembersController.php +++ b/src/Controller/ManageMembersController.php @@ -7,6 +7,7 @@ use Drupal\Core\Render\RendererInterface; use Drupal\Core\Entity\Controller\EntityController; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Link; +use Drupal\islandora\IslandoraUtils; use Drupal\node\NodeInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -36,6 +37,13 @@ class ManageMembersController extends EntityController { */ protected $renderer; + /** + * Islandora Utils. + * + * @var \Drupal\islandora\IslandoraUtils + */ + protected $utils; + /** * Constructor. * @@ -45,15 +53,19 @@ class ManageMembersController extends EntityController { * The entity field manager. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. + * @param \Drupal\islandora\IslandoraUtils $utils + * Islandora utils. */ public function __construct( EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, - RendererInterface $renderer + RendererInterface $renderer, + IslandoraUtils $utils ) { $this->entityTypeManager = $entity_type_manager; $this->entityFieldManager = $entity_field_manager; $this->renderer = $renderer; + $this->utils = $utils; } /** @@ -63,7 +75,8 @@ class ManageMembersController extends EntityController { return new static( $container->get('entity_type.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. */ public function addToNodePage(NodeInterface $node) { + $field = IslandoraUtils::MEMBER_OF_FIELD; return $this->generateTypeList( 'node', 'node_type', 'node.add', 'node.type_add', - $node, - 'field_member_of' + $field, + ['query' => ["edit[$field][widget][0][target_id]" => $node->id()]] ); } /** * 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); $build = [ @@ -115,7 +129,7 @@ class ManageMembersController extends EntityController { $bundle->label(), $entity_add_form, [$bundle_type => $bundle->id()], - ['query' => ["edit[$field][widget][0][target_id]" => $node->id()]] + $url_options ), ]; } diff --git a/src/Form/AddChildrenForm.php b/src/Form/AddChildrenForm.php new file mode 100644 index 00000000..0ff72496 --- /dev/null +++ b/src/Form/AddChildrenForm.php @@ -0,0 +1,246 @@ +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(); + } + +} diff --git a/src/Form/AddMediaForm.php b/src/Form/AddMediaForm.php new file mode 100644 index 00000000..281abd9a --- /dev/null +++ b/src/Form/AddMediaForm.php @@ -0,0 +1,368 @@ +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(); + } + +} diff --git a/src/Form/IslandoraSettingsForm.php b/src/Form/IslandoraSettingsForm.php index 2e8513f0..8d0ae36f 100644 --- a/src/Form/IslandoraSettingsForm.php +++ b/src/Form/IslandoraSettingsForm.php @@ -23,6 +23,9 @@ class IslandoraSettingsForm extends ConfigFormBase { const BROKER_USER = 'broker_user'; const BROKER_PASSWORD = 'broker_password'; 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 FEDORA_URL = 'fedora_url'; const TIME_INTERVALS = [ @@ -58,7 +61,10 @@ class IslandoraSettingsForm extends ConfigFormBase { * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info * 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->entityTypeBundleInfo = $entity_type_bundle_info; $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) { return new static( $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'); if ($flysystem_config != NULL) { $fedora_url = $flysystem_config['fedora']['config']['root']; @@ -296,6 +328,8 @@ class IslandoraSettingsForm extends ConfigFormBase { $config ->set(self::BROKER_URL, $form_state->getValue(self::BROKER_URL)) ->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) ->save(); diff --git a/src/IslandoraUtils.php b/src/IslandoraUtils.php index 46108d45..a4dda6c1 100644 --- a/src/IslandoraUtils.php +++ b/src/IslandoraUtils.php @@ -32,6 +32,8 @@ class IslandoraUtils { const MEDIA_OF_FIELD = 'field_media_of'; const MEDIA_USAGE_FIELD = 'field_media_use'; + const MEMBER_OF_FIELD = 'field_member_of'; + const MODEL_FIELD = 'field_model'; /** * The entity type manager. @@ -612,4 +614,62 @@ class IslandoraUtils { 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; + } + } diff --git a/tests/src/Functional/AddChildTest.php b/tests/src/Functional/AddChildTest.php new file mode 100644 index 00000000..9fc2f9e2 --- /dev/null +++ b/tests/src/Functional/AddChildTest.php @@ -0,0 +1,60 @@ +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." + ); + } + +} diff --git a/tests/src/Functional/AddMediaTest.php b/tests/src/Functional/AddMediaTest.php new file mode 100644 index 00000000..c3f8e05a --- /dev/null +++ b/tests/src/Functional/AddMediaTest.php @@ -0,0 +1,46 @@ +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." + ); + } + +} diff --git a/tests/src/Functional/GenerateDerivativeTestBase.php b/tests/src/Functional/GenerateDerivativeTestBase.php index c74ff255..0f67d591 100644 --- a/tests/src/Functional/GenerateDerivativeTestBase.php +++ b/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([ 'type' => $this->testType->id(), 'title' => 'Test Node', - 'field_tags' => [$this->imageTerm->id()], + 'field_model' => [$this->imageTerm->id()], ]); $this->node->save(); } diff --git a/tests/src/Functional/IslandoraFunctionalTestBase.php b/tests/src/Functional/IslandoraFunctionalTestBase.php index a948daa4..c154c5c2 100644 --- a/tests/src/Functional/IslandoraFunctionalTestBase.php +++ b/tests/src/Functional/IslandoraFunctionalTestBase.php @@ -204,13 +204,13 @@ EOD; ]); $this->testType->save(); $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. $this->testMediaType = $this->createMediaType('file', ['id' => 'test_media_type']); $this->testMediaType->save(); $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. $source = new FileStorage(__DIR__ . '/../../fixtures/config'); diff --git a/tests/src/Functional/LinkHeaderTest.php b/tests/src/Functional/LinkHeaderTest.php index 1cd1e0a8..7cb741d5 100644 --- a/tests/src/Functional/LinkHeaderTest.php +++ b/tests/src/Functional/LinkHeaderTest.php @@ -61,13 +61,13 @@ class LinkHeaderTest extends IslandoraFunctionalTestBase { 'type' => $this->testType->id(), 'title' => 'Referencer', 'field_member_of' => [$this->referenced->id()], - 'field_tags' => [$this->imageTerm->id()], + 'field_model' => [$this->imageTerm->id()], ]); $this->referencer->save(); list($this->file, $this->media) = $this->makeMediaAndFile($account); $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(); } diff --git a/tests/src/Functional/NodeHasTermTest.php b/tests/src/Functional/NodeHasTermTest.php index 1d4dd44c..eff5b5c3 100644 --- a/tests/src/Functional/NodeHasTermTest.php +++ b/tests/src/Functional/NodeHasTermTest.php @@ -35,7 +35,7 @@ class NodeHasTermTest extends IslandoraFunctionalTestBase { ->create([ 'type' => 'test_type', 'title' => 'Test Node', - 'field_tags' => [$this->imageTerm->id()], + 'field_model' => [$this->imageTerm->id()], ]); // Create and execute the condition. @@ -66,23 +66,12 @@ class NodeHasTermTest extends IslandoraFunctionalTestBase { ->create([ 'type' => 'test_type', 'title' => 'Test Node', - 'field_tags' => [$this->preservationMasterTerm->id()], + 'field_model' => [$this->preservationMasterTerm->id()], ]); $condition->setContextValue('node', $node); $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. // Node still only has one. $condition = $condition_manager->createInstance( @@ -101,7 +90,7 @@ class NodeHasTermTest extends IslandoraFunctionalTestBase { ->create([ 'type' => 'test_type', 'title' => 'Test Node', - 'field_tags' => [ + 'field_model' => [ $this->imageTerm->id(), $this->preservationMasterTerm->id(), ],