diff --git a/modules/islandora_iiif/README.md b/modules/islandora_iiif/README.md index c1f89872c..676723e9e 100644 --- a/modules/islandora_iiif/README.md +++ b/modules/islandora_iiif/README.md @@ -6,7 +6,7 @@ ## Introduction -Provides IIIF manifests using views. +Provides [IIIF manifests](https://iiif.io) using views. ## Requirements @@ -20,7 +20,7 @@ For a full digital repository solution, see our [installation documentation](htt To download/enable just this module, use the following from the command line: ```bash -$ composer require islandora/islandora +$ composer require drupal/islandora $ drush en islandora_core_feature $ drush mim islandora_tags $ drush en islandora_iiif @@ -38,9 +38,46 @@ This module implements a Views Style plugin. It provides the following settings: 1. Tile Source: A field that was added to the views list of fields with the image to be served. This should be a File or Image type field on a Media. 2. Structured Text field: This lets you specify a file field where OCR text with positional data, e.g., hOCR can be found. +3. Structured Text Term: If your Islandora Object has a separate media with hOCR, point to it with this setting. +4. File Width and Height fields: If you want to use TIFF or JP2 files directly, set the fields where the files' width and height can be found with these settings. + +### Media Attributes from IIIF Action + +The module also provides an action that lets a site owner populate a TIFF or JP2 image's width and +height attributes into fields so the IIIF server is not bogged down trying to generate a manifest if +it doesn't have them. + +It is a complex action that must be configured. Go to +Admin -> Actions UI -> Actions, choose +"Add image dimensions retrieved from the IIIF server" from the Create Action drop-down +And on the next screen, choose the width +and height fields that the action should +populate. + +To use it, either: +- Add it as a derivative reaction to a node with an Original FIle as its child, or +- Use it as a batch action, such as on a Paged Content object's list of child pages. + +### Setting up the action as a Context Reaction + +These instructions assume a standard Islandora Starter Site, so if you have different field names or Contexts, adjust accordingly. + +1. Go to admin/config/system/actions +2. Choose "Add image dimensions retrieved from the IIIF server" from the Create Complex Action drop-down and then click Create. +3. Enter Original File for the Source Media Use term. +4. Choose media -- file -- Width and Media -- File -- Height for the corresponding configuration values and click Save. +5. Go to admin/structure/context, and click Duplicate oon the Page Derivatives row. +6. Name the new context something like "Retrieve Page Dimensionss". and edit it. +7. This is the tricky bit, delete 'Original File' from the 'Media has term with URI' field and replace it with Service File. The explanation for this is that to retrieve a file from the IIIF server, it must be part of an Islandora Media that has been fully created and saved and given a URL. This hasn't happened yet when Original File derivatives are being created, so we need to hang our action onto a derivative that is created after the original one. +8. Under Reactions, deselect the existing actions and select "Add i mage dimensions from IIIF sserver" and click Save and continue. +9. Go back to your Paged Content object and add another child with a File media, to which you should upload another TIFF or JP2 file. +10. Without going to the Original File Book Manifestt, make sure that a service file has been generated, then click Edit on the Original File media. +11. Ensure that the Width and Height fields are populated with thee correct values from the file. + + ## Documentation -Official documentation is available on the [Islandora 8 documentation site](https://islandora.github.io/documentation/). +Official documentation is available on the [Islandora 2 documentation site](https://islandora.github.io/documentation/). ## Development @@ -48,8 +85,6 @@ If you would like to contribute, please get involved by attending our weekly [Te If you would like to contribute code to the project, you need to be covered by an Islandora Foundation [Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_cla.pdf) or [Corporate Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_ccla.pdf). Please see the [Contributors](http://islandora.ca/resources/contributors) pages on Islandora.ca for more information. -We recommend using the [islandora-playbook](https://github.com/Islandora-Devops/islandora-playbook) to get started. - ## License [GPLv2](http://www.gnu.org/licenses/gpl-2.0.txt) diff --git a/modules/islandora_iiif/islandora_iiif.install b/modules/islandora_iiif/islandora_iiif.install new file mode 100644 index 000000000..dc9af68bf --- /dev/null +++ b/modules/islandora_iiif/islandora_iiif.install @@ -0,0 +1,41 @@ +listAll(); + $views_configs = array_values(array_filter($all_configs, function ($config) { + return str_starts_with($config, 'views.view.'); + })); + + foreach ($views_configs as $views_config_name) { + $needs_save = FALSE; + $view_config = $config_factory->getEditable($views_config_name); + $displays = $view_config->get('display'); + foreach ($displays as $display_name => $display) { + if ($display['display_plugin'] == 'rest_export' + && $display['display_options']['style']['type'] == 'iiif_manifest' + &&!empty($display['display_options']['style']['options']['iiif_ocr_file_field'])) { + + $display['display_options']['style']['options']['advanced']['iiif_ocr_file_field'] = $display['display_options']['style']['options']['iiif_ocr_file_field']; + unset($display['display_options']['style']['options']['iiif_ocr_file_field']); + $view_config->set('display.' . $display_name . '.display_options.style.options', $display['display_options']['style']['options']); + $needs_save = TRUE; + } + } + if ($needs_save) { + $view_config->save(); + } + } +} diff --git a/modules/islandora_iiif/islandora_iiif.services.yml b/modules/islandora_iiif/islandora_iiif.services.yml new file mode 100644 index 000000000..fd39211cd --- /dev/null +++ b/modules/islandora_iiif/islandora_iiif.services.yml @@ -0,0 +1,4 @@ +services: + islandora_iiif: + class: Drupal\islandora_iiif\IiifInfo + arguments: ['@config.factory', '@http_client', '@logger.channel.islandora', '@jwt.authentication.jwt'] diff --git a/modules/islandora_iiif/src/IiifInfo.php b/modules/islandora_iiif/src/IiifInfo.php new file mode 100644 index 000000000..f2b1138ad --- /dev/null +++ b/modules/islandora_iiif/src/IiifInfo.php @@ -0,0 +1,151 @@ +configFactory = $config_factory; + + $this->iiifConfig = $this->configFactory->get('islandora_iiif.settings'); + $this->httpClient = $http_client; + $this->logger = $channel; + $this->jwtAuth = $jwt_auth; + } + + /** + * The IIIF base URL for an image. + * + * Visiting this URL will resolve to the info.json for the image. + * + * @return string + * The absolute URL on the IIIF server. + */ + public function baseUrl($image) { + + if ($this->iiifConfig->get('use_relative_paths')) { + $file_url = ltrim($image->createFileUrl(TRUE), '/'); + } + else { + $file_url = $image->createFileUrl(FALSE); + } + + $iiif_address = $this->iiifConfig->get('iiif_server'); + $iiif_url = rtrim($iiif_address, '/') . '/' . urlencode($file_url); + + return $iiif_url; + } + + /** + * Retrieve an image's original dimensions via the IIIF server. + * + * @param \Drupal\File\FileInterface $file + * The image file. + * + * @return array|false + * The image dimensions in an array as [$width, $height] + */ + public function getImageDimensions(FileInterface $file) { + $iiif_url = $this->baseUrl($file); + try { + $info_json = $this->httpClient->request('get', $iiif_url, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->jwtAuth->generateToken(), + ], + ])->getBody(); + $resource = json_decode($info_json, TRUE); + $width = $resource['width']; + $height = $resource['height']; + if (is_numeric($width) && is_numeric($height)) { + return [intval($width), intval($height)]; + } + } + catch (ClientException | ConnectException | RequestException | ServerException $e) { + $this->logger->info("Error getting image file dimensions from IIIF server: " . $e->getMessage()); + } + return FALSE; + } + + /** + * The IIIF base URL for an image. + * + * Visiting this URL resolves to the image resized to the maximum dimensions. + * + * @param \Drupal\file\FileInterface $image + * The image entity. + * @param int $width + * The maximum width of the image to be returned. 0 for no constraint. + * @param int $height + * The maxim um height of the image to be returned. 0 for no contraint. + * + * @return string + * The IIIF URl to retrieve the full image with the given max dimensions. + */ + public function getImageWithMaxDimensions(FileInterface $image, $width = 0, $height = 0) { + $base_url = $this->baseUrl($image); + return $base_url . "/full/!$width,$height/0/default.jpg"; + + } + +} diff --git a/modules/islandora_iiif/src/Plugin/Action/MediaAttributesFromIiif.php b/modules/islandora_iiif/src/Plugin/Action/MediaAttributesFromIiif.php new file mode 100644 index 000000000..cfe41f17c --- /dev/null +++ b/modules/islandora_iiif/src/Plugin/Action/MediaAttributesFromIiif.php @@ -0,0 +1,302 @@ +configFactory = $config_factory; + $this->entityTypeManager = $entity_type_manager; + $this->httpClient = $http_client; + $this->iiifInfo = $iiif_info; + $this->utils = $islandora_utils; + $this->mediaSource = $media_source; + $this->logger = $channel; + $this->entityFieldManager = $entity_field_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager'), + $container->get('datetime.time'), + $container->get('http_client'), + $container->get('islandora_iiif'), + $container->get('islandora.utils'), + $container->get('islandora.media_source_service'), + $container->get('logger.channel.islandora'), + $container->get('entity_field.manager'), + $container->get('config.factory') + ); + } + + /** + * {@inheritdoc} + */ + public function execute($entity = NULL) { + $width = $height = FALSE; + + // Get the original File media use term. + $source_term = $this->utils->getTermForUri($this->configuration['source_term_uri']); + + $source_mids = $this->utils->getMediaReferencingNodeAndTerm($entity, $source_term); + if (!empty($source_mids)) { + + foreach ($source_mids as $source_mid) { + + /** + * @var \Drupal\Media\MediaInterface + */ + $source_media = $this->entityTypeManager->getStorage('media')->load($source_mid); + + // Get the media MIME Type. + $source_file = $this->mediaSource->getSourceFile($source_media); + $mime_type = $source_file->getMimeType(); + + if (in_array($mime_type, ['image/tiff', 'image/jp2'])) { + [$width, $height] = $this->iiifInfo->getImageDimensions($source_file); + } + + $width_field = $this->getShortFieldName($this->configuration['width_field']); + $height_field = $this->getShortFieldName($this->configuration['height_field']); + if ($source_media->hasField($width_field) && $source_media->hasField($height_field)) { + $source_media->set($width_field, $width); + $source_media->set($height_field, $height); + $source_media->save(); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + + /** +* @var \Drupal\Core\Entity\EntityInterface $object +*/ + return $object->access('update', $account, $return_as_object); + } + + /** + * {@inheritDoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + + $integer_fields = $this->getIntegerFields(); + + $form['source_term'] = [ + '#type' => 'entity_autocomplete', + '#target_type' => 'taxonomy_term', + '#title' => $this->t('Source media use term'), + '#default_value' => $this->utils->getTermForUri($this->configuration['source_term_uri']), + '#required' => TRUE, + '#description' => $this->t('Term indicating the source media'), + ]; + + $form['width_field'] = [ + '#type' => 'select', + '#title' => $this->t('Width Field'), + '#description' => $this->t("Field to populate with an image's width."), + '#default_value' => $this->configuration['width_field'], + '#options' => $integer_fields, + ]; + + $form['height_field'] = [ + '#type' => 'select', + '#title' => $this->t('Height Field'), + '#description' => $this->t("Field to populate with an image's height."), + '#default_value' => $this->configuration['height_field'], + '#options' => $integer_fields, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + $config = parent::defaultConfiguration(); + + $config['media_use_term'] = ''; + $config['width_field'] = ''; + $config['height_field'] = ''; + + return $config; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $tid = $form_state->getValue('source_term'); + $term = $this->entityTypeManager->getStorage('taxonomy_term')->load($tid); + $this->configuration['source_term_uri'] = $this->utils->getUriForTerm($term); + + $this->configuration['width_field'] = $form_state->getValue('width_field'); + $this->configuration['height_field'] = $form_state->getValue('height_field'); + + } + + /** + * Get all fields of an entity that are integer types. + * + * @return array + * The integer type fields. + */ + protected function getIntegerFields() { + // Get media types. + $media_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple(); + $all_integer_fields = []; + foreach (array_keys($media_types) as $key => $value) { + $fields = $this->entityFieldManager->getFieldDefinitions("media", $value); + + $integer_fields = array_filter( + $fields, + function ($field_value, $field_key) { + // Only keep fields of type 'integer'. + return (strpos($field_value->getType(), 'integer') > -1) + && is_a($field_value, '\Drupal\Core\Field\FieldConfigInterface'); + }, ARRAY_FILTER_USE_BOTH + ); + foreach ($integer_fields as $integer_field) { + $all_integer_fields[$integer_field->id()] = $integer_field->getTargetEntityTypeId() + . ' -- ' . $integer_field->getTargetBundle() . ' -- ' . $integer_field->getLabel(); + } + + } + return $all_integer_fields; + } + + /** + * Returns the last part of a qualified field name. + * + * @param string $field_id + * The full field id, e.g., 'media.file.field_height'. + * + * @return string + * The short field name, e.g., 'field_height'. + */ + protected function getShortFieldName(string $field_id): string { + [$entity_type, $bundle, $field_name] = explode('.', $field_id); + return $field_name; + } + +} diff --git a/modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php b/modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php index f0aca47da..526e34eb7 100644 --- a/modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php +++ b/modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php @@ -6,22 +6,21 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Field\FieldItemInterface; +use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Url; use Drupal\islandora\IslandoraUtils; +use Drupal\islandora_iiif\IiifInfo; +use Drupal\media\MediaInterface; use Drupal\taxonomy\TermInterface; use Drupal\views\Plugin\views\style\StylePluginBase; use Drupal\views\ResultRow; use GuzzleHttp\Client; -use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\Exception\ConnectException; -use GuzzleHttp\Exception\ServerException; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Serializer\SerializerInterface; /** * Provide serializer format for IIIF Manifest. @@ -68,6 +67,13 @@ class IIIFManifest extends StylePluginBase { */ protected $serializer; + /** + * The IIIF Info service. + * + * @var \Drupal\islandora_iiif\IiifInfo + */ + protected $iiifInfo; + /** * The request service. * @@ -134,7 +140,7 @@ class IIIFManifest extends StylePluginBase { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, Request $request, ImmutableConfig $iiif_config, EntityTypeManagerInterface $entity_type_manager, FileSystemInterface $file_system, Client $http_client, MessengerInterface $messenger, ModuleHandlerInterface $moduleHandler, IslandoraUtils $utils) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, Request $request, ImmutableConfig $iiif_config, EntityTypeManagerInterface $entity_type_manager, FileSystemInterface $file_system, Client $http_client, MessengerInterface $messenger, ModuleHandlerInterface $moduleHandler, IslandoraUtils $utils, IiifInfo $iiif_info) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->serializer = $serializer; @@ -146,6 +152,8 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition $this->messenger = $messenger; $this->utils = $utils; $this->moduleHandler = $moduleHandler; + $this->utils = $utils; + $this->iiifInfo = $iiif_info; } /** @@ -164,7 +172,8 @@ public static function create(ContainerInterface $container, array $configuratio $container->get('http_client'), $container->get('messenger'), $container->get('module_handler'), - $container->get('islandora.utils') + $container->get('islandora.utils'), + $container->get('islandora_iiif') ); } @@ -296,7 +305,11 @@ protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_bas $canvas_id = $iiif_base_id . '/canvas/' . $entity->id(); $annotation_id = $iiif_base_id . '/annotation/' . $entity->id(); - [$width, $height] = $this->getCanvasDimensions($iiif_url, $image, $mime_type); + [$width, $height] = $this->getCanvasDimensions($iiif_url, $entity, $image, $mime_type); + + if ($width == 0) { + continue; + } $tmp_canvas = [ // @see https://iiif.io/api/presentation/2.1/#canvas @@ -357,6 +370,8 @@ protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_bas * * @param string $iiif_url * Base URL of the canvas. + * @param \Drupal\media\MediaInterface $media + * The Media entity. * @param \Drupal\Core\Field\FieldItemInterface $image * The image field. * @param string $mime_type @@ -365,42 +380,73 @@ protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_bas * @return [string] * The width and height of the image. */ - protected function getCanvasDimensions(string $iiif_url, FieldItemInterface $image, string $mime_type) { + protected function getCanvasDimensions(string $iiif_url, MediaInterface $media, FieldItemInterface $image, string $mime_type) { if (isset($image->width) && is_numeric($image->width) && isset($image->height) && is_numeric($image->height)) { - return [intval($image->width), intval($image->height)]; + return [intval($image->width), + intval($image->height), + ]; } - try { - $info_json = $this->httpClient->get($iiif_url)->getBody(); - $resource = json_decode($info_json, TRUE); - $width = $resource['width']; - $height = $resource['height']; + if ($properties = $image->getProperties() + && isset($properties['width']) && is_numeric($properties['width']) + && isset($properties['height']) && is_numeric($properties['width'])) { + return [intval($properties['width']), + intval($properties['height']), + ]; } - catch (ClientException | ServerException | ConnectException $e) { - // If we couldn't get the info.json from IIIF - // try seeing if we can get it from Drupal. - if (empty($width) || empty($height)) { - // Get the image properties so we know the image width/height. - $properties = $image->getProperties(); - $width = isset($properties['width']) ? $properties['width'] : 0; - $height = isset($properties['height']) ? $properties['height'] : 0; - - // If this is a TIFF AND we don't know the width/height - // see if we can get the image size via PHP's core function. - if ($mime_type === 'image/tiff' && (!$width || !$height)) { - $uri = $image->entity->getFileUri(); - $path = $this->fileSystem->realpath($uri); - $image_size = getimagesize($path); - if ($image_size) { - $width = $image_size[0]; - $height = $image_size[1]; - } + + $entity = $image->entity; + + if ($entity->hasField('field_height') && !$entity->get('field_height')->isEmpty() + && $entity->get('field_height')->value > 0 + && $entity->hasField('field_width') + && !$entity->get('field_width')->isEmpty() + && $entity->get('field_width')->value > 0) { + return [$entity->get('field_width')->value, + $entity->get('field_height')->value, + ]; + } + + // If the media has width and height fields, return those values. + $width_field = !empty($this->options['advanced']['custom_width_height']['width_field']) ? $this->options['advanced']['custom_width_height']['height_field'] : 'field_width'; + $height_field = !empty($this->options['advanced']['custom_width_height']['height_field']) ? $this->options['advanced']['custom_width_height']['height_field'] : 'field_height'; + if ($media->hasField($height_field) + && !$media->get($height_field)->isEmpty() + && $media->get($height_field)->value > 0 + && $media->hasField($width_field) + && !$media->get($width_field)->isEmpty() + && $media->get($width_field)->value > 0) { + return [intval($media->get($width_field)->value), + intval($media->get($height_field)->value), + ]; + } + + if ($mime_type === 'image/tiff') { + // If this is a TIFF AND we don't know the width/height + // see if we can get the image size via PHP's core function. + $uri = $image->entity->getFileUri(); + $path = $this->fileSystem->realpath($uri); + if (!empty($path)) { + $image_size = getimagesize($path); + if ($image_size) { + return [intval($image_size[0]), + intval($image_size[1]), + ]; } } } - return [$width, $height]; + + // As a last resort, get it from the IIIF server. + // This can be very slow and will fail if there are too many pages. + $dimensions = $this->iiifInfo->getImageDimensions($image->entity); + if ($dimensions !== FALSE) { + $this->storeImageDimensions($media, $dimensions[0], $dimensions[1]); + return $dimensions; + } + + return [0, 0]; } /** @@ -410,12 +456,11 @@ protected function getCanvasDimensions(string $iiif_url, FieldItemInterface $ima * The entity at the current row. * * @return string|false - * The absolute URL of the current row's structured text, - * or FALSE if none. + * The URL where the OCR text is found. */ protected function getOcrUrl(EntityInterface $entity) { $ocr_url = FALSE; - $iiif_ocr_file_field = !empty($this->options['iiif_ocr_file_field']) ? array_filter(array_values($this->options['iiif_ocr_file_field'])) : []; + $iiif_ocr_file_field = !empty($this->options['advanced']['iiif_ocr_file_field']) ? array_filter(array_values($this->options['advanced']['iiif_ocr_file_field'])) : []; $ocrField = count($iiif_ocr_file_field) > 0 ? $this->view->field[$iiif_ocr_file_field[0]] : NULL; if ($ocrField) { $ocr_entity = $entity; @@ -526,6 +571,8 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { // File formatters. 'file_default', 'file_url_plain', ]; + $dimensions_field_options = []; + /** @var \Drupal\views\Plugin\views\field\FieldPluginBase[] $fields */ foreach ($fields as $field_name => $field) { // If this is a known Islandora file/image field @@ -539,6 +586,10 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { (!empty($field->options['type']) && in_array($field->options['type'], $file_views_field_formatters))) { $field_options[$field_name] = $field->adminLabel(); } + else { + // Put it in the list of fields that may contain the custom value. + $dimensions_field_options[$field_name] = $field->adminLabel(); + } } // If no fields to choose from, add an error message indicating such. @@ -547,6 +598,8 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { You will need to add a field to this View'), 'error'); } + $dimensions_field_options = array_merge(['' => ' - None -- '], $dimensions_field_options); + $form['iiif_tile_field'] = [ '#title' => $this->t('Tile source field(s)'), '#type' => 'checkboxes', @@ -559,10 +612,36 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#required' => count($field_options) > 0, ]; - $form['iiif_ocr_file_field'] = [ + $form['advanced'] = [ + '#type' => 'details', + '#title' => $this->t('Advanced'), + '#open' => FALSE, + ]; + + $form['advanced']['custom_width_height'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Custom width and height fields.'), + '#description' => $this->t('Use these if the media type of the image does not have built in Width and height fields, e.g., File. As a fallback, if the media has fields with the name "field_width" and "field_height" this formatter will try and get the width from that.'), + ]; + + $form['advanced']['custom_width_height']['height_field'] = [ + '#type' => 'select', + '#title' => $this->t('Custom Height field'), + '#default_value' => $this->options['advanced']['custom_width_height']['height_field'], + '#options' => $dimensions_field_options, + ]; + + $form['advanced']['custom_width_height']['width_field'] = [ + '#type' => 'select', + '#title' => $this->t('Custom width field'), + '#default_value' => $this->options['advanced']['custom_width_height']['width_field'], + '#options' => $dimensions_field_options, + ]; + + $form['advanced']['iiif_ocr_file_field'] = [ '#title' => $this->t('Structured OCR data file field'), '#type' => 'checkboxes', - '#default_value' => $this->options['iiif_ocr_file_field'], + '#default_value' => $this->options['advanced']['iiif_ocr_file_field'], '#description' => $this->t("If the hOCR is a field on the same entity as the image source field above, select it here. If it's found in a related entity via the term below, leave this blank."), '#options' => $field_options, '#required' => FALSE, @@ -581,7 +660,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#type' => 'textfield', '#title' => $this->t("Search endpoint path."), '#description' => $this->t("If there is a search endpoint to search within the book that returns IIIF annotations, put it here. Use %node substitution where needed.
E.g., paged-content-search/%node"), - '#default_value' => $this->options['search_endpoint'], + '#default_value' => !empty($this->options['search_endpoint']) ? $this->options['search_endpoint'] : '', '#required' => FALSE, ]; } @@ -613,7 +692,9 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) { $tid = $style_options['structured_text_term']; unset($style_options['structured_text_term']); $term = $this->entityTypeManager->getStorage('taxonomy_term')->load($tid); - $style_options['structured_text_term_uri'] = $this->utils->getUriForTerm($term); + if ($term) { + $style_options['structured_text_term_uri'] = $this->utils->getUriForTerm($term); + } $form_state->setValue('style_options', $style_options); parent::submitOptionsForm($form, $form_state); } @@ -633,4 +714,34 @@ protected function getStructuredTextTerm() : ?TermInterface { return $this->structuredTextTerm; } + /** + * Store the image dimensions back onto the entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to store the dimensions on to. + * @param int $width + * The image's width. + * @param int $height + * The image's height. + */ + protected function storeImageDimensions(EntityInterface $entity, $width, $height) { + $height_field = !empty($this->options['advanced']['custom_width_height']['height_field']) ? $this->view->field[$this->options['advanced']['custom_width_height']['height_field']]->definition['field_name'] : 'field_height'; + $width_field = !empty($this->options['advanced']['custom_width_height']['width_field']) ? $this->view->field[$this->options['advanced']['custom_width_height']['width_field']]->definition['field_name'] : 'field_width'; + + $needs_save = FALSE; + if ($entity->hasField($height_field) && $entity->get($height_field)->getString() !== $height) { + $entity->set($height_field, $height); + $needs_save = TRUE; + } + + if ($entity->hasField($width_field) && $entity->get($width_field)->getString() !== $width) { + $entity->set($width_field, $width); + $needs_save = TRUE; + } + + if ($needs_save) { + $entity->save(); + } + } + }