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;
+  }
+
+}