From 762f10eb474c10901edd90c8a990afd4c974f647 Mon Sep 17 00:00:00 2001 From: dannylamb Date: Wed, 13 Nov 2019 16:29:15 +0000 Subject: [PATCH] Upload form --- config/schema/islandora.schema.yml | 21 +++ islandora.links.action.yml | 10 +- islandora.routing.yml | 23 ++- src/Form/AddChildrenForm.php | 179 +++++++++++++++++++ src/Form/AddMediaForm.php | 246 +++++++++++++++++++++++++++ src/Form/IslandoraSettingsForm.php | 86 +++++++++- src/IslandoraUtils.php | 27 +++ src/Plugin/Condition/NodeHasTerm.php | 42 ++++- 8 files changed, 608 insertions(+), 26 deletions(-) create mode 100644 src/Form/AddChildrenForm.php create mode 100644 src/Form/AddMediaForm.php diff --git a/config/schema/islandora.schema.yml b/config/schema/islandora.schema.yml index 8fd719ce..b4983fa2 100644 --- a/config/schema/islandora.schema.yml +++ b/config/schema/islandora.schema.yml @@ -14,6 +14,18 @@ islandora.settings: jwt_expiry: type: string label: 'How long JWTs should last before expiring.' + upload_form_bundle: + type: string + label: 'Upload Form Content Type' + upload_form_location: + type: string + label: 'Upload Form Location' + upload_form_term: + type: string + label: 'Upload Form Media Use Term' + upload_form_allowed_extensions: + type: string + label: 'Upload Form Allowed Extensions' gemini_url: type: uri label: 'Url to Gemini microservice' @@ -78,6 +90,9 @@ condition.plugin.node_has_term: uri: type: text label: 'Taxonomy Term URI' + logic: + type: string + label: 'Logic (AND or OR)' condition.plugin.node_has_parent: type: condition.plugin @@ -95,6 +110,9 @@ condition.plugin.media_has_term: uri: type: text label: 'Taxonomy Term URI' + logic: + type: string + label: 'Logic (AND or OR)' condition.plugin.parent_node_has_term: type: condition.plugin @@ -102,6 +120,9 @@ condition.plugin.parent_node_has_term: uri: type: text label: 'Taxonomy Term URI' + logic: + type: text + label: 'Logic (AND or OR)' condition.plugin.file_uses_filesystem: type: condition.plugin diff --git a/islandora.links.action.yml b/islandora.links.action.yml index f8bf7d2b..8a1f6768 100644 --- a/islandora.links.action.yml +++ b/islandora.links.action.yml @@ -1,12 +1,12 @@ islandora.add_media_to_node: - route_name: islandora.add_media_to_node_page - title: Add media + route_name: islandora.add_media_form + title: Add Media appears_on: - view.media_of.page_1 -islandora.add_member_to_node: - route_name: islandora.add_member_to_node_page - title: Add child +islandora.add_children_to_node: + route_name: islandora.add_children_form + title: Add Children appears_on: - view.manage_members.page_1 diff --git a/islandora.routing.yml b/islandora.routing.yml index 58dea1a2..814bacab 100644 --- a/islandora.routing.yml +++ b/islandora.routing.yml @@ -16,27 +16,24 @@ system.islandora_settings: requirements: _permission: 'administer site configuration' -islandora.add_member_to_node_page: +islandora.add_children_form: path: '/node/{node}/members/add' defaults: - _controller: '\Drupal\islandora\Controller\ManageMembersController::addToNodePage' - _title_callback: '\Drupal\islandora\Controller\ManageMembersController::addTitle' - entity_type_id: node + _form: '\Drupal\islandora\Form\AddChildrenForm' + _title: 'Add children' options: - _admin_route: 'true' + _admin_route: 'TRUE' requirements: - _entity_create_any_access: 'node' + _permssion: 'create node,create media' -islandora.add_media_to_node_page: +islandora.add_media_form: path: '/node/{node}/media/add' - defaults: - _controller: '\Drupal\islandora\Controller\ManageMediaController::addToNodePage' - _title_callback: '\Drupal\islandora\Controller\ManageMediaController::addTitle' - entity_type_id: media + _form: '\Drupal\islandora\Form\AddMediaForm' + _title: 'Add media' options: - _admin_route: 'true' + _admin_route: 'TRUE' requirements: - _entity_create_any_access: 'media' + _permssion: 'create media' islandora.media_source_update: path: '/media/{media}/source' diff --git a/src/Form/AddChildrenForm.php b/src/Form/AddChildrenForm.php new file mode 100644 index 00000000..14b787b3 --- /dev/null +++ b/src/Form/AddChildrenForm.php @@ -0,0 +1,179 @@ +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); + + $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, + ]; + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Submit'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $parent_id = $this->routeMatch->getParameter('node'); + $parent = $this->entityTypeManager->getStorage('node')->load($parent_id); + + $fids = $form_state->getValue('upload'); + + $operations = []; + foreach ($fids as $fid) { + $operations[] = [[$this, 'buildNodeForFile'], [$fid, $parent_id]]; + } + + $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 array $context + * Batch context. + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + */ + public function buildNodeForFile($fid, $parent_id, array &$context) { + // Since we make 3 different entities, do this in a transaction. + $transaction = $this->database->startTransaction(); + + try { + $file = $this->entityTypeManager->getStorage('file')->load($fid); + $file->setPermanent(); + $file->save(); + + $parent = $this->entityTypeManager->getStorage('node')->load($parent_id); + + $mime = $file->getMimetype(); + $exploded_mime = explode('/', $mime); + if ($exploded_mime[0] == 'image') { + if (in_array($exploded_mime[1], ['tiff', 'jp2'])) { + $media_type = 'file'; + } + else { + $media_type = 'image'; + } + $model = $this->utils->getTermForUri('http://purl.org/coar/resource_type/c_c513'); + } + elseif ($exploded_mime[0] == 'audio') { + $media_type = 'audio'; + $model = $this->utils->getTermForUri('http://purl.org/coar/resource_type/c_18cc'); + } + elseif ($exploded_mime[0] == 'video') { + $media_type = 'video'; + $model = $this->utils->getTermForUri('http://purl.org/coar/resource_type/c_12ce'); + } + else { + $media_type = 'file'; + if ($mime == 'application/pdf') { + $model = $this->utils->getTermForUri('https://schema.org/DigitalDocument'); + } + else { + $model = $this->utils->getTermForUri('http://purl.org/coar/resource_type/c_1843'); + } + } + $source_field = $this->mediaSource->getSourceFieldName($media_type); + + $node = $this->entityTypeManager->getStorage('node')->create([ + 'type' => $this->config->get(IslandoraSettingsForm::UPLOAD_FORM_BUNDLE), + 'title' => $file->getFileName(), + IslandoraUtils::MODEL_FIELD => $model, + IslandoraUtils::MEMBER_OF_FIELD => $parent, + 'uid' => $this->account->id(), + 'status' => 1, + ]); + $node->save(); + + $uri = $this->config->get(IslandoraSettingsForm::UPLOAD_FORM_TERM); + $term = $this->utils->getTermForUri($uri); + $media = $this->entityTypeManager->getStorage('media')->create([ + 'bundle' => $media_type, + $source_field => $fid, + 'name' => $file->getFileName(), + IslandoraUtils::MEDIA_USAGE_FIELD => $term, + IslandoraUtils::MEDIA_OF_FIELD => $node, + ]); + $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() + ); + } + +} diff --git a/src/Form/AddMediaForm.php b/src/Form/AddMediaForm.php new file mode 100644 index 00000000..bb02b414 --- /dev/null +++ b/src/Form/AddMediaForm.php @@ -0,0 +1,246 @@ +entityTypeManager = $entity_type_manager; + $this->utils = $utils; + $this->mediaSource = $media_source; + $this->config = $config; + $this->token = $token; + $this->account = $account; + $this->routeMatch = $route_match; + $this->database = $database; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.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') + ); + } + + /** + * {@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); + + $form['upload'] = [ + '#type' => 'managed_file', + '#title' => $this->t('File'), + '#description' => $this->t("Upload a file for @title", ['@title' => $parent->getTitle()]), + '#upload_location' => $upload_location, + '#upload_validators' => [ + 'file_validate_extensions' => [$valid_extensions], + ], + '#required' => TRUE, + ]; + + $options = []; + $terms = $this->entityTypeManager->getStorage('taxonomy_term')->loadTree('islandora_media_use', 0, NULL, TRUE); + foreach ($terms as $term) { + $options[$this->utils->getUriForTerm($term)] = $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, + '#required' => TRUE, + ]; + + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Submit'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $parent_id = $this->routeMatch->getParameter('node'); + $parent = $this->entityTypeManager->getStorage('node')->load($parent_id); + + $fid = $form_state->getValue('upload')[0]; + $external_uris = $form_state->getValue('use'); + + // Since we make 2 different entities, do this in a transaction. + $transaction = $this->database->startTransaction(); + + try { + $file = $this->entityTypeManager->getStorage('file')->load($fid); + $file->setPermanent(); + $file->save(); + + $parent = $this->entityTypeManager->getStorage('node')->load($parent_id); + + $mime = $file->getMimetype(); + $exploded_mime = explode('/', $mime); + if ($exploded_mime[0] == 'image') { + if (in_array($exploded_mime[1], ['tiff', 'jp2'])) { + $media_type = 'file'; + } + else { + $media_type = 'image'; + } + } + elseif ($exploded_mime[0] == 'audio') { + $media_type = 'audio'; + } + elseif ($exploded_mime[0] == 'video') { + $media_type = 'video'; + } + else { + $media_type = 'file'; + } + $source_field = $this->mediaSource->getSourceFieldName($media_type); + + $terms = []; + foreach ($external_uris as $uri) { + $terms[] = $this->utils->getTermForUri($uri); + } + $media = $this->entityTypeManager->getStorage('media')->create([ + 'bundle' => $media_type, + 'uid' => $this->account->id(), + $source_field => $fid, + 'name' => $file->getFileName(), + IslandoraUtils::MEDIA_USAGE_FIELD => $terms, + IslandoraUtils::MEDIA_OF_FIELD => $parent, + ]); + $media->save(); + + $form_state->setRedirect( + 'view.media_of.page_1', + ['node' => $parent_id] + ); + } + catch (HttpException $e) { + $transaction->rollBack(); + throw $e; + } + catch (\Exception $e) { + $transaction->rollBack(); + throw new HttpException(500, $e->getMessage()); + } + } + +} diff --git a/src/Form/IslandoraSettingsForm.php b/src/Form/IslandoraSettingsForm.php index f71cb93c..141b0287 100644 --- a/src/Form/IslandoraSettingsForm.php +++ b/src/Form/IslandoraSettingsForm.php @@ -4,9 +4,11 @@ namespace Drupal\islandora\Form; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityTypeBundleInfo; +use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; +use Drupal\islandora\IslandoraUtils; use GuzzleHttp\Exception\ConnectException; use Islandora\Crayfish\Commons\Client\GeminiClient; use Stomp\Client; @@ -22,6 +24,11 @@ class IslandoraSettingsForm extends ConfigFormBase { const CONFIG_NAME = 'islandora.settings'; const BROKER_URL = 'broker_url'; const JWT_EXPIRY = 'jwt_expiry'; + const UPLOAD_FORM = 'upload_form'; + const UPLOAD_FORM_BUNDLE = 'upload_form_bundle'; + const UPLOAD_FORM_TERM = 'upload_form_term'; + const UPLOAD_FORM_LOCATION = 'upload_form_location'; + const UPLOAD_FORM_ALLOWED_MIMETYPES = 'upload_form_allowed_mimetypes'; const GEMINI_URL = 'gemini_url'; const GEMINI_PSEUDO = 'gemini_pseudo_bundles'; @@ -32,6 +39,15 @@ class IslandoraSettingsForm extends ConfigFormBase { */ private $entityTypeBundleInfo; + /** + * Islandora utils. + * + * @var \Drupal\islandora\IslandoraUtils + */ + private $utils; + + private $termStorage; + /** * Constructs a \Drupal\system\ConfigFormBase object. * @@ -39,10 +55,21 @@ class IslandoraSettingsForm extends ConfigFormBase { * The factory for configuration objects. * @param \Drupal\Core\Entity\EntityTypeBundleInfo $entity_type_bundle_info * The EntityTypeBundleInfo service. + * @param \Drupal\islandora\IslandoraUtils $utils + * Islandora utils. + * @param \Drupal\Core\Entity\EntityStorageInterface $term_storage + * Term storage. */ - public function __construct(ConfigFactoryInterface $config_factory, EntityTypeBundleInfo $entity_type_bundle_info) { + public function __construct( + ConfigFactoryInterface $config_factory, + EntityTypeBundleInfo $entity_type_bundle_info, + IslandoraUtils $utils, + EntityStorageInterface $term_storage + ) { $this->setConfigFactory($config_factory); $this->entityTypeBundleInfo = $entity_type_bundle_info; + $this->utils = $utils; + $this->termStorage = $term_storage; } /** @@ -51,7 +78,9 @@ 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'), + $container->get('islandora.utils'), + $container->get('entity_type.manager')->getStorage('taxonomy_term') ); } @@ -90,6 +119,55 @@ class IslandoraSettingsForm extends ConfigFormBase { '#description' => 'Eg: 60, "2 days", "10h", "7d". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms").', ]; + $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://[date:custom:Y]-[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), + ]; + + $options = []; + foreach ($this->entityTypeBundleInfo->getBundleInfo('node') as $bundle_id => $bundle) { + if ($this->utils->isIslandoraType('node', $bundle_id)) { + $options[$bundle_id] = $bundle['label']; + } + }; + $form[self::UPLOAD_FORM][self::UPLOAD_FORM_BUNDLE] = [ + '#type' => 'select', + '#title' => $this->t('Content type to create (Add Children Form Only)'), + '#default_value' => $config->get(self::UPLOAD_FORM_BUNDLE), + '#options' => $options, + ]; + + $options = []; + foreach ($this->termStorage->loadTree('islandora_media_use', 0, NULL, TRUE) as $term) { + $options[$this->utils->getUriForTerm($term)] = $term->getName(); + }; + $form[self::UPLOAD_FORM][self::UPLOAD_FORM_TERM] = [ + '#type' => 'select', + '#title' => $this->t('Media Use Term for Uploaded Files (Add Children Form Only)'), + '#default_value' => $config->get(self::UPLOAD_FORM_TERM), + '#options' => $options, + ]; + $form[self::GEMINI_URL] = [ '#type' => 'textfield', '#title' => $this->t('Gemini URL'), @@ -216,6 +294,10 @@ 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_BUNDLE, $form_state->getValue(self::UPLOAD_FORM_BUNDLE)) + ->set(self::UPLOAD_FORM_TERM, $form_state->getValue(self::UPLOAD_FORM_TERM)) + ->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_URL, $form_state->getValue(self::GEMINI_URL)) ->set(self::GEMINI_PSEUDO, $pseudo_types) ->save(); diff --git a/src/IslandoraUtils.php b/src/IslandoraUtils.php index e98766c7..3ebf9aa8 100644 --- a/src/IslandoraUtils.php +++ b/src/IslandoraUtils.php @@ -31,6 +31,8 @@ class IslandoraUtils { const EXTERNAL_URI_FIELD = 'field_external_uri'; 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. @@ -570,4 +572,29 @@ 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]) && isset($fields[self::MODEL_FIELD]); + } + } + } diff --git a/src/Plugin/Condition/NodeHasTerm.php b/src/Plugin/Condition/NodeHasTerm.php index dae52ad0..c912ea9b 100644 --- a/src/Plugin/Condition/NodeHasTerm.php +++ b/src/Plugin/Condition/NodeHasTerm.php @@ -79,6 +79,16 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI ); } + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array_merge( + ['logic' => 'and'], + parent::defaultConfiguration() + ); + } + /** * {@inheritdoc} */ @@ -100,6 +110,16 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI '#required' => TRUE, ]; + $form['logic'] = [ + '#type' => 'radios', + '#title' => $this->t('Logic'), + '#description' => $this->t('Whether to use AND or OR logic to evaluate multiple terms'), + '#options' => [ + 'and' => 'And', + 'or' => 'Or', + ], + '#default_value' => $this->configuration['logic'], + ]; return parent::buildConfigurationForm($form, $form_state); } @@ -124,6 +144,9 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI $this->configuration['uri'] = implode(',', $uris); } } + + $this->configuration['logic'] = $form_state->getValue('logic'); + parent::submitConfigurationForm($form, $form_state); } @@ -168,7 +191,7 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI // FALSE if there's no URIs on the node. if (empty($haystack)) { - return $this->isNegated() ? TRUE : FALSE; + return FALSE; } // Get the URIs to look for. It's a required field, so there @@ -176,12 +199,19 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI $needles = explode(',', $this->configuration['uri']); // TRUE if every needle is in the haystack. - if (count(array_intersect($needles, $haystack)) == count($needles)) { - return $this->isNegated() ? FALSE : TRUE; + if ($this->configuration['logic'] == 'and') { + if (count(array_intersect($needles, $haystack)) == count($needles)) { + return TRUE; + } + return FALSE; + } + // TRUE if any needle is in the haystack. + else { + if (count(array_intersect($needles, $haystack)) > 0) { + return TRUE; + } + return FALSE; } - - // Otherwise, FALSE. - return $this->isNegated() ? TRUE : FALSE; } /**