commit c1cd174af3a5c22e1a46d39f7a66be6c045f69d7 Author: ajstanley <astanley@upei.ca> Date: Wed Mar 5 19:12:40 2025 +0000 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..d0a39b6 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +## INTRODUCTION + +The Islandora Inplace Media module allows ussers ti create and attach media from files staged on the server. + +## INSTALLATION + +Install as you would normally install a contributed Drupal module. +See: https://www.drupal.org/node/895232 for further information. + +## USAGE +Navigate to islandora-inplace-media/create-media-from-file and fill in the form. + + +## MAINTAINERS + +Current maintainers for Drupal 10: + +- Robertson Library UPEI + diff --git a/css/form-styles.css b/css/form-styles.css new file mode 100644 index 0000000..f440e47 --- /dev/null +++ b/css/form-styles.css @@ -0,0 +1,13 @@ +/* Use Flexbox to align form fields side by side */ +.media-fields-wrapper { + display: flex; + gap: 20px; /* Space between elements */ + align-items: center; +} + +/* Ensure each field takes up equal space */ +.media-side-by-side { + flex: 1; + min-width: 200px; /* Prevents elements from getting too small */ +} + diff --git a/islandora_inplace_media.info.yml b/islandora_inplace_media.info.yml new file mode 100644 index 0000000..a93da23 --- /dev/null +++ b/islandora_inplace_media.info.yml @@ -0,0 +1,7 @@ +name: 'Islandora Inplace Media' +type: module +description: 'Allows Uploaded files to be attched to exisitng nodes as media' +package: Custom +core_version_requirement: ^10 || ^11 +dependencies: + - islandora:islandora diff --git a/islandora_inplace_media.libraries.yml b/islandora_inplace_media.libraries.yml new file mode 100644 index 0000000..9bfef0d --- /dev/null +++ b/islandora_inplace_media.libraries.yml @@ -0,0 +1,5 @@ +form_styles: + version: 1.x + css: + theme: + css/form-styles.css: {} \ No newline at end of file diff --git a/islandora_inplace_media.module b/islandora_inplace_media.module new file mode 100644 index 0000000..6e6c734 --- /dev/null +++ b/islandora_inplace_media.module @@ -0,0 +1,6 @@ +<?php + +/** + * @file + * Primary module hooks for Islandora Inplace Media module. + */ diff --git a/islandora_inplace_media.routing.yml b/islandora_inplace_media.routing.yml new file mode 100644 index 0000000..b41c873 --- /dev/null +++ b/islandora_inplace_media.routing.yml @@ -0,0 +1,7 @@ +islandora_inplace_media.create_media_from_file: + path: '/islandora-inplace-media/create-media-from-file' + defaults: + _title: 'Create Media From File' + _form: 'Drupal\islandora_inplace_media\Form\CreateMediaFromFileForm' + requirements: + _permission: 'access content' diff --git a/islandora_inplace_media.services.yml b/islandora_inplace_media.services.yml new file mode 100644 index 0000000..e819314 --- /dev/null +++ b/islandora_inplace_media.services.yml @@ -0,0 +1,4 @@ +services: + islandora_inplace_media.utils: + class: Drupal\islandora_inplace_media\Utils + arguments: ['@entity_type.manager', '@file_system', '@logger.factory', '@entity_field.manager'] diff --git a/src/Form/CreateMediaFromFileForm.php b/src/Form/CreateMediaFromFileForm.php new file mode 100644 index 0000000..48577e9 --- /dev/null +++ b/src/Form/CreateMediaFromFileForm.php @@ -0,0 +1,154 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\islandora_inplace_media\Form; + +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\islandora_inplace_media\Utils; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides an Islandora Inplace Media form. + */ +final class CreateMediaFromFileForm extends FormBase { + + /** + * The Inplace Media Utils service. + * + * @var \Drupal\islandora_inplace_media\Utils + */ + protected Utils $mediaUtils; + + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * {@inheritdoc} + */ + public function __construct(Utils $mediaUtils, EntityTypeManagerInterface $entityTypeManager) { + $this->mediaUtils = $mediaUtils; + $this->entityTypeManager = $entityTypeManager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): self { + return new self( + $container->get('islandora_inplace_media.utils'), + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'islandora_inplace_media_create_media_from_file'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $vid = 'islandora_media_use'; + $terms = $this->entityTypeManager->getStorage('taxonomy_term')->loadTree($vid); + $media_use_options = []; + $term_default = ''; + foreach ($terms as $term) { + $media_use_options[$term->tid] = $term->name; + if ($term->name == 'Original File') { + $term_default = $term->tid; + } + } + + $media_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple(); + $types = array_map(fn($media_type) => $media_type->label(), $media_types); + $form['source'] = [ + '#type' => 'textfield', + '#title' => $this->t('Enter full file path to input directory'), + '#required' => TRUE, + ]; + $form['directory'] = [ + '#type' => 'textfield', + '#title' => $this->t('Enter the destination directory'), + '#required' => TRUE, + ]; + $form['destination'] = [ + '#type' => 'radios', + '#options' => [ + 'public' => $this->t('Public'), + 'private' => $this->t('Private'), + ], + '#title' => $this->t('Select file system'), + '#required' => TRUE, + '#default_value' => 'public', + ]; + $form['field_wrapper'] = [ + '#type' => 'container', + '#attributes' => ['class' => ['media-fields-wrapper']], + ]; + $form['field_wrapper']['media_type'] = [ + '#title' => $this->t("Media type."), + '#type' => 'select', + '#options' => $types, + '#attributes' => ['class' => ['media-side-by-side']], + ]; + $form['field_wrapper']['media_use'] = [ + '#title' => $this->t("Media use."), + '#type' => 'select', + '#options' => $media_use_options, + '#default_value' => $term_default, + '#attributes' => ['class' => ['media-side-by-side']], + ]; + + $form['actions'] = [ + '#type' => 'actions', + 'submit' => [ + '#type' => 'submit', + '#value' => $this->t('Send'), + ], + ]; + $form['#attached']['library'][] = 'islandora_inplace_media/form_styles'; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state): void { + $source_path = $form_state->getValue('source'); + + if (!is_dir($source_path)) { + $form_state->setErrorByName('source', $this->t('The specified directory does not exist.')); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + $source_path = $form_state->getValue('source'); + $directory = trim($form_state->getValue('directory'), '/'); + $file_system = $form_state->getValue('destination') === 'public' ? 'public://' : 'private://'; + $destination = "{$file_system}{$directory}"; + $media_type = $form_state->getValue('media_type'); + $media_use = $form_state->getValue('media_use'); + $results = $this->mediaUtils->buildMedia($source_path, $destination, $media_type, $media_use); + $this->messenger()->addStatus($this->t('Media files have been processed from %source to %destination.', [ + '%source' => $source_path, + '%destination' => $destination, + ])); + + $form_state->setRedirect('<front>'); + } + +} diff --git a/src/Utils.php b/src/Utils.php new file mode 100644 index 0000000..dc59149 --- /dev/null +++ b/src/Utils.php @@ -0,0 +1,120 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\islandora_inplace_media; + +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; +use Drupal\file\Entity\File; +use Drupal\media\Entity\Media; +use Drupal\media\Entity\MediaType; + +/** + * Utility class for managing media file operations in Islandora. + */ +final class Utils { + + /** + * Constructs a Utils object. + */ + public function __construct( + private readonly EntityTypeManagerInterface $entityTypeManager, + private readonly FileSystemInterface $fileSystem, + private readonly LoggerChannelFactoryInterface $loggerFactory, + private readonly EntityFieldManagerInterface $entityFieldManager, + ) {} + + /** + * Moves files from staging to a managed folder and creates media entities. + * + * @param string $dir + * The source directory containing files. + * @param string $dest + * The destination directory (e.g., 'public://media' or 'private://media'). + * @param string $media_type + * The media type to be created. + * @param string $media_use_tid + * The terms id for new media. + */ + public function buildMedia(string $dir, string $dest, string $media_type, string $media_use_tid): array { + $results = []; + if (!is_dir($dir)) { + $this->loggerFactory->get('islandora_inplace_media')->error('Source directory does not exist: @dir', ['@dir' => $dir]); + return $results; + } + $default_file_field = $this->getDefaultFileField($media_type); + $this->fileSystem->prepareDirectory($dest, FileSystemInterface::CREATE_DIRECTORY); + + $files = scandir($dir); + $files = array_diff($files, ['.', '..']); + + foreach ($files as $file_name) { + $source_path = $dir . '/' . $file_name; + $destination_path = $dest . '/' . $file_name; + + $moved_file = $this->fileSystem->move($source_path, $destination_path, FileSystemInterface::EXISTS_RENAME); + $new_file = File::create([ + 'uri' => $moved_file, + 'status' => 1, + ]); + $new_file->save(); + $results[$new_file->id()] = $moved_file; + if (preg_match('/n(\d+)_/', $file_name, $matches)) { + $nid = $matches[1] ?? NULL; + } + unlink($source_path); + if ($nid) { + $media = Media::create([ + "bundle" => $media_type, + "name" => $file_name, + $default_file_field => [ + "target_id" => $new_file->id(), + ], + 'field_media_use' => [ + "target_id" => $media_use_tid, + ], + "field_media_of" => $nid, + ]); + $media->save(); + } + } + return $results; + } + + /** + * Get the default file field from a media type. + * + * @param string $media_type_id + * The media type ID. + * + * @return string|null + * The field name of the default file field, or NULL if none is found. + */ + public function getDefaultFileField(string $media_type_id): ?string { + // Load the media type. + $media_type = MediaType::load($media_type_id); + if (!$media_type) { + $this->loggerFactory->get('islandora_inplace_media')->error('Media type @type not found.', ['@type' => $media_type_id]); + return NULL; + } + + // Get all field definitions for the media entity type. + $field_definitions = $this->entityFieldManager->getFieldDefinitions('media', $media_type_id); + + // Look for the first file-based field (file or image). + foreach ($field_definitions as $field_name => $field_definition) { + $storage_type = $field_definition->getType(); + if (in_array($storage_type, ['file', 'image'])) { + if ($field_name != 'thumbnail') { + return $field_name; + } + } + } + + return NULL; + } + +}