From 503dc04d7575195bf682b2190e07c69b25b31dde Mon Sep 17 00:00:00 2001 From: astanley Date: Tue, 21 Oct 2025 13:21:54 -0300 Subject: [PATCH] initial commit --- islandora_collection_harvest.info.yml | 7 + islandora_collection_harvest.permissions.yml | 3 + islandora_collection_harvest.routing.yml | 14 ++ src/Controller/HarvestDownloadController.php | 117 +++++++++++++ src/Form/CollectionHarvestForm.php | 173 +++++++++++++++++++ 5 files changed, 314 insertions(+) create mode 100644 islandora_collection_harvest.info.yml create mode 100644 islandora_collection_harvest.permissions.yml create mode 100644 islandora_collection_harvest.routing.yml create mode 100644 src/Controller/HarvestDownloadController.php create mode 100644 src/Form/CollectionHarvestForm.php diff --git a/islandora_collection_harvest.info.yml b/islandora_collection_harvest.info.yml new file mode 100644 index 0000000..ad26cb8 --- /dev/null +++ b/islandora_collection_harvest.info.yml @@ -0,0 +1,7 @@ +name: Islandora Collection Harvest +type: module +description: Provides a form to select an Islandora collection and media use, build a ZIP, and download it via streams. +core_version_requirement: ^10 +package: Islandora +dependencies: + - drupal:islandora diff --git a/islandora_collection_harvest.permissions.yml b/islandora_collection_harvest.permissions.yml new file mode 100644 index 0000000..d9b3ca1 --- /dev/null +++ b/islandora_collection_harvest.permissions.yml @@ -0,0 +1,3 @@ +access collection harvest: + title: 'Access Collection Harvest form' + description: 'Allows a user to access the collection harvest form to download media as ZIP' diff --git a/islandora_collection_harvest.routing.yml b/islandora_collection_harvest.routing.yml new file mode 100644 index 0000000..ea532dd --- /dev/null +++ b/islandora_collection_harvest.routing.yml @@ -0,0 +1,14 @@ +islandora_collection_harvest.harvest_form: + path: '/admin/islandora/collection-harvest' + defaults: + _form: '\Drupal\islandora_collection_harvest\Form\CollectionHarvestForm' + _title: 'Collection Harvest' + requirements: + _permission: 'access collection harvest' + +islandora_collection_harvest.download_zip: + path: '/islandora/collection-harvest/download/{filename}' + defaults: + _controller: '\Drupal\islandora_collection_harvest\Controller\HarvestDownloadController::download' + requirements: + _permission: 'access collection harvest' diff --git a/src/Controller/HarvestDownloadController.php b/src/Controller/HarvestDownloadController.php new file mode 100644 index 0000000..d5dc417 --- /dev/null +++ b/src/Controller/HarvestDownloadController.php @@ -0,0 +1,117 @@ +fileSystem = $file_system; + $this->tempStoreFactory = $tempStoreFactory; + $this->flysystemFactory = $flysystemFactory; + $this->streamWrapperManager = $streamWrapperManager; + } + + public static function create(ContainerInterface $container): self { + return new self( + $container->get('file_system'), + $container->get('tempstore.private'), + $container->get('flysystem_factory'), + $container->get('stream_wrapper_manager') + ); + } + + /** + * Builds and downloads archive. + * + * @param string $filename + * + * @return \Symfony\Component\HttpFoundation\StreamedResponse + */ + public function download(string $filename): StreamedResponse { + $temp_store = $this->tempStoreFactory->get('islandora_collection_harvest'); + $media_entities = $temp_store->get($filename); + if (empty($media_entities)) { + throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException('No media found.'); + } + + return new StreamedResponse(function() use ($media_entities) { + $zip = new \ZipArchive(); + $tmpfile = tempnam(sys_get_temp_dir(), 'collection_zip_'); + $zip->open($tmpfile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE); + + foreach ($media_entities as $media) { + $source_field = $media->getSource() + ->getConfiguration()['source_field'] ?? NULL; + if (!$source_field || !$media->hasField($source_field)) { + continue; + } + $file = $media->get($source_field)->entity; + if (!$file) { + continue; + } + + $uri = $file->getFileUri(); + $filename_in_zip = basename($uri); + $real_path = $this->fileSystem->realpath($uri); + + if ($real_path && file_exists($real_path)) { + $zip->addFile($real_path, $filename_in_zip); + } + else { + // Flysystem v1 fallback + try { + $scheme = $this->streamWrapperManager->getScheme($uri); + $path = substr($uri, strlen($scheme) + 3); + $filesystem = $this->flysystemFactory->getFilesystem($scheme); + + if ($filesystem && $filesystem->has($path)) { + $contents = $filesystem->read($path); + if ($contents !== FALSE) { + $tmp_fly = tempnam(sys_get_temp_dir(), 'zip_'); + file_put_contents($tmp_fly, $contents); + $zip->addFile($tmp_fly, $filename_in_zip); + } + } + } + catch (\Exception $e) { + $this->logger('islandora_collection_harvest')->error( + 'Error reading @uri from Flysystem: @msg', + ['@uri' => $uri, '@msg' => $e->getMessage()] + ); + } + } + } + + $zip->close(); + readfile($tmpfile); + unlink($tmpfile); + }, 200, [ + 'Content-Type' => 'application/zip', + 'Content-Disposition' => 'attachment; filename="' . $filename . '"', + ]); + } + +} diff --git a/src/Form/CollectionHarvestForm.php b/src/Form/CollectionHarvestForm.php new file mode 100644 index 0000000..36571f7 --- /dev/null +++ b/src/Form/CollectionHarvestForm.php @@ -0,0 +1,173 @@ +entityTypeManager = $entity_type_manager; + $this->utils = $utils; + $this->fileSystem = $file_system; + $this->flysystemFactory = $flysystem_factory; + $this->tempStoreFactory = $temp_store_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): self { + return new self( + $container->get('entity_type.manager'), + $container->get('islandora.utils'), + $container->get('file_system'), + $container->get('flysystem_factory'), + $container->get('tempstore.private') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'islandora_collection_harvest_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $term = $this->utils->getTermForUri('http://purl.org/dc/dcmitype/Collection'); + $collections = []; + if ($term) { + $query = $this->entityTypeManager->getStorage('node')->getQuery(); + $query->condition('type', 'islandora_object'); + $query->condition('field_model', $term->id(), 'IN'); + $query->accessCheck(FALSE); + $nids = $query->execute(); + $nodes = $this->entityTypeManager->getStorage('node') + ->loadMultiple($nids); + foreach ($nodes as $node) { + $collections[$node->id()] = $node->label(); + } + } + + $vid = 'islandora_media_use'; + $terms = $this->entityTypeManager->getStorage('taxonomy_term') + ->loadTree($vid); + $media_use_options = []; + foreach ($terms as $term) { + $media_use_options[$term->tid] = $term->name; + } + + $form['#attributes']['id'] = 'collection-harvest-form'; + $form['collection'] = [ + '#type' => 'select', + '#title' => $this->t('Collection'), + '#options' => $collections, + '#required' => TRUE, + ]; + + $form['media_use'] = [ + '#type' => 'select', + '#title' => $this->t('Media Use'), + '#options' => $media_use_options, + '#required' => TRUE, + ]; + + $form['actions'] = [ + '#type' => 'actions', + 'submit' => [ + '#type' => 'submit', + '#value' => $this->t('Build Download'), + '#button_type' => 'primary', + ], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + $collection_id = $form_state->getValue('collection'); + $media_use_tid = $form_state->getValue('media_use'); + + $term = $this->entityTypeManager->getStorage('taxonomy_term') + ->load($media_use_tid); + if (!$term) { + $this->messenger()->addError($this->t('Media use term not found.')); + return; + } + + // Load nodes in collection + $query = $this->entityTypeManager->getStorage('node')->getQuery(); + $query->condition('type', 'islandora_object'); + $query->condition('field_member_of', $collection_id); + $query->accessCheck(FALSE); + $nids = $query->execute(); + + $media_entities = []; + foreach ($nids as $nid) { + $node = $this->entityTypeManager->getStorage('node')->load($nid); + $media = $this->utils->getMediaWithTerm($node, $term); + if ($media) { + $source_field = $media->getSource() + ->getConfiguration()['source_field'] ?? NULL; + if ($source_field && $media->hasField($source_field) && !$media->get($source_field) + ->isEmpty()) { + $media_entities[] = $media; + } + } + } + + if (empty($media_entities)) { + $this->messenger() + ->addError($this->t('No media found for this collection.')); + return; + } + + // Store in tempstore + $temp_store = $this->tempStoreFactory->get('islandora_collection_harvest'); + $zip_filename = 'collection_' . time() . '.zip'; + $temp_store->set($zip_filename, $media_entities); + $this->messenger() + ->addStatus($this->t('Your ZIP download has been initiated. Please wait for the download to start.')); + + // Redirect to download route + $form_state->setRedirect('islandora_collection_harvest.download_zip', ['filename' => $zip_filename]); + } + +}