diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index df4255eea..a9417abed 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -40,4 +40,4 @@ Any additional information that you think would be helpful when reviewing this
PR.
# Interested parties
-Tag (@ mention) interested parties or, if unsure, @Islandora/8-x-committers
+Tag (@ mention) interested parties or, if unsure, @Islandora/committers
diff --git a/.github/workflows/build-2.x.yml b/.github/workflows/build-2.x.yml
index 306278ba6..8e4e19bed 100644
--- a/.github/workflows/build-2.x.yml
+++ b/.github/workflows/build-2.x.yml
@@ -19,17 +19,23 @@ jobs:
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
+ continue-on-error: ${{ matrix.allowed_failure }}
strategy:
+ fail-fast: false
matrix:
- php-versions: ["7.3", "7.4"]
+ php-versions: ["8.1", "8.2"]
+ # test-suite functional-javascript will appear to pass but will skip tests; missing chromedriver.
test-suite: ["kernel", "functional", "functional-javascript"]
- drupal-version: ["8.9.11", "9.1.5"]
+ drupal-version: ["10.0.x", "10.1.x", "10.2.x-dev"]
+ mysql: ["8.0"]
+ allowed_failure: [false]
- name: PHP ${{ matrix.php-versions }} drupal ${{ matrix.drupal-version }} test-suite ${{ matrix.test-suite }}
+
+ name: PHP ${{ matrix.php-versions }} | drupal ${{ matrix.drupal-version }} | mysql ${{ matrix.mysql }} | test-suite ${{ matrix.test-suite }}
services:
mysql:
- image: mysql:5.7
+ image: mysql:${{ matrix.mysql }}
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: drupal
@@ -44,14 +50,15 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
+
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
path: build_dir
- name: Checkout islandora_ci
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
repository: islandora/islandora_ci
ref: github-actions
@@ -66,6 +73,7 @@ jobs:
- name: Setup Mysql client
run: |
sudo apt-get update
+ sudo apt-get remove -y mysql-client mysql-common
sudo apt-get install -y mysql-client
- name: Set environment variables
@@ -76,7 +84,7 @@ jobs:
echo "PHPUNIT_FILE=$GITHUB_WORKSPACE/build_dir/phpunit.xml" >> $GITHUB_ENV
- name: Cache Composer dependencies
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
path: /tmp/composer-cache
key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -113,4 +121,3 @@ jobs:
run: |
cd $DRUPAL_DIR/web/core
$DRUPAL_DIR/vendor/bin/phpunit --verbose --testsuite "${{ matrix.test-suite }}"
-
diff --git a/.github/workflows/gitlab-mirror.yml b/.github/workflows/gitlab-mirror.yml
new file mode 100644
index 000000000..f59207e8a
--- /dev/null
+++ b/.github/workflows/gitlab-mirror.yml
@@ -0,0 +1,26 @@
+name: Mirror and run GitLab CI
+
+on:
+ push:
+ branches: [2.x]
+ tags: '*'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Mirror + trigger CI
+ uses: SvanBoxel/gitlab-mirror-and-ci-action@master
+ with:
+ args: "https://git.drupalcode.org/project/islandora"
+ env:
+ FOLLOW_TAGS: "true"
+ FORCE_PUSH: "false"
+ GITLAB_HOSTNAME: "git.drupal.org"
+ GITLAB_USERNAME: "project_34868_bot"
+ GITLAB_PASSWORD: ${{ secrets.GITLAB_PASSWORD }}
+ GITLAB_PROJECT_ID: "34868"
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index c085764db..546d21923 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# ![Islandora](https://cloud.githubusercontent.com/assets/2371345/25624809/f95b0972-2f30-11e7-8992-a8f135402cdc.png) Islandora
+# Islandora
-[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg?style=flat-square)](https://php.net/)
+[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.4-8892BF.svg?style=flat-square)](https://php.net/)
[![Build Status](https://github.com/islandora/islandora/actions/workflows/build-2.x.yml/badge.svg)](https://github.com/Islandora/islandora/actions)
[![Contribution Guidelines](http://img.shields.io/badge/CONTRIBUTING-Guidelines-blue.svg)](./CONTRIBUTING.md)
[![LICENSE](https://img.shields.io/badge/license-GPLv2-blue.svg?style=flat-square)](./LICENSE)
@@ -40,7 +40,6 @@ Installing via composer will download all required libraries and modules. Howev
- [eva](http://drupal.org/project/eva)
- [features](http://drupal.org/project/features)
- [migrate_plus](http://drupal.org/project/migrate_plus)
-- [migrate_tools](http://drupal.org/project/migrate_tools)
- [migrate_source_csv](http://drupal.org/project/migrate_source_csv)
- [flysystem](http://drupal.org/project/flysystem)
@@ -49,6 +48,8 @@ It also requires the following PHP libraries:
- [Crayfish Commons](https://packagist.org/packages/islandora/crayfish-commons)
- [Stomp PHP](http://drupal.org/project/)
+If you are using a Drush version less than 10.4 you will also need to install and enable [migrate_tools](http://drupal.org/project/migrate_tools) separately.
+
## Installation
For a full digital repository solution, see our [installation documentation](https://islandora.github.io/documentation/installation/component_overview/).
@@ -90,27 +91,27 @@ Having problems or solved a problem? Check out the Islandora google groups for a
Current maintainers:
-* [Danny Lamb](https://github.com/dannylamb)
+* [Islandora Technical Advisory Group](https://github.com/Islandora/islandora-community/wiki/Technical-Advisory-Group-%28TAG%29)
## Sponsors
-* UPEI
-* discoverygarden inc.
-* LYRASIS
-* McMaster University
-* University of Limerick
-* York University
-* University of Manitoba
-* Simon Fraser University
-* PALS
-* American Philosophical Society
-* Common Media Inc.
+* [American Philosophical Society](https://www.amphilsoc.org/)
+* [Born-Digital, Inc.](https://www.born-digital.com/)
+* [discoverygarden inc.](https://www.discoverygarden.ca/)
+* [LYRASIS](https://www.lyrasis.org/)
+* [McMaster University](https://www.mcmaster.ca/)
+* [PALS](https://www.mnpals.org/)
+* [University of Limerick](https://www.ul.ie/)
+* [University of Manitoba](https://umanitoba.ca/)
+* [UPEI](https://www.upei.ca/)
+* [Simon Fraser University](https://www.sfu.ca/)
+* [York University](https://www.yorku.ca/)
## Development
-If you would like to contribute, please get involved by attending our weekly [Tech Call](https://github.com/Islandora/documentation/wiki). We love to hear from you!
+If you would like to contribute, please get involved by attending our weekly [Tech Call](https://github.com/Islandora/islandora-community/wiki/Weekly-Open-Tech-Call). We love to hear from you!
-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.
+If you would like to contribute code to the project, you need to be covered by an Islandora Foundation [Contributor License Agreement](https://github.com/Islandora/islandora-community/wiki/Onboarding-Checklist#contributor-license-agreements) or [Corporate Contributor License Agreement](https://github.com/Islandora/islandora-community/wiki/Onboarding-Checklist#contributor-license-agreements). Please see the [Contributor License Agreements](https://github.com/Islandora/islandora-community/wiki/Contributor-License-Agreements) page on the islandora-community wiki for more information.
We recommend using the [islandora-playbook](https://github.com/Islandora-Devops/islandora-playbook) to get started.
diff --git a/composer.json b/composer.json
index f787f94a7..34107d37b 100644
--- a/composer.json
+++ b/composer.json
@@ -14,34 +14,35 @@
}
],
"require": {
- "drupal/context": "^4.0@beta",
- "drupal/search_api": "~1.8",
- "islandora/jsonld": "^2",
- "stomp-php/stomp-php": "4.*",
- "drupal/jwt": "^1.0.0-beta5",
- "drupal/filehash": "^1.1",
- "drupal/prepopulate" : "^2.2",
- "drupal/eva" : "^2.0",
- "drupal/features" : "^3.7",
- "drupal/migrate_plus" : "^5.1",
- "drupal/migrate_tools" : "^5.0",
+ "drupal/context": "^4 || ^5@RC",
+ "drupal/ctools": "^3.8 || ^4",
+ "drupal/eva" : "^3.0",
+ "drupal/features" : "^3.13",
+ "drupal/file_replace": "^1.1",
+ "drupal/filehash": "^2",
+ "drupal/flysystem" : "^2.0@alpha",
+ "drupal/jwt": "^1.1 || ^2",
+ "drupal/migrate_plus" : "^5.1 || ^6",
"drupal/migrate_source_csv" : "^3.4",
+ "drupal/prepopulate" : "^2.2",
+ "drupal/search_api": "^1.8",
"drupal/token" : "^1.3",
- "drupal/flysystem" : "^2.0@alpha",
- "islandora/crayfish-commons": "^2",
- "drupal/file_replace": "^1.1"
-
+ "islandora/chullo": "^2.0",
+ "islandora/fedora-entity-mapper": "^1.0",
+ "islandora/jsonld": "^2 || ^3",
+ "stomp-php/stomp-php": "4.* || ^5"
},
"require-dev": {
"phpunit/phpunit": "^6",
- "squizlabs/php_codesniffer": "2.7.1",
+ "squizlabs/php_codesniffer": "^2.7.1",
"drupal/coder": "*",
"sebastian/phpcpd": "*"
},
"suggest": {
- "drupal/transliterate_filenames": "Sanitizes filenames when they are uploaded so they don't break your repository."
+ "drupal/transliterate_filenames": "Sanitizes filenames when they are uploaded so they don't break your repository.",
+ "drupal/coi": "Some configuration fields work with Config Override Inspector."
},
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"authors": [
{
"name": "Islandora Foundation",
diff --git a/config/install/islandora.settings.yml b/config/install/islandora.settings.yml
index 8fb25fb8c..1497c291e 100644
--- a/config/install/islandora.settings.yml
+++ b/config/install/islandora.settings.yml
@@ -1,4 +1,4 @@
broker_url: 'tcp://localhost:61613'
jwt_expiry: '+2 hour'
-gemini_url: ''
+delete_media_and_files: TRUE
gemini_pseudo_bundles: []
diff --git a/config/schema/islandora.schema.yml b/config/schema/islandora.schema.yml
index de7a3e46e..49de998b3 100644
--- a/config/schema/islandora.schema.yml
+++ b/config/schema/islandora.schema.yml
@@ -14,15 +14,18 @@ islandora.settings:
jwt_expiry:
type: string
label: 'How long JWTs should last before expiring.'
+ delete_media_and_files:
+ type: boolean
+ label: 'Node Delete with Media and Files'
+ redirect_after_media_save:
+ type: boolean
+ label: 'Redirect to node after media save.'
upload_form_location:
type: string
label: 'Upload Form Location'
upload_form_allowed_mimetypes:
type: string
label: 'Upload Form Allowed Extensions'
- gemini_url:
- type: uri
- label: 'Url to Gemini microservice'
gemini_pseudo_bundles:
type: sequence
label: 'List of node, media and taxonomy terms that should include the linked Fedora URI'
@@ -87,6 +90,14 @@ condition.plugin.node_has_term:
logic:
type: string
label: 'Logic (AND or OR)'
+ tids:
+ type: sequence
+ sequence:
+ type: mapping
+ mapping:
+ target_id:
+ type: integer
+ label: The target taxonomy term IDs
condition.plugin.node_has_parent:
type: condition.plugin
@@ -160,12 +171,74 @@ condition.plugin.node_had_namespace:
label: 'PID field'
field.formatter.settings.islandora_image:
- type: mapping
- label: 'Image field display format settings'
+ type: field.formatter.settings.image
+ label: 'Islandora image field display format settings'
+
+condition.plugin.islandora_entity_bundle:
+ type: condition.plugin
+ mapping:
+ bundles:
+ type: sequence
+ sequence:
+ type: string
+
+condition.plugin.media_source_mimetype:
+ type: condition.plugin
+ mapping:
+ mimetype:
+ type: string
+
+reaction.plugin.alter_jsonld_type:
+ type: islandora.reaction_plugin_with_saved
+ mapping:
+ source_field:
+ type: string
+
+islandora.reaction_plugin_with_saved:
+ type: reaction.plugin
mapping:
- image_link:
+ saved:
+ type: boolean
+ label: Default config upstream; however, left undefined in the schema.
+
+reaction.plugin.islandora_map_uri_predicate:
+ type: islandora.reaction_plugin_with_saved
+ mapping:
+ drupal_uri_predicate:
type: string
- label: 'Link image to'
- image_style:
+
+reaction.plugin.view_mode_alter:
+ type: islandora.reaction_plugin_with_saved
+ mapping:
+ mode:
type: string
- label: 'Image style'
+ label: The view mode to which to switch
+
+islandora.reaction.actions:
+ type: islandora.reaction_plugin_with_saved
+ mapping:
+ actions:
+ type: sequence
+ sequence:
+ type: string
+
+reaction.plugin.index:
+ type: islandora.reaction.actions
+
+reaction.plugin.delete:
+ type: islandora.reaction.actions
+
+reaction.plugin.derivative:
+ type: islandora.reaction.actions
+
+field.widget.settings.media_track:
+ type: field.widget.settings.file_generic
+
+field.field_settings.media_track:
+ type: field.field_settings.file
+ mapping:
+ languages:
+ type: string
+
+field.storage_settings.media_track:
+ type: field.storage_settings.file
diff --git a/css/islandora.css b/css/islandora.css
new file mode 100644
index 000000000..696587ac1
--- /dev/null
+++ b/css/islandora.css
@@ -0,0 +1,3 @@
+.container .islandora-media-items {
+ margin: 0;
+}
diff --git a/islandora.info.yml b/islandora.info.yml
index c66c1ba8d..34e8118ac 100644
--- a/islandora.info.yml
+++ b/islandora.info.yml
@@ -4,32 +4,31 @@ name: 'islandora'
description: "Islandora Core"
type: module
package: Islandora
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
dependencies:
+ - context:context_ui
+ - ctools:ctools
+ - drupal:action
+ - drupal:basic_auth
- drupal:block
+ - drupal:content_translation
+ - drupal:link
+ - drupal:media
- drupal:node
- - drupal:path
- - drupal:text
- drupal:options
- - drupal:link
- - drupal:jsonld
- - drupal:search_api
- - drupal:jwt
+ - drupal:path
- drupal:rest
- - drupal:filehash
- - drupal:basic_auth
- - drupal:context_ui
- - drupal:action
- - drupal:eva
- drupal:taxonomy
+ - drupal:text
- drupal:views_ui
- - drupal:media
- - drupal:prepopulate
- - drupal:features_ui
- - drupal:migrate_tools
- - drupal:migrate_source_csv
- - drupal:content_translation
- - drupal:flysystem
- - drupal:token
- - drupal:file_replace
+ - eva:eva
+ - features:features_ui
+ - file_replace:file_replace
+ - filehash:filehash
+ - flysystem:flysystem
+ - jsonld:jsonld
+ - jwt:jwt
+ - migrate_source_csv:migrate_source_csv
+ - prepopulate:prepopulate
+ - search_api:search_api
+ - token:token
diff --git a/islandora.install b/islandora.install
index f9eb1225f..01e5a467b 100644
--- a/islandora.install
+++ b/islandora.install
@@ -5,6 +5,10 @@
* Install/update hook implementations.
*/
+use Drupal\Core\Extension\ExtensionNameLengthException;
+use Drupal\Core\Extension\MissingDependencyException;
+use Drupal\Core\Utility\UpdateException;
+
/**
* Adds common namespaces to jsonld.settings.
*/
@@ -174,3 +178,51 @@ function update_jsonld_included_namespaces() {
->warning("Could not find required jsonld.settings to add default RDF namespaces.");
}
}
+
+/**
+ * Ensure that ctools is enabled.
+ */
+function islandora_update_8007() {
+ $module_handler = \Drupal::moduleHandler();
+ if ($module_handler->moduleExists('ctools')) {
+ return t('The "@module_name" module is already enabled, no action necessary.', [
+ '@module_name' => 'ctools',
+ ]);
+ }
+
+ /** @var \Drupal\Core\Extension\ModuleInstallerInterface $installer */
+ $installer = \Drupal::service('module_installer');
+
+ try {
+ if ($installer->install(['ctools'], TRUE)) {
+ return t('The "@module_name" module was enabled successfully.', [
+ '@module_name' => 'ctools',
+ ]);
+ }
+ }
+ catch (ExtensionNameLengthException | MissingDependencyException $e) {
+ throw new UpdateException('Failed; ensure that the ctools module is available in the Drupal installation.', 0, $e);
+ }
+ catch (\Exception $e) {
+ throw new UpdateException('Failed; encountered an exception while trying to enable ctools.', 0, $e);
+ }
+
+ // Theoretically impossible to hit, as ModuleInstaller::install() only returns
+ // TRUE (or throws/propagates an exception), but... probably a good idea to
+ // have the here, just in case?
+ throw new UpdateException('Failed; hit the end of the update hook implementation, which is not expected.');
+}
+
+/**
+ * Set config to no redirect after media save.
+ */
+function islandora_update_8008() {
+ $config = \Drupal::configFactory()->getEditable('islandora.settings');
+ if ($config) {
+ $config->set('redirect_after_media_save', FALSE);
+ $config->save(TRUE);
+ return t('A new configuration option, "Redirect after media save" is now available.
+ It has been turned off to preserve existing behaviour. To enable this setting visit
+ Configuration > Islandora > Core Settings.');
+ }
+}
diff --git a/islandora.libraries.yml b/islandora.libraries.yml
new file mode 100644
index 000000000..840dc294b
--- /dev/null
+++ b/islandora.libraries.yml
@@ -0,0 +1,5 @@
+islandora:
+ version: VERSION
+ css:
+ theme:
+ css/islandora.css: {}
diff --git a/islandora.module b/islandora.module
index 738be5b43..d3dfa01d9 100644
--- a/islandora.module
+++ b/islandora.module
@@ -26,6 +26,9 @@ use Drupal\media\MediaInterface;
use Drupal\file\FileInterface;
use Drupal\taxonomy\TermInterface;
use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\serialization\Normalizer\CacheableNormalizerInterface;
+use Drupal\Core\Entity\EntityForm;
+use Drupal\file\Entity\File;
/**
* Implements hook_help().
@@ -180,7 +183,8 @@ function islandora_file_insert(FileInterface $file) {
*/
function islandora_file_update(FileInterface $file) {
// Exit early if unchanged.
- if ($file->filehash != NULL && $file->original->filehash != NULL && $file->filehash['sha1'] == $file->original->filehash['sha1']) {
+ if ($file->hasField('sha1') && $file->original->hasField('sha1')
+ && $file->sha1->getString() == $file->original->sha1->getString()) {
return;
}
@@ -251,8 +255,8 @@ function islandora_jsonld_alter_normalized_array(EntityInterface $entity, array
$reaction->execute($entity, $normalized, $context);
foreach ($context_manager->getActiveContexts() as $context_config) {
try {
- if ($context_config->getReaction($reaction->getPluginId())) {
- $context['cacheability']->addCacheTags($context_config->getCacheTags());
+ if ($context_config->getReaction($reaction->getPluginId()) && isset($context[CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY])) {
+ $context[CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY]->addCacheableDependency($context_config);
};
}
catch (PluginNotFoundException $e) {
@@ -328,19 +332,227 @@ function islandora_form_alter(&$form, FormStateInterface $form_state, $form_id)
if ($node) {
$form['name']['widget'][0]['value']['#default_value'] = $node->getTitle();
}
+ $form['actions']['submit']['#submit'][] = 'islandora_media_custom_form_submit';
}
}
+
+ $form_object = $form_state->getFormObject();
+ $utils = \Drupal::service('islandora.utils');
+ $config = \Drupal::config('islandora.settings')->get('delete_media_and_files');
+
+ if ($config == 1 && $form_object instanceof EntityForm) {
+ $entity = $form_object->getEntity();
+
+ if ($entity->getEntityTypeId() == "node" && $utils->isIslandoraType($entity->getEntityTypeId(), $entity->bundle()) && strpos($form['#form_id'], 'delete_form') !== FALSE) {
+ $medias = $utils->getMedia($form_state->getFormObject()->getEntity());
+ if (count($medias) != 0) {
+ $form['delete_associated_content'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Delete all associated medias and nodes'),
+ ];
+
+ $media_list = [];
+
+ foreach ($medias as $media) {
+ $media_list[] = $media->getName();
+ }
+
+ $form['container'] = [
+ '#type' => 'container',
+ '#states' => [
+ 'visible' => [
+ ':input[name="delete_associated_content"]' => ['checked' => TRUE],
+ ],
+ ],
+ ];
+
+ $form['container']['media_items'] = [
+ '#theme' => 'item_list',
+ '#type' => 'ul',
+ '#items' => $media_list,
+ '#attributes' => ['class' => ['islandora-media-items']],
+ '#wrapper_attributes' => ['class' => ['container']],
+ '#attached' => [
+ 'library' => [
+ 'islandora/islandora',
+ ],
+ ],
+ ];
+
+ $form['actions']['submit']['#submit'][] = 'islandora_object_delete_form_submit';
+ return $form;
+ }
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Redirect submit handler for media save.
+ */
+function islandora_media_custom_form_submit(&$form, FormStateInterface $form_state) {
+ // Check configuration to see whether a redirect is desired.
+ $redirect = \Drupal::config('islandora.settings')->get('redirect_after_media_save');
+ if ($redirect) {
+ $params = \Drupal::request()->query->all();
+ if (!empty($params)) {
+ $target_id = $params['edit']['field_media_of']['widget'][0]['target_id'];
+ $url = Url::fromRoute('view.media_of.page_1', ['node' => $target_id]);
+ $form_state->setRedirectUrl($url);
+ }
+ }
+}
+
+/**
+ * Implements a submit handler for the delete form.
+ */
+function islandora_object_delete_form_submit($form, FormStateInterface $form_state) {
+
+ $result = $form_state->getValues('delete_associated_content');
+ $utils = \Drupal::service('islandora.utils');
+
+ if ($result['delete_associated_content'] == 1) {
+
+ $node = $form_state->getFormObject()->getEntity();
+ $medias = $utils->getMedia($node);
+ $media_list = [];
+
+ $entity_field_manager = \Drupal::service('entity_field.manager');
+ $current_user = \Drupal::currentUser();
+ $logger = \Drupal::logger('logger.channel.islandora');
+ $messenger = \Drupal::messenger();
+
+ $delete_media = [];
+ $media_translations = [];
+ $media_files = [];
+ $entity_protected_medias = [];
+ $inaccessible_entities = [];
+
+ foreach ($medias as $id => $media) {
+ $lang = $media->language()->getId();
+ $selected_langcodes[$lang] = $lang;
+
+ if (!$media->access('delete', $current_user)) {
+ $inaccessible_entities[] = $media;
+ continue;
+ }
+ // Check for files.
+ $fields = $entity_field_manager->getFieldDefinitions('media', $media->bundle());
+ foreach ($fields as $field) {
+ $type = $field->getType();
+ if ($type == 'file' || $type == 'image') {
+ $target_id = $media->get($field->getName())->target_id;
+ $file = File::load($target_id);
+ if ($file) {
+ if (!$file->access('delete', $current_user)) {
+ $inaccessible_entities[] = $file;
+ continue;
+ }
+ $media_files[$id][$file->id()] = $file;
+ }
+ }
+ }
+
+ foreach ($selected_langcodes as $langcode) {
+ // We're only working with media, which are translatable.
+ $entity = $media->getTranslation($langcode);
+ if ($entity->isDefaultTranslation()) {
+ $delete_media[$id] = $entity;
+ unset($media_translations[$id]);
+ }
+ elseif (!isset($delete_media[$id])) {
+ $media_translations[$id][] = $entity;
+ }
+ }
+ }
+
+ if ($delete_media) {
+ foreach ($delete_media as $id => $media) {
+ try {
+ $media->delete();
+ $media_list[] = $id;
+ $logger->notice('The media %label has been deleted.', [
+ '%label' => $media->label(),
+ ]);
+ }
+ catch (Exception $e) {
+ $entity_protected_medias[] = $id;
+ }
+ }
+ }
+
+ $delete_files = array_filter($media_files, function ($media) use ($entity_protected_medias) {
+ return !in_array($media, $entity_protected_medias);
+ }, ARRAY_FILTER_USE_KEY);
+
+ if ($delete_files) {
+ foreach ($delete_files as $files_array) {
+ foreach ($files_array as $file) {
+ $file->delete();
+ $logger->notice('The file %label has been deleted.', [
+ '%label' => $file->label(),
+ ]);
+ }
+ }
+ }
+
+ $delete_media_translations = array_filter($media_translations, function ($media) use ($entity_protected_medias) {
+ return !in_array($media, $entity_protected_medias);
+ }, ARRAY_FILTER_USE_KEY);
+
+ if ($delete_media_translations) {
+ foreach ($delete_media_translations as $id => $translations) {
+ $media = $medias[$id];
+ foreach ($translations as $translation) {
+ $media->removeTranslation($translation->language()->getId());
+ }
+ $media->save();
+ foreach ($translations as $translation) {
+ $logger->notice('The media %label @language translation has been deleted', [
+ '%label' => $media->label(),
+ '@language' => $translation->language()->getName(),
+ ]);
+ }
+ }
+ }
+
+ if ($inaccessible_entities) {
+ $messenger->addWarning("@count items have not been deleted because you do not have the necessary permissions.", [
+ '@count' => count($inaccessible_entities),
+ ]);
+ }
+
+ $build = [
+ 'heading' => [
+ '#type' => 'html_tag',
+ '#tag' => 'div',
+ '#value' => t("The repository item @node and @media", [
+ '@node' => $node->getTitle(),
+ '@media' => \Drupal::translation()->formatPlural(
+ count($media_list), 'the media with the id @media has been deleted.',
+ 'the medias with the ids @media have been deleted.',
+ ['@media' => implode(", ", $media_list)],
+ ),
+ ]),
+ ],
+ ];
+
+ $message = \Drupal::service('renderer')->renderPlain($build);
+ $messenger->deleteByType('status');
+ $messenger->addStatus($message);
+ }
}
/**
- * Implements hook_field_widget_WIDGET_TYPE_form_alter().
+ * Implements hook_field_widget_single_element_WIDGET_TYPE_form_alter().
*/
-function islandora_field_widget_image_image_form_alter(&$element, $form_state, $context) {
+function islandora_field_widget_single_element_image_image_form_alter(&$element, $form_state, $context) {
$element['#process'][] = 'islandora_add_default_image_alt_text';
}
/**
- * Callback for hook_field_widget_WIDGET_TYPE_form_alter().
+ * Callback for hook_field_widget_single_element_WIDGET_TYPE_form_alter().
*/
function islandora_add_default_image_alt_text($element, $form_state, $form) {
if ($element['alt']['#access']) {
@@ -421,8 +633,8 @@ function islandora_entity_extra_field_info() {
if (!empty($pseudo_bundles)) {
foreach ($pseudo_bundles as $key) {
- list($bundle, $content_entity) = explode(":", $key);
- $extra_field[$content_entity][$bundle]['display']['field_gemini_uri'] = [
+ [$bundle, $content_entity] = explode(":", $key);
+ $extra_field[$content_entity][$bundle]['display'][IslandoraSettingsForm::GEMINI_PSEUDO_FIELD] = [
'label' => t('Fedora URI'),
'description' => t('The URI to the persistent'),
'weight' => 100,
@@ -450,6 +662,17 @@ function islandora_entity_view(array &$build, EntityInterface $entity, EntityVie
// Check if the source file is in Fedora or not.
$media_source_service = \Drupal::service('islandora.media_source_service');
$source_file = $media_source_service->getSourceFile($entity);
+ if (!$source_file) {
+ \Drupal::logger('islandora')->error(
+ \Drupal::service('string_translation')->translate(
+ "Can't get source file for @label (@id)", [
+ '@label' => $entity->label(),
+ "@id" => $entity->id(),
+ ]
+ )
+ );
+ return;
+ }
$uri = $source_file->getFileUri();
$scheme = \Drupal::service('stream_wrapper_manager')->getScheme($uri);
$flysystem_config = Settings::get('flysystem');
@@ -503,7 +726,7 @@ function islandora_preprocess_views_view_table(&$variables) {
// Check for a weight selector field.
foreach ($variables['view']->field as $field_key => $field) {
- if ($field->options['plugin_id'] == 'integer_weight_selector') {
+ if ($field->getPluginId() == 'integer_weight_selector') {
// Check if the weight selector is on the first column.
$is_first_column = array_search($field_key, array_keys($variables['view']->field)) > 0 ? FALSE : TRUE;
diff --git a/islandora.post_update.php b/islandora.post_update.php
new file mode 100644
index 000000000..0a29e56ed
--- /dev/null
+++ b/islandora.post_update.php
@@ -0,0 +1,16 @@
+getEditable('islandora.settings');
+ $config->set('delete_media_and_files', TRUE);
+ $config->save(TRUE);
+}
diff --git a/islandora.routing.yml b/islandora.routing.yml
index 6fea02e98..86d134828 100644
--- a/islandora.routing.yml
+++ b/islandora.routing.yml
@@ -37,15 +37,15 @@ islandora.add_member_to_node_page:
_entity_create_any_access: 'node'
islandora.upload_children:
- path: '/node/{node}/members/upload'
+ path: '/node/{node}/members/upload/{step}'
defaults:
- _form: '\Drupal\islandora\Form\AddChildrenForm'
+ _wizard: '\Drupal\islandora\Form\AddChildrenWizard\ChildForm'
_title: 'Upload Children'
+ step: 'type_selection'
options:
_admin_route: 'TRUE'
requirements:
- _access: 'TRUE'
- #_permssion: 'create node,create media'
+ _custom_access: '\Drupal\islandora\Form\AddChildrenWizard\Access::childAccess'
islandora.add_media_to_node_page:
path: '/node/{node}/media/add'
@@ -59,14 +59,15 @@ islandora.add_media_to_node_page:
_entity_create_any_access: 'media'
islandora.upload_media:
- path: '/node/{node}/media/upload'
+ path: '/node/{node}/media/upload/{step}'
defaults:
- _form: '\Drupal\islandora\Form\AddMediaForm'
+ _wizard: '\Drupal\islandora\Form\AddChildrenWizard\MediaForm'
_title: 'Add media'
+ step: 'type_selection'
options:
_admin_route: 'TRUE'
requirements:
- _custom_access: '\Drupal\islandora\Form\AddMediaForm::access'
+ _custom_access: '\Drupal\islandora\Form\AddChildrenWizard\Access::mediaAccess'
islandora.media_source_update:
path: '/media/{media}/source'
diff --git a/islandora.services.yml b/islandora.services.yml
index 4b3a9d16e..74725d803 100644
--- a/islandora.services.yml
+++ b/islandora.services.yml
@@ -31,6 +31,9 @@ services:
logger.channel.islandora:
parent: logger.channel_base
arguments: ['islandora']
+ logger.channel.fedora_flysystem:
+ parent: logger.channel_base
+ arguments: ['fedora_flysystem']
islandora.media_route_context_provider:
class: Drupal\islandora\ContextProvider\MediaRouteContextProvider
arguments: ['@current_route_match']
@@ -53,9 +56,25 @@ services:
class: Drupal\islandora\IslandoraUtils
arguments: ['@entity_type.manager', '@entity_field.manager', '@context.manager', '@flysystem_factory', '@language_manager']
islandora.entity_mapper:
- class: Islandora\Crayfish\Commons\EntityMapper\EntityMapper
+ class: Islandora\EntityMapper\EntityMapper
islandora.stomp.auth_header_listener:
class: Drupal\islandora\EventSubscriber\StompHeaderEventSubscriber
arguments: ['@jwt.authentication.jwt']
tags:
- { name: event_subscriber }
+ islandora.upload_children.batch_processor:
+ class: Drupal\islandora\Form\AddChildrenWizard\ChildBatchProcessor
+ arguments:
+ - '@entity_type.manager'
+ - '@database'
+ - '@current_user'
+ - '@messenger'
+ - '@date.formatter'
+ islandora.upload_media.batch_processor:
+ class: Drupal\islandora\Form\AddChildrenWizard\MediaBatchProcessor
+ arguments:
+ - '@entity_type.manager'
+ - '@database'
+ - '@current_user'
+ - '@messenger'
+ - '@date.formatter'
diff --git a/islandora.tokens.inc b/islandora.tokens.inc
index 8d1c4b6b2..f528dee3d 100644
--- a/islandora.tokens.inc
+++ b/islandora.tokens.inc
@@ -19,6 +19,18 @@ function islandora_token_info() {
'name' => t('Islandora Tokens'),
'description' => t('Tokens for Islandora objects.'),
];
+ $node['media-original-file:filename'] = [
+ 'name' => t('Media: Original File filename without extension.'),
+ 'description' => t('File name without extension of original uploaded file associated with Islandora Object via Media.'),
+ ];
+ $node['media-original-file:basename'] = [
+ 'name' => t('Media: Original File filename with extension.'),
+ 'description' => t('File name with extension of original uploaded file associated with Islandora Object via Media.'),
+ ];
+ $node['media-original-file:extension'] = [
+ 'name' => t('Media: Original File extension.'),
+ 'description' => t('File extension of original uploaded file associated with Islandora Object via Media.'),
+ ];
$node['media-thumbnail-image:url'] = [
'name' => t('Media: Thumbnail Image URL.'),
'description' => t('URL of Thumbnail Image associated with Islandora Object via Media.'),
@@ -70,6 +82,24 @@ function islandora_tokens($type, $tokens, array $data, array $options, Bubbleabl
$islandoraUtils = \Drupal::service('islandora.utils');
foreach ($tokens as $name => $original) {
switch ($name) {
+ case 'media-original-file:basename':
+ case 'media-original-file:filename':
+ case 'media-original-file:extension':
+ $term = $islandoraUtils->getTermForUri('http://pcdm.org/use#OriginalFile');
+ $media = $islandoraUtils->getMediaWithTerm($data['node'], $term);
+ // Is there media?
+ if ($media) {
+ $file = \Drupal::service('islandora.media_source_service')->getSourceFile($media);
+ if (!empty($file)) {
+ $path_info = pathinfo($file->createFileUrl());
+ $key = explode(':', $name)[1];
+ if (array_key_exists($key, $path_info)) {
+ $replacements[$original] = $path_info[$key];
+ }
+ }
+ }
+ break;
+
case 'media-thumbnail-image:url':
case 'media_thumbnail_image:url':
$term = $islandoraUtils->getTermForUri('http://pcdm.org/use#ThumbnailImage');
@@ -79,7 +109,7 @@ function islandora_tokens($type, $tokens, array $data, array $options, Bubbleabl
if ($media) {
$file = \Drupal::service('islandora.media_source_service')->getSourceFile($media);
if (!empty($file)) {
- $url = $file->url();
+ $url = $file->createFileUrl();
$replacements[$original] = $url;
}
}
@@ -104,7 +134,7 @@ function islandora_tokens($type, $tokens, array $data, array $options, Bubbleabl
break;
case 'pdf_url':
- $replacements[$original] = ' ' . islandora_url_to_service_file_media_by_mimetype($data['node'], 'application/pdf');
+ $replacements[$original] = islandora_url_to_service_file_media_by_mimetype($data['node'], 'application/pdf');
break;
}
}
@@ -158,4 +188,5 @@ function islandora_url_to_service_file_media_by_mimetype($node, $mime_type) {
}
}
}
+ return '';
}
diff --git a/islandora.views.inc b/islandora.views.inc
index cd826d081..f249c633f 100644
--- a/islandora.views.inc
+++ b/islandora.views.inc
@@ -9,17 +9,46 @@
* Implements hook_views_data_alter().
*/
function islandora_views_data_alter(&$data) {
- // For now only support Nodes.
- $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions('node');
- foreach ($fields as $field => $field_storage_definition) {
- if ($field_storage_definition->getType() == 'integer' && strpos($field, "field_") === 0) {
- $data['node__' . $field][$field . '_value']['field'] = $data['node__' . $field][$field]['field'];
- $data['node__' . $field][$field]['title'] = t('Integer Weight Selector (@field)', [
- '@field' => $field,
- ]);
- $data['node__' . $field][$field]['help'] = t('Provides a drag-n-drop reordering of integer-based weight fields.');
- $data['node__' . $field][$field]['title short'] = t('Integer weight selector');
- $data['node__' . $field][$field]['field']['id'] = 'integer_weight_selector';
+ // For now only support Nodes and Media.
+ foreach (['node', 'media'] as $entity_type) {
+ $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type);
+ foreach ($fields as $field => $field_storage_definition) {
+ if ($field_storage_definition->getType() == 'integer' && strpos($field, "field_") === 0) {
+ $prefixed_field = $entity_type . '__' . $field;
+ if (isset($data[$prefixed_field])) {
+ $data[$prefixed_field][$field . '_value']['field'] = $data[$prefixed_field][$field]['field'];
+ $data[$prefixed_field][$field]['title'] = t('Integer Weight Selector (@field)', [
+ '@field' => $field,
+ ]);
+ $data[$prefixed_field][$field]['help'] = t('Provides a drag-n-drop reordering of integer-based weight fields.');
+ $data[$prefixed_field][$field]['title short'] = t('Integer weight selector');
+ $data[$prefixed_field][$field]['field']['id'] = 'integer_weight_selector';
+ }
+ }
}
}
+
+ // Add Has Media filter.
+ $data['node_field_data']['islandora_has_media'] = [
+ 'title' => t('Node has Media Use'),
+ 'group' => t('Content'),
+ 'filter' => [
+ 'title' => t('Node has media use filter'),
+ 'help' => t('Provides a custom filter for nodes that do or do not have media with a given use.'),
+ 'field' => 'nid',
+ 'id' => 'islandora_node_has_media_use',
+ ],
+ ];
+
+ // Add Is Islandora filter.
+ $data['node_field_data']['islandora_node_is_islandora'] = [
+ 'title' => t('Node is Islandora'),
+ 'group' => t('Content'),
+ 'filter' => [
+ 'title' => t('Node is Islandora'),
+ 'help' => t('Node has a content type that possesses the mandatory Islandora fields.'),
+ 'field' => 'nid',
+ 'id' => 'islandora_node_is_islandora',
+ ],
+ ];
}
diff --git a/modules/islandora_advanced_search/islandora_advanced_search.info.yml b/modules/islandora_advanced_search/islandora_advanced_search.info.yml
index 524e9dc74..a5ad90efb 100644
--- a/modules/islandora_advanced_search/islandora_advanced_search.info.yml
+++ b/modules/islandora_advanced_search/islandora_advanced_search.info.yml
@@ -4,8 +4,10 @@ name: 'Islandora Advanced Search'
description: "Creates an Advanced Search block and other enhancements to search."
type: module
package: Islandora
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
dependencies:
- drupal:facets
- drupal:facets_summary
- drupal:search_api_solr
+lifecycle: deprecated
+lifecycle_link: https://groups.google.com/g/islandora/c/SEOAWJrfE_M
diff --git a/modules/islandora_advanced_search/islandora_advanced_search.module b/modules/islandora_advanced_search/islandora_advanced_search.module
index cbf52667a..f08dd3460 100644
--- a/modules/islandora_advanced_search/islandora_advanced_search.module
+++ b/modules/islandora_advanced_search/islandora_advanced_search.module
@@ -13,51 +13,13 @@
*/
use Drupal\block\Entity\Block;
-use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormStateInterface;
use Drupal\islandora_advanced_search\AdvancedSearchQuery;
-use Drupal\islandora_advanced_search\Form\SettingsForm;
use Drupal\islandora_advanced_search\Utilities;
use Drupal\search_api\Query\QueryInterface as DrupalQueryInterface;
use Drupal\views\ViewExecutable;
use Solarium\Core\Query\QueryInterface as SolariumQueryInterface;
-/**
- * Implements hook_theme().
- */
-function islandora_advanced_search_theme() {
- return [
- 'facets_item_list__include_exclude_links' => [
- 'template' => 'facets/facets-item-list--include-exclude-links',
- 'base hook' => 'facets_item_list',
- ],
- 'facets_result_item__include_exclude_links' => [
- 'template' => 'facets/facets-result-item--include-exclude-links',
- 'base hook' => 'facets_result_item',
- ],
- 'facets_result_item__summary' => [
- 'template' => 'facets/facets-result-item--summary',
- 'base hook' => 'facets_result_item',
- ],
- ];
-}
-
-/**
- * Implements hook_library_info_alter().
- */
-function islandora_advanced_search_library_info_alter(&$libraries, $extension) {
- if ($extension == 'facets') {
- // Override facets module javascript with customizations.
- $path = '/' . drupal_get_path('module', 'islandora_advanced_search') . '/js/facets';
- $libraries['soft-limit']['js'] = [
- "$path/soft-limit.js" => [],
- ];
- $libraries['drupal.facets.views-ajax']['js'] = [
- "$path/facets-views-ajax.js" => [],
- ];
- }
-}
-
/**
* Implements hook_search_api_solr_converted_query_alter().
*/
@@ -99,20 +61,6 @@ function islandora_advanced_search_form_block_form_alter(&$form, FormStateInterf
$form['visibility'][$condition_id] = $condition_form;
}
-/**
- * Implements hook_preprocess_block__facets_summary().
- */
-function islandora_advanced_search_preprocess_block__facets_summary(&$variables) {
- // Copy data-attributes to the content as the javascript expects
- // there to be no elements between the data declaration and the
- // content of the block.
- foreach ($variables['attributes'] as $key => $value) {
- if (substr($key, 0, 4) === "data") {
- $variables['content_attributes'][$key] = $value;
- }
- }
-}
-
/**
* Implements hook_preprocess_preprocess_views_view().
*/
@@ -139,53 +87,3 @@ function islandora_advanced_search_views_pre_view(ViewExecutable $view, $display
$advanced_search_query = new AdvancedSearchQuery();
$advanced_search_query->alterView(\Drupal::request(), $view, $display_id);
}
-
-/**
- * Implements hook_preprocess_facets_summary_item_list().
- */
-function islandora_advanced_search_preprocess_facets_summary_item_list(&$variables) {
- foreach ($variables['items'] as &$item) {
- $item['attributes']['class'][] = 'facet-summary-item';
- }
-}
-
-/**
- * Implements hook_preprocess_facets_item_list().
- */
-function islandora_advanced_search_preprocess_facets_item_list(&$variables) {
- $widget = $variables['facet']->getWidget();
- $soft_limit = $widget['config']['soft_limit'];
- // Break into two groups less / more which can display be toggled as a single
- // element change rather than showing / hiding all
elements individually.
- // As its slow and causes the page to snap when loading.
- $variables['less'] = array_slice($variables['items'], 0, $soft_limit);
- $variables['more'] = array_slice($variables['items'], $soft_limit);
- $variables['show_more_label'] = $widget['config']['soft_limit_settings']['show_more_label'];
-}
-
-/**
- * Implements hook_preprocess_facets_result_item().
- */
-function islandora_advanced_search_preprocess_facets_result_item(&$variables) {
- $settings = \Drupal::config(SettingsForm::CONFIG_NAME);
- $length = $settings->get(SettingsForm::FACET_TRUNCATE);
- if (is_numeric($length)) {
- // Limit the length of facets display to at most 32 characters.
- if (is_string($variables['value'])) {
- $variables['value'] = Unicode::truncate(
- $variables['value'],
- $length,
- TRUE,
- TRUE
- );
- }
- elseif (is_string($variables['value']['text']['#title'])) {
- $variables['value']['text']['#title'] = Unicode::truncate(
- $variables['value']['text']['#title'],
- $length,
- TRUE,
- TRUE
- );
- }
- }
-}
diff --git a/modules/islandora_advanced_search/js/facets/facets-views-ajax.js b/modules/islandora_advanced_search/js/facets/facets-views-ajax.js
deleted file mode 100644
index 0b04ea33d..000000000
--- a/modules/islandora_advanced_search/js/facets/facets-views-ajax.js
+++ /dev/null
@@ -1,147 +0,0 @@
-//# sourceURL=modules/contrib/islandora/modules/islandora_advanced_search/js/facets/facets-view.ajax.js
-/**
- * @file
- * Overrides the facets-view-ajax.js behavior from the 'facets' module.
- */
-(function ($, Drupal) {
- "use strict";
-
- // Generate events on push state.
- (function (history) {
- var pushState = history.pushState;
- history.pushState = function (state, title, url) {
- var ret = pushState.apply(this, arguments);
- var event = new Event("pushstate");
- window.dispatchEvent(event);
- return ret;
- };
- })(window.history);
-
- function reload(url) {
- // Update View.
- if (drupalSettings && drupalSettings.views && drupalSettings.views.ajaxViews) {
- var view_path = drupalSettings.views.ajax_path;
- $.each(drupalSettings.views.ajaxViews, function (views_dom_id) {
- var views_parameters = Drupal.Views.parseQueryString(url);
- var views_arguments = Drupal.Views.parseViewArgs(url, "search");
- var views_settings = $.extend(
- {},
- Drupal.views.instances[views_dom_id].settings,
- views_arguments,
- views_parameters
- );
- var views_ajax_settings =
- Drupal.views.instances[views_dom_id].element_settings;
- views_ajax_settings.submit = views_settings;
- views_ajax_settings.url =
- view_path + "?" + $.param(Drupal.Views.parseQueryString(url));
- Drupal.ajax(views_ajax_settings).execute();
- });
- }
-
- // Replace filter, pager, summary, and facet blocks.
- var blocks = {};
- $(
- ".block[class*='block-plugin-id--islandora-advanced-search-result-pager'], .block[class*='block-plugin-id--views-exposed-filter-block'], .block[class*='block-plugin-id--facet']"
- ).each(function () {
- var id = $(this).attr("id");
- var block_id = id
- .slice("block-".length, id.length)
- .replace(/--.*$/g, "")
- .replace(/-/g, "_");
- blocks[block_id] = "#" + id;
- });
- Drupal.ajax({
- url: Drupal.url("islandora-advanced-search-ajax-blocks"),
- submit: {
- link: url,
- blocks: blocks,
- },
- }).execute();
- }
-
- // On location change reload all the blocks / ajax view.
- window.addEventListener("pushstate", function (e) {
- reload(window.location.href);
- });
-
- window.addEventListener("popstate", function (e) {
- if (e.state != null) {
- reload(window.location.href);
- }
- });
-
- /**
- * Push state on form/pager/facet change.
- */
- Drupal.behaviors.islandoraAdvancedSearchViewsAjax = {
- attach: function (context, settings) {
- window.historyInitiated = true;
-
- // Remove existing behavior from form.
- if (settings && settings.views && settings.views.ajaxViews) {
- $.each(settings.views.ajaxViews, function (index, settings) {
- var exposed_form = $(
- "form#views-exposed-form-" +
- settings.view_name.replace(/_/g, "-") +
- "-" +
- settings.view_display_id.replace(/_/g, "-")
- );
- exposed_form
- .once()
- .find("input[type=submit], input[type=image]")
- .not("[data-drupal-selector=edit-reset]")
- .each(function (index) {
- $(this).unbind("click");
- $(this).click(function (e) {
- // Let ctrl/cmd click open in a new window.
- if (e.shiftKey || e.ctrlKey || e.metaKey) {
- return;
- }
- e.preventDefault();
- e.stopPropagation();
- var href = window.location.href;
- var params = Drupal.Views.parseQueryString(href);
- // Remove the page if set as submitting the form should always take
- // the user to the first page (facets do the same).
- delete params.page;
- // Include values from the form in the URL.
- $.each(exposed_form.serializeArray(), function () {
- params[this.name] = this.value;
- });
- href = href.split("?")[0] + "?" + $.param(params);
- window.history.pushState(null, document.title, href);
- });
- });
- });
- }
-
- // Attach behavior to pager, summary, facet links.
- $("[data-drupal-pager-id], [data-drupal-facets-summary-id], [data-drupal-facet-id]")
- .once()
- .find("a:not(.facets-soft-limit-link)")
- .click(function (e) {
- // Let ctrl/cmd click open in a new window.
- if (e.shiftKey || e.ctrlKey || e.metaKey) {
- return;
- }
- e.preventDefault();
- window.history.pushState(null, document.title, $(this).attr("href"));
- });
-
- // Trigger on sort change.
- $('[data-drupal-pager-id] select[name="order"]')
- .once()
- .change(function () {
- var href = window.location.href;
- var params = Drupal.Views.parseQueryString(href);
- var selection = $(this).val();
- var option = $('option[value="' + selection + '"]');
- params.sort_order = option.data("sort_order");
- params.sort_by = option.data("sort_by");
- href = href.split("?")[0] + "?" + $.param(params);
- window.history.pushState(null, document.title, href);
- });
- },
- };
-})(jQuery, Drupal);
diff --git a/modules/islandora_advanced_search/js/facets/soft-limit.js b/modules/islandora_advanced_search/js/facets/soft-limit.js
deleted file mode 100644
index a81a267ce..000000000
--- a/modules/islandora_advanced_search/js/facets/soft-limit.js
+++ /dev/null
@@ -1,70 +0,0 @@
-//# sourceURL=modules/contrib/islandora/modules/islandora_advanced_search/js/facets/soft-limit.js
-/**
- * @file
- * Overrides the soft-limit.js behavior from the 'facets' module.
- * As when having many facets the original version causes the page to slow down and snap to hidden when rendering.
- */
-(function ($) {
-
- 'use strict';
-
- Drupal.behaviors.facetSoftLimit = {
- attach: function (context, settings) {
- if (settings.facets.softLimit !== 'undefined') {
- $.each(settings.facets.softLimit, function (facet, limit) {
- Drupal.facets.applySoftLimit(facet, limit, settings);
- });
- }
- }
- };
-
- Drupal.facets = Drupal.facets || {};
-
- /**
- * Applies the soft limit UI feature to a specific facets list.
- *
- * @param {string} facet
- * The facet id.
- * @param {string} limit
- * The maximum amount of items to show.
- * @param {object} settings
- * Settings.
- */
- Drupal.facets.applySoftLimit = function (facet, limit, settings) {
- var zero_based_limit = (limit - 1);
- var facet_id = facet;
- var facetsList = $('ul[data-drupal-facet-id="' + facet_id + '"]');
-
- // In case of multiple instances of a facet, we need to key them.
- if (facetsList.length > 1) {
- facetsList.each(function (key, $value) {
- $(this).attr('data-drupal-facet-id', facet_id + '-' + key);
- });
- }
-
- // Add "Show more" / "Show less" links.
- facetsList.filter(function () {
- return $(this).next('ul').length == 1; // Has expanding list.
- }).each(function () {
- var facet = $(this);
- var expand = facet.next('ul');
- var link = expand.next('a');
- var showLessLabel = settings.facets.softLimitSettings[facet_id].showLessLabel;
- var showMoreLabel = settings.facets.softLimitSettings[facet_id].showMoreLabel;
- link.text(showMoreLabel)
- .once()
- .on('click', function () {
- if (!expand.is(":visible")) {
- expand.slideDown();
- $(this).addClass('open').text(showLessLabel);
- }
- else {
- expand.slideUp();
- $(this).removeClass('open').text(showMoreLabel);
- }
- return false;
- })
- });
- };
-
-})(jQuery);
diff --git a/modules/islandora_advanced_search/src/Form/AdvancedSearchForm.php b/modules/islandora_advanced_search/src/Form/AdvancedSearchForm.php
index c00ce5665..09dd1646d 100644
--- a/modules/islandora_advanced_search/src/Form/AdvancedSearchForm.php
+++ b/modules/islandora_advanced_search/src/Form/AdvancedSearchForm.php
@@ -71,7 +71,7 @@ public function __construct(Request $request, RouteMatchInterface $current_route
*/
public static function create(ContainerInterface $container) {
return new static(
- $container->get('request_stack')->getMasterRequest(),
+ $container->get('request_stack')->getMainRequest(),
$container->get('current_route_match')
);
}
diff --git a/modules/islandora_advanced_search/src/Plugin/Block/AdvancedSearchBlock.php b/modules/islandora_advanced_search/src/Plugin/Block/AdvancedSearchBlock.php
index c5f5adb8c..2aa77f124 100644
--- a/modules/islandora_advanced_search/src/Plugin/Block/AdvancedSearchBlock.php
+++ b/modules/islandora_advanced_search/src/Plugin/Block/AdvancedSearchBlock.php
@@ -108,7 +108,7 @@ public static function create(ContainerInterface $container, array $configuratio
$plugin_definition,
$container->get('plugin.manager.search_api.display'),
$container->get('form_builder'),
- $container->get('request_stack')->getMasterRequest()
+ $container->get('request_stack')->getMainRequest()
);
}
diff --git a/modules/islandora_advanced_search/src/Plugin/Block/SearchResultsPagerBlock.php b/modules/islandora_advanced_search/src/Plugin/Block/SearchResultsPagerBlock.php
index f2e4a1703..e1e477d83 100644
--- a/modules/islandora_advanced_search/src/Plugin/Block/SearchResultsPagerBlock.php
+++ b/modules/islandora_advanced_search/src/Plugin/Block/SearchResultsPagerBlock.php
@@ -58,7 +58,7 @@ public static function create(ContainerInterface $container, array $configuratio
$configuration,
$plugin_id,
$plugin_definition,
- $container->get('request_stack')->getMasterRequest()
+ $container->get('request_stack')->getMainRequest()
);
}
diff --git a/modules/islandora_audio/config/schema/islandora_audio.schema.yml b/modules/islandora_audio/config/schema/islandora_audio.schema.yml
index 5f09740a3..82b080887 100644
--- a/modules/islandora_audio/config/schema/islandora_audio.schema.yml
+++ b/modules/islandora_audio/config/schema/islandora_audio.schema.yml
@@ -29,3 +29,6 @@ action.configuration.generate_audio_derivative:
path:
type: text
label: 'File path with extension'
+
+field.formatter.settings.islandora_file_audio:
+ type: field.formatter.settings.file_audio
diff --git a/modules/islandora_audio/islandora_audio.info.yml b/modules/islandora_audio/islandora_audio.info.yml
index 998590f57..5e6beb5ae 100644
--- a/modules/islandora_audio/islandora_audio.info.yml
+++ b/modules/islandora_audio/islandora_audio.info.yml
@@ -2,7 +2,6 @@ name: 'Islandora Audio'
description: 'Islandora audio derivative actions'
type: module
package: Islandora
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
dependencies:
- drupal:islandora
diff --git a/modules/islandora_audio/tests/src/Functional/GenerateAudioDerivativeTest.php b/modules/islandora_audio/tests/src/Functional/GenerateAudioDerivativeTest.php
index fc1c6188e..6b85cd1b5 100644
--- a/modules/islandora_audio/tests/src/Functional/GenerateAudioDerivativeTest.php
+++ b/modules/islandora_audio/tests/src/Functional/GenerateAudioDerivativeTest.php
@@ -66,9 +66,10 @@ public function testGenerateAudioDerivativeFromScratch() {
'name[0][value]' => 'Test Media',
'files[field_media_file_0]' => __DIR__ . '/../../fixtures/test_file.txt',
'field_media_of[0][target_id]' => 'Test Node',
- 'field_tags[0][target_id]' => 'Preservation Master',
+ 'field_media_use[0][target_id]' => $this->preservationMasterTerm->label(),
];
- $this->drupalPostForm('media/add/' . $this->testMediaType->id(), $values, $this->t('Save'));
+ $this->drupalGet('media/add/' . $this->testMediaType->id());
+ $this->submitForm($values, $this->t('Save'));
$expected = [
'source_uri' => 'test_file.txt',
diff --git a/modules/islandora_breadcrumbs/config/install/islandora_breadcrumbs.breadcrumbs.yml b/modules/islandora_breadcrumbs/config/install/islandora_breadcrumbs.breadcrumbs.yml
index ad166b50b..aabb58916 100644
--- a/modules/islandora_breadcrumbs/config/install/islandora_breadcrumbs.breadcrumbs.yml
+++ b/modules/islandora_breadcrumbs/config/install/islandora_breadcrumbs.breadcrumbs.yml
@@ -1,9 +1,4 @@
maxDepth: -1
includeSelf: FALSE
-referenceField: field_member_of
-dependencies:
- module:
- - islandora
- enforced:
- module:
- - islandora_breadcrumbs
+referenceFields:
+ - field_member_of
diff --git a/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml b/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml
index 6bc44096d..4175cbf51 100644
--- a/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml
+++ b/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml
@@ -7,6 +7,9 @@ islandora_breadcrumbs.breadcrumbs:
includeSelf:
type: boolean
label: 'Include Self'
- referenceField:
- type: string
- label: 'Reference Field'
+ referenceFields:
+ type: sequence
+ label: 'Reference Fields'
+ sequence:
+ type: string
+ label: 'Reference Field'
diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml b/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml
index 56a10bc14..661ec70a2 100644
--- a/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml
+++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml
@@ -1,8 +1,7 @@
name: 'Islandora Breadcrumbs'
type: module
description: 'Builds breadcrumbs based on field_member_of relationships.'
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
package: Islandora
dependencies:
- - drupal:islandora
+ - islandora:islandora
diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.install b/modules/islandora_breadcrumbs/islandora_breadcrumbs.install
new file mode 100644
index 000000000..2ca9ada68
--- /dev/null
+++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.install
@@ -0,0 +1,18 @@
+getEditable('islandora_breadcrumbs.breadcrumbs');
+ $config->set('referenceFields', [$config->get('referenceField')]);
+ $config->clear('referenceField');
+ $config->save();
+ return "Updated referenceFields config.";
+}
diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.links.menu.yml b/modules/islandora_breadcrumbs/islandora_breadcrumbs.links.menu.yml
new file mode 100644
index 000000000..dcf995346
--- /dev/null
+++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.links.menu.yml
@@ -0,0 +1,5 @@
+system.islandora_breadcrumbs_settings:
+ title: 'Breadcrumbs Settings'
+ parent: system.admin_config_islandora
+ route_name: system.islandora_breadcrumbs_settings
+ description: 'Configure Islandora breadcrumb settings'
diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.routing.yml b/modules/islandora_breadcrumbs/islandora_breadcrumbs.routing.yml
new file mode 100644
index 000000000..e9fd9ac1b
--- /dev/null
+++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.routing.yml
@@ -0,0 +1,7 @@
+system.islandora_breadcrumbs_settings:
+ path: '/admin/config/islandora/breadcrumbs'
+ defaults:
+ _form: 'Drupal\islandora_breadcrumbs\Form\IslandoraBreadcrumbsSettingsForm'
+ _title: 'Islandora Breadcrumbs Settings'
+ requirements:
+ _permission: 'administer site configuration'
diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml b/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml
index 58e3c9594..71c723f02 100644
--- a/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml
+++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml
@@ -1,6 +1,6 @@
services:
islandora_breadcrumbs.breadcrumb:
class: Drupal\islandora_breadcrumbs\IslandoraBreadcrumbBuilder
- arguments: ['@entity_type.manager', '@config.factory']
+ arguments: ['@entity_type.manager', '@config.factory', '@islandora.utils']
tags:
- { name: breadcrumb_builder, priority: 100 }
diff --git a/modules/islandora_breadcrumbs/src/Form/IslandoraBreadcrumbsSettingsForm.php b/modules/islandora_breadcrumbs/src/Form/IslandoraBreadcrumbsSettingsForm.php
new file mode 100644
index 000000000..679a565e6
--- /dev/null
+++ b/modules/islandora_breadcrumbs/src/Form/IslandoraBreadcrumbsSettingsForm.php
@@ -0,0 +1,132 @@
+config(static::SETTINGS);
+
+ $form['maxDepth'] = [
+ '#type' => 'number',
+ '#default_value' => $config->get('maxDepth'),
+ '#min' => -1,
+ '#step' => 1,
+ '#title' => $this->t('Maximum number of ancestor breadcrumbs'),
+ '#description' => $this->t("Stops adding ancestor references when the chain reaches this number. The count does not include the current node when enabled. The default value, '-1' disables this feature."),
+ ];
+
+ $form['includeSelf'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Include the current node in the breadcrumbs?'),
+ '#default_value' => $config->get('includeSelf'),
+ ];
+
+ // Using the textarea instead of a select so the site maintainer can
+ // provide an ordered list of items rather than simply selecting from a
+ // list which enforces it's own order.
+ $form['referenceFields'] = [
+ '#type' => 'textarea',
+ '#title' => $this->t('Entity Reference fields to follow'),
+ '#default_value' => implode("\n", $config->get('referenceFields')),
+ '#description' => $this->t("Entity Reference field machine names to follow when building the breadcrumbs.
One per line.
Valid options: @options",
+ [
+ "@options" => implode(", ", static::getNodeEntityReferenceFields()),
+ ]
+ ),
+ '#element_validate' => [[get_class($this), 'validateReferenceFields']],
+
+ ];
+
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * Returns a list of node entity reference field machine names.
+ *
+ * We use this for building the form field description and for
+ * validating the reference fields value.
+ */
+ protected static function getNodeEntityReferenceFields() {
+ return array_keys(\Drupal::service('entity_field.manager')->getFieldMapByFieldType('entity_reference')['node']);
+ }
+
+ /**
+ * Turns a text area into an array of values.
+ *
+ * Used for validating the field reference text area
+ * and saving the form state.
+ */
+ protected static function textToArray($string) {
+ return array_filter(array_map('trim', explode("\n", $string)), 'strlen');
+ }
+
+ /**
+ * Callback for settings form.
+ *
+ * @param array $element
+ * An associative array containing the properties and children of the
+ * generic form element.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form for the form this element belongs to.
+ *
+ * @see \Drupal\Core\Render\Element\FormElement::processPattern()
+ */
+ public static function validateReferenceFields(array $element, FormStateInterface $form_state) {
+
+ $valid_fields = static::getNodeEntityReferenceFields();
+
+ foreach (static::textToArray($element['#value']) as $value) {
+ if (!in_array($value, $valid_fields)) {
+ $form_state->setError($element, t('"@field" is not a valid entity reference field!', ["@field" => $value]));
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $this->configFactory->getEditable(static::SETTINGS)
+ ->set('referenceFields', static::textToArray($form_state->getValue('referenceFields')))
+ ->set('maxDepth', $form_state->getValue('maxDepth'))
+ ->set('includeSelf', $form_state->getValue('includeSelf'))
+ ->save();
+
+ parent::submitForm($form, $form_state);
+ }
+
+}
diff --git a/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php b/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php
index 93ed7097b..5ed7f6d06 100644
--- a/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php
+++ b/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php
@@ -4,12 +4,12 @@
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\islandora\IslandoraUtils;
/**
* Provides breadcrumbs for nodes using a configured entity reference field.
@@ -31,6 +31,13 @@ class IslandoraBreadcrumbBuilder implements BreadcrumbBuilderInterface {
*/
protected $nodeStorage;
+ /**
+ * Islandora utils.
+ *
+ * @var \Drupal\islandora\IslandoraUtils
+ */
+ protected $utils;
+
/**
* Constructs a breadcrumb builder.
*
@@ -38,10 +45,13 @@ class IslandoraBreadcrumbBuilder implements BreadcrumbBuilderInterface {
* Storage to load nodes.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
+ * @param \Drupal\islandora\IslandoraUtils $utils
+ * Islandora utils service.
*/
- public function __construct(EntityTypeManagerInterface $entity_manager, ConfigFactoryInterface $config_factory) {
+ public function __construct(EntityTypeManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, IslandoraUtils $utils) {
$this->nodeStorage = $entity_manager->getStorage('node');
$this->config = $config_factory->get('islandora_breadcrumbs.breadcrumbs');
+ $this->utils = $utils;
}
/**
@@ -54,7 +64,14 @@ public function applies(RouteMatchInterface $attributes) {
$nid = $attributes->getRawParameters()->get('node');
if (!empty($nid)) {
$node = $this->nodeStorage->load($nid);
- return (!empty($node) && $node->hasField($this->config->get('referenceField')));
+ if (empty($node)) {
+ return FALSE;
+ }
+ foreach ($this->config->get('referenceFields') as $field) {
+ if ($node->hasField($field)) {
+ return TRUE;
+ }
+ }
}
}
@@ -66,51 +83,29 @@ public function build(RouteMatchInterface $route_match) {
$nid = $route_match->getRawParameters()->get('node');
$node = $this->nodeStorage->load($nid);
$breadcrumb = new Breadcrumb();
+ $breadcrumb->addCacheableDependency($this->config);
$breadcrumb->addLink(Link::createFromRoute($this->t('Home'), ''));
- $chain = [];
- $this->walkMembership($node, $chain);
+ $chain = array_reverse($this->utils->findAncestors($node, $this->config->get('referenceFields'), $this->config->get('maxDepth')));
- if (!$this->config->get('includeSelf')) {
- array_pop($chain);
+ // XXX: Handle a looping breadcrumb scenario by filtering the present
+ // node out and then optionally re-adding it after if set to do so.
+ $chain = array_filter($chain, function ($link) use ($nid) {
+ return $link !== $nid;
+ });
+ if ($this->config->get('includeSelf')) {
+ array_push($chain, $nid);
}
$breadcrumb->addCacheableDependency($node);
// Add membership chain to the breadcrumb.
foreach ($chain as $chainlink) {
- $breadcrumb->addCacheableDependency($chainlink);
- $breadcrumb->addLink($chainlink->toLink());
+ $node = $this->nodeStorage->load($chainlink);
+ $breadcrumb->addCacheableDependency($node);
+ $breadcrumb->addLink($node->toLink());
}
$breadcrumb->addCacheContexts(['route']);
return $breadcrumb;
}
- /**
- * Follows chain of field_member_of links.
- *
- * We pass crumbs by reference to enable checking for looped chains.
- */
- protected function walkMembership(EntityInterface $entity, &$crumbs) {
- // Avoid infinate loops, return if we've seen this before.
- foreach ($crumbs as $crumb) {
- if ($crumb->uuid == $entity->uuid) {
- return;
- }
- }
-
- // Add this item onto the pile.
- array_unshift($crumbs, $entity);
-
- if ($this->config->get('maxDepth') > 0 && count($crumbs) >= $this->config->get('maxDepth')) {
- return;
- }
-
- // Find the next in the chain, if there are any.
- if ($entity->hasField($this->config->get('referenceField')) &&
- !$entity->get($this->config->get('referenceField'))->isEmpty() &&
- $entity->get($this->config->get('referenceField'))->entity instanceof EntityInterface) {
- $this->walkMembership($entity->get($this->config->get('referenceField'))->entity, $crumbs);
- }
- }
-
}
diff --git a/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php b/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php
index 80f5dbee0..ee35a1ed3 100644
--- a/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php
+++ b/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php
@@ -20,7 +20,7 @@ class BreadcrumbsTest extends IslandoraFunctionalTestBase {
*
* @var array
*/
- public static $modules = [
+ protected static $modules = [
'islandora_breadcrumbs',
];
@@ -56,7 +56,7 @@ class BreadcrumbsTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
// Create some nodes.
diff --git a/modules/islandora_core_feature/config/install/field.storage.node.field_weight.yml b/modules/islandora_core_feature/config/install/field.storage.node.field_weight.yml
index 97619cd26..4976d1d60 100644
--- a/modules/islandora_core_feature/config/install/field.storage.node.field_weight.yml
+++ b/modules/islandora_core_feature/config/install/field.storage.node.field_weight.yml
@@ -14,6 +14,8 @@ module: core
locked: false
cardinality: 1
translatable: true
-indexes: { }
+indexes:
+ value:
+ - value
persist_with_no_fields: false
custom_storage: false
diff --git a/modules/islandora_core_feature/config/install/filehash.settings.yml b/modules/islandora_core_feature/config/install/filehash.settings.yml
index aa9c188d1..3dcae2973 100644
--- a/modules/islandora_core_feature/config/install/filehash.settings.yml
+++ b/modules/islandora_core_feature/config/install/filehash.settings.yml
@@ -1,5 +1,24 @@
algos:
- sha1: sha1
+ blake2b_128: '0'
+ blake2b_160: '0'
+ blake2b_224: '0'
+ blake2b_256: '0'
+ blake2b_384: '0'
+ blake2b_512: '0'
md5: '0'
+ sha1: sha1
+ sha224: '0'
sha256: '0'
-dedupe: false
+ sha384: '0'
+ sha512_224: '0'
+ sha512_256: '0'
+ sha512: '0'
+ sha3_224: '0'
+ sha3_256: '0'
+ sha3_384: '0'
+ sha3_512: '0'
+dedupe: 0
+rehash: true
+original: true
+dedupe_original: false
+mime_types: { }
diff --git a/modules/islandora_core_feature/config/install/views.view.all_taxonomy_terms.yml b/modules/islandora_core_feature/config/install/views.view.all_taxonomy_terms.yml
index 56b450669..8c3cb0f3d 100644
--- a/modules/islandora_core_feature/config/install/views.view.all_taxonomy_terms.yml
+++ b/modules/islandora_core_feature/config/install/views.view.all_taxonomy_terms.yml
@@ -168,4 +168,3 @@ display:
- url.query_args
- user.permissions
tags: { }
-
diff --git a/modules/islandora_core_feature/config/install/views.view.file_checksum.yml b/modules/islandora_core_feature/config/install/views.view.file_checksum.yml
index b498ba666..e529f21ec 100644
--- a/modules/islandora_core_feature/config/install/views.view.file_checksum.yml
+++ b/modules/islandora_core_feature/config/install/views.view.file_checksum.yml
@@ -1,14 +1,15 @@
langcode: en
status: true
dependencies:
- enforced:
- module:
- - islandora_core_feature
module:
- file
- filehash
- rest
- serialization
+ - user
+ enforced:
+ module:
+ - islandora_core_feature
id: file_checksum
label: 'File Checksum'
module: views
@@ -16,71 +17,24 @@ description: 'Exposes a REST endpoint for getting the checksum of a File'
tag: ''
base_table: file_managed
base_field: fid
-core: 8.x
display:
default:
- display_plugin: default
id: default
display_title: Master
+ display_plugin: default
position: 0
display_options:
- access:
- type: none
- options: { }
- cache:
- type: tag
- options: { }
- query:
- type: views_query
- options:
- disable_sql_rewrite: false
- distinct: false
- replica: false
- query_comment: ''
- query_tags: { }
- exposed_form:
- type: basic
- options:
- submit_button: Apply
- reset_button: false
- reset_button_label: Reset
- exposed_sorts_label: 'Sort by'
- expose_sort_order: true
- sort_asc_label: Asc
- sort_desc_label: Desc
- pager:
- type: mini
- options:
- items_per_page: 10
- offset: 0
- id: 0
- total_pages: null
- expose:
- items_per_page: false
- items_per_page_label: 'Items per page'
- items_per_page_options: '5, 10, 25, 50'
- items_per_page_options_all: false
- items_per_page_options_all_label: '- All -'
- offset: false
- offset_label: Offset
- tags:
- previous: ‹‹
- next: ››
- style:
- type: serializer
- row:
- type: 'entity:file'
- options:
- relationship: none
- view_mode: default
fields:
sha1:
id: sha1
- table: filehash
+ table: file_managed
field: sha1
relationship: none
group_type: group
admin_label: ''
+ entity_type: file
+ entity_field: sha1
+ plugin_id: field
label: ''
exclude: false
alter:
@@ -92,7 +46,7 @@ display:
external: false
replace_spaces: false
path_case: none
- trim_whitespace: false
+ trim_whitespace: true
alt: ''
rel: ''
link_class: ''
@@ -106,7 +60,7 @@ display:
more_link: false
more_link_text: ''
more_link_path: ''
- strip_tags: false
+ strip_tags: true
trim: false
preserve_tags: ''
html: false
@@ -122,13 +76,120 @@ display:
hide_empty: false
empty_zero: false
hide_alter_empty: true
- plugin_id: standard
- filters: { }
- sorts: { }
- header: { }
- footer: { }
+ click_sort_column: value
+ type: filehash
+ settings: { }
+ group_column: value
+ group_columns: { }
+ group_rows: true
+ delta_limit: 0
+ delta_offset: 0
+ delta_reversed: false
+ delta_first_last: false
+ multi_type: separator
+ separator: ', '
+ field_api_classes: false
+ original_sha1:
+ id: original_sha1
+ table: file_managed
+ field: original_sha1
+ relationship: none
+ group_type: group
+ admin_label: ''
+ entity_type: file
+ entity_field: original_sha1
+ plugin_id: field
+ label: ''
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: true
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: true
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: false
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: false
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ click_sort_column: value
+ type: filehash
+ settings: { }
+ group_column: value
+ group_columns: { }
+ group_rows: true
+ delta_limit: 0
+ delta_offset: 0
+ delta_reversed: false
+ delta_first_last: false
+ multi_type: separator
+ separator: ', '
+ field_api_classes: false
+ pager:
+ type: mini
+ options:
+ offset: 0
+ items_per_page: 10
+ total_pages: null
+ id: 0
+ tags:
+ next: ››
+ previous: ‹‹
+ expose:
+ items_per_page: false
+ items_per_page_label: 'Items per page'
+ items_per_page_options: '5, 10, 25, 50'
+ items_per_page_options_all: false
+ items_per_page_options_all_label: '- All -'
+ offset: false
+ offset_label: Offset
+ exposed_form:
+ type: basic
+ options:
+ submit_button: Apply
+ reset_button: false
+ reset_button_label: Reset
+ exposed_sorts_label: 'Sort by'
+ expose_sort_order: true
+ sort_asc_label: Asc
+ sort_desc_label: Desc
+ access:
+ type: perm
+ options:
+ perm: 'view checksums'
+ cache:
+ type: tag
+ options: { }
empty: { }
- relationships: { }
+ sorts: { }
arguments:
fid:
id: fid
@@ -137,6 +198,9 @@ display:
relationship: none
group_type: group
admin_label: ''
+ entity_type: file
+ entity_field: fid
+ plugin_id: file_fid
default_action: 'not found'
exception:
value: all
@@ -151,8 +215,8 @@ display:
summary_options:
base_path: ''
count: true
- items_per_page: 25
override: false
+ items_per_page: 25
summary:
sort_order: asc
number_of_records: 0
@@ -164,31 +228,47 @@ display:
validate_options: { }
break_phrase: false
not: false
- entity_type: file
- entity_field: fid
- plugin_id: file_fid
+ filters: { }
+ style:
+ type: serializer
+ row:
+ type: 'entity:file'
+ options:
+ relationship: none
+ view_mode: default
+ query:
+ type: views_query
+ options:
+ query_comment: ''
+ disable_sql_rewrite: false
+ distinct: false
+ replica: false
+ query_tags: { }
+ relationships: { }
+ header: { }
+ footer: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
+ - 'languages:language_content'
- 'languages:language_interface'
- request_format
- url
- url.query_args
+ - user.permissions
tags: { }
rest_export_1:
- display_plugin: rest_export
id: rest_export_1
display_title: 'REST export'
+ display_plugin: rest_export
position: 1
display_options:
- display_extenders: { }
- path: checksum/%file
pager:
type: some
options:
- items_per_page: 10
offset: 0
+ items_per_page: 10
style:
type: serializer
options:
@@ -198,6 +278,19 @@ display:
type: data_field
options:
field_options: { }
+ display_extenders:
+ matomo:
+ enabled: false
+ keyword_gets: ''
+ keyword_behavior: first
+ keyword_concat_separator: ' '
+ category_behavior: none
+ category_gets: ''
+ category_concat_separator: ' '
+ category_fallback: ''
+ category_facets: { }
+ category_facets_concat_separator: ', '
+ path: checksum/%file
auth:
- basic_auth
- jwt_auth
@@ -205,7 +298,9 @@ display:
cache_metadata:
max-age: -1
contexts:
+ - 'languages:language_content'
- 'languages:language_interface'
- request_format
- url
+ - user.permissions
tags: { }
diff --git a/modules/islandora_core_feature/config/install/views.view.manage_members.yml b/modules/islandora_core_feature/config/install/views.view.manage_members.yml
index a978f1d22..4a2429249 100644
--- a/modules/islandora_core_feature/config/install/views.view.manage_members.yml
+++ b/modules/islandora_core_feature/config/install/views.view.manage_members.yml
@@ -1,15 +1,17 @@
langcode: en
status: true
dependencies:
- enforced:
- module:
- - islandora_core_feature
module:
- jsonld
- node
- rest
- serialization
- user
+ enforced:
+ module:
+ - islandora_core_feature
+_core:
+ default_config_hash: Zwu8JUsBiYaxPko_9DbJJhA-ZZSGOm81I9XtT9NH3M4
id: manage_members
label: 'Manage members'
module: views
@@ -17,61 +19,14 @@ description: 'Manage members belonging to a piece of content'
tag: ''
base_table: node_field_data
base_field: nid
-core: 8.x
display:
default:
- display_plugin: default
id: default
display_title: Master
+ display_plugin: default
position: 0
display_options:
- access:
- type: perm
- options:
- perm: 'manage members'
- cache:
- type: tag
- options: { }
- query:
- type: views_query
- options:
- disable_sql_rewrite: false
- distinct: false
- replica: false
- query_comment: ''
- query_tags: { }
- exposed_form:
- type: basic
- options:
- submit_button: Apply
- reset_button: false
- reset_button_label: Reset
- exposed_sorts_label: 'Sort by'
- expose_sort_order: true
- sort_asc_label: Asc
- sort_desc_label: Desc
- pager:
- type: mini
- options:
- items_per_page: 10
- offset: 0
- id: 0
- total_pages: null
- expose:
- items_per_page: false
- items_per_page_label: 'Items per page'
- items_per_page_options: '5, 10, 25, 50'
- items_per_page_options_all: false
- items_per_page_options_all_label: '- All -'
- offset: false
- offset_label: Offset
- tags:
- previous: ‹‹
- next: ››
- style:
- type: table
- row:
- type: fields
+ title: 'Manage members'
fields:
node_bulk_form:
id: node_bulk_form
@@ -80,6 +35,8 @@ display:
relationship: none
group_type: group
admin_label: ''
+ entity_type: node
+ plugin_id: node_bulk_form
label: 'Node operations bulk form'
exclude: false
alter:
@@ -124,33 +81,27 @@ display:
action_title: Action
include_exclude: exclude
selected_actions: { }
- entity_type: node
- plugin_id: node_bulk_form
title:
id: title
table: node_field_data
field: title
+ relationship: none
+ group_type: group
+ admin_label: ''
entity_type: node
entity_field: title
+ plugin_id: field
+ label: Title
+ exclude: false
alter:
alter_text: false
make_link: false
absolute: false
- trim: false
word_boundary: false
ellipsis: false
strip_tags: false
+ trim: false
html: false
- hide_empty: false
- empty_zero: false
- settings:
- link_to_entity: true
- plugin_id: field
- relationship: none
- group_type: group
- admin_label: ''
- label: Title
- exclude: false
element_type: ''
element_class: ''
element_label_type: ''
@@ -160,9 +111,13 @@ display:
element_wrapper_class: ''
element_default_classes: true
empty: ''
+ hide_empty: false
+ empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
+ settings:
+ link_to_entity: true
group_column: value
group_columns: { }
group_rows: true
@@ -180,6 +135,8 @@ display:
relationship: none
group_type: group
admin_label: ''
+ entity_type: node
+ plugin_id: entity_operations
label: 'Operations links'
exclude: false
alter:
@@ -222,15 +179,56 @@ display:
empty_zero: false
hide_alter_empty: true
destination: false
- entity_type: node
- plugin_id: entity_operations
- filters: { }
- sorts: { }
- title: 'Manage members'
- header: { }
- footer: { }
+ pager:
+ type: mini
+ options:
+ offset: 0
+ items_per_page: 10
+ total_pages: null
+ id: 0
+ tags:
+ next: ››
+ previous: ‹‹
+ expose:
+ items_per_page: false
+ items_per_page_label: 'Items per page'
+ items_per_page_options: '5, 10, 25, 50'
+ items_per_page_options_all: false
+ items_per_page_options_all_label: '- All -'
+ offset: false
+ offset_label: Offset
+ exposed_form:
+ type: basic
+ options:
+ submit_button: Apply
+ reset_button: false
+ reset_button_label: Reset
+ exposed_sorts_label: 'Sort by'
+ expose_sort_order: true
+ sort_asc_label: Asc
+ sort_desc_label: Desc
+ access:
+ type: perm
+ options:
+ perm: 'manage members'
+ cache:
+ type: tag
+ options: { }
empty: { }
- relationships: { }
+ sorts:
+ field_weight_value:
+ id: field_weight_value
+ table: node__field_weight
+ field: field_weight_value
+ relationship: none
+ group_type: group
+ admin_label: ''
+ plugin_id: standard
+ order: ASC
+ expose:
+ label: ''
+ field_identifier: ''
+ exposed: false
arguments:
field_member_of_target_id:
id: field_member_of_target_id
@@ -239,6 +237,7 @@ display:
relationship: none
group_type: group
admin_label: ''
+ plugin_id: numeric
default_action: default
exception:
value: all
@@ -252,8 +251,8 @@ display:
summary_options:
base_path: ''
count: true
- items_per_page: 25
override: false
+ items_per_page: 25
summary:
sort_order: asc
number_of_records: 0
@@ -263,17 +262,32 @@ display:
type: 'entity:node'
fail: 'not found'
validate_options:
- operation: view
- multiple: 0
bundles: { }
access: false
+ operation: view
+ multiple: 0
break_phrase: false
not: false
- plugin_id: numeric
- display_extenders: { }
+ filters: { }
filter_groups:
operator: AND
groups: { }
+ style:
+ type: table
+ row:
+ type: fields
+ query:
+ type: views_query
+ options:
+ query_comment: ''
+ disable_sql_rewrite: false
+ distinct: false
+ replica: false
+ query_tags: { }
+ relationships: { }
+ header: { }
+ footer: { }
+ display_extenders: { }
cache_metadata:
max-age: 0
contexts:
@@ -285,9 +299,9 @@ display:
- user.permissions
tags: { }
page_1:
- display_plugin: page
id: page_1
display_title: Page
+ display_plugin: page
position: 1
display_options:
display_extenders: { }
@@ -296,11 +310,11 @@ display:
type: tab
title: Children
description: ''
+ weight: 0
expanded: false
+ menu_name: main
parent: ''
- weight: 0
context: '0'
- menu_name: main
cache_metadata:
max-age: 0
contexts:
@@ -312,13 +326,11 @@ display:
- user.permissions
tags: { }
rest_export_1:
- display_plugin: rest_export
id: rest_export_1
display_title: 'REST export'
+ display_plugin: rest_export
position: 1
display_options:
- display_extenders: { }
- path: node/%node/members
style:
type: serializer
options:
@@ -326,6 +338,8 @@ display:
formats:
jsonld: jsonld
json: json
+ display_extenders: { }
+ path: node/%node/members
auth:
- basic_auth
- jwt_auth
diff --git a/modules/islandora_core_feature/config/install/views.view.non_fedora_files.yml b/modules/islandora_core_feature/config/install/views.view.non_fedora_files.yml
index 88b0f308e..b90494f5d 100644
--- a/modules/islandora_core_feature/config/install/views.view.non_fedora_files.yml
+++ b/modules/islandora_core_feature/config/install/views.view.non_fedora_files.yml
@@ -194,4 +194,3 @@ display:
- url.query_args
- user.permissions
tags: { }
-
diff --git a/modules/islandora_core_feature/islandora_core_feature.info.yml b/modules/islandora_core_feature/islandora_core_feature.info.yml
index bf4f8d7a6..6976eb8ca 100755
--- a/modules/islandora_core_feature/islandora_core_feature.info.yml
+++ b/modules/islandora_core_feature/islandora_core_feature.info.yml
@@ -1,8 +1,7 @@
name: 'Islandora Core Feature'
description: 'Minimum configuration required for Islandora.'
type: module
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
dependencies:
- drupal:basic_auth
- drupal:content_translation
diff --git a/modules/islandora_core_feature/islandora_core_feature.post_update.php b/modules/islandora_core_feature/islandora_core_feature.post_update.php
new file mode 100644
index 000000000..10547231a
--- /dev/null
+++ b/modules/islandora_core_feature/islandora_core_feature.post_update.php
@@ -0,0 +1,20 @@
+getStorage('field_storage_config');
+ $field = $storage->load('node.field_weight');
+ $indexes = $field->getIndexes();
+ $indexes += [
+ 'value' => ['value'],
+ ];
+ $field->setIndexes($indexes);
+ $field->save();
+}
diff --git a/modules/islandora_iiif/README.md b/modules/islandora_iiif/README.md
index ab06524be..c1f89872c 100644
--- a/modules/islandora_iiif/README.md
+++ b/modules/islandora_iiif/README.md
@@ -1,4 +1,4 @@
-# Islandora IIIF
+# Islandora IIIF
[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg?style=flat-square)](https://php.net/)
[![Contribution Guidelines](http://img.shields.io/badge/CONTRIBUTING-Guidelines-blue.svg)](./CONTRIBUTING.md)
@@ -11,7 +11,7 @@ Provides IIIF manifests using views.
## Requirements
- `islandora` and `islandora_core_feature`
-- A IIIF image server (such as Cantaloupe)
+- A IIIF image server (such as Cantaloupe)
## Installation
@@ -32,6 +32,12 @@ You can set the following configuration at `admin/config/islandora/iiif`:
- IIIF Image server location
- The URL to your IIIF image server (without trailing slash).
+### Views Style Plugin
+
+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.
## Documentation
Official documentation is available on the [Islandora 8 documentation site](https://islandora.github.io/documentation/).
diff --git a/modules/islandora_iiif/config/schema/islandora_iiif.schema.yml b/modules/islandora_iiif/config/schema/islandora_iiif.schema.yml
index 6ef42bc4c..f9e870efa 100644
--- a/modules/islandora_iiif/config/schema/islandora_iiif.schema.yml
+++ b/modules/islandora_iiif/config/schema/islandora_iiif.schema.yml
@@ -5,3 +5,14 @@ islandora_iiif.settings:
iiif_server:
type: string
label: 'IIIF Server Url'
+ use_relative_paths:
+ type: boolean
+ label: 'Use relative paths in manifest.'
+
+views.style.iiif_manifest:
+ type: views_style
+ mapping:
+ iiif_tile_field:
+ type: sequence
+ sequence:
+ type: string
diff --git a/modules/islandora_iiif/islandora_iiif.info.yml b/modules/islandora_iiif/islandora_iiif.info.yml
index 0492158a2..39b835c09 100644
--- a/modules/islandora_iiif/islandora_iiif.info.yml
+++ b/modules/islandora_iiif/islandora_iiif.info.yml
@@ -1,8 +1,7 @@
name: 'Islandora IIIF'
type: module
description: 'IIIF support for Islandora'
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
package: Islandora
dependencies:
- drupal:islandora
diff --git a/modules/islandora_iiif/src/Form/IslandoraIIIFConfigForm.php b/modules/islandora_iiif/src/Form/IslandoraIIIFConfigForm.php
index dc750a5a9..a99539a3c 100644
--- a/modules/islandora_iiif/src/Form/IslandoraIIIFConfigForm.php
+++ b/modules/islandora_iiif/src/Form/IslandoraIIIFConfigForm.php
@@ -72,7 +72,18 @@ public function buildForm(array $form, FormStateInterface $form_state) {
'#title' => $this->t('IIIF Image server location'),
'#description' => $this->t('Please enter the image server location without trailing slash. e.g. http://www.example.org/iiif/2.'),
'#default_value' => $config->get('iiif_server'),
+ '#config' => [
+ 'key' => 'islandora_iiif.settings:iiif_server',
+ ],
];
+
+ $form['use_relative_paths'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t("Use relative file paths in manifest."),
+ '#description' => $this->t("Check this if your IIIF Server is configured to access files locally. If unchecked, the absolute URL will be given and the IIIF server will make requests to this site to retrieve images."),
+ '#default_value' => $config->get('use_relative_paths'),
+ ];
+
return parent::buildForm($form, $form_state);
}
@@ -99,6 +110,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('islandora_iiif.settings')
->set('iiif_server', $form_state->getValue('iiif_server'))
+ ->set('use_relative_paths', $form_state->getValue('use_relative_paths'))
->save();
}
diff --git a/modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php b/modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php
index 073ca04e8..804153abf 100644
--- a/modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php
+++ b/modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php
@@ -2,19 +2,24 @@
namespace Drupal\islandora_iiif\Plugin\views\style;
-use Drupal\views\Plugin\views\style\StylePluginBase;
+use Drupal\Core\Config\ImmutableConfig;
+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\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\Core\Url;
+use Drupal\views\Plugin\views\style\StylePluginBase;
use Drupal\views\ResultRow;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\Serializer\SerializerInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Drupal\Core\Config\ImmutableConfig;
-use Drupal\Core\File\FileSystemInterface;
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;
/**
* Provide serializer format for IIIF Manifest.
@@ -68,6 +73,13 @@ class IIIFManifest extends StylePluginBase {
*/
protected $iiifConfig;
+ /**
+ * The Drupal Entity Type Manager service.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
/**
* The Drupal Filesystem.
*
@@ -75,6 +87,13 @@ class IIIFManifest extends StylePluginBase {
*/
protected $fileSystem;
+ /**
+ * The Guzzle HTTP Client.
+ *
+ * @var \GuzzleHttp\Client
+ */
+ protected $httpClient;
+
/**
* The messenger.
*
@@ -82,18 +101,27 @@ class IIIFManifest extends StylePluginBase {
*/
protected $messenger;
+ /**
+ * Module Handler for running hooks.
+ *
+ * @var \Drupal\Core\Extention\ModuleHandlerInterface
+ */
+ protected $moduleHandler;
+
/**
* {@inheritdoc}
*/
- public function __construct(array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, Request $request, ImmutableConfig $iiif_config, FileSystemInterface $file_system, Client $http_client, MessengerInterface $messenger) {
+ 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) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->serializer = $serializer;
$this->request = $request;
$this->iiifConfig = $iiif_config;
+ $this->entityTypeManager = $entity_type_manager;
$this->fileSystem = $file_system;
$this->httpClient = $http_client;
$this->messenger = $messenger;
+ $this->moduleHandler = $moduleHandler;
}
/**
@@ -107,12 +135,24 @@ public static function create(ContainerInterface $container, array $configuratio
$container->get('serializer'),
$container->get('request_stack')->getCurrentRequest(),
$container->get('config.factory')->get('islandora_iiif.settings'),
+ $container->get('entity_type.manager'),
$container->get('file_system'),
$container->get('http_client'),
- $container->get('messenger')
+ $container->get('messenger'),
+ $container->get('module_handler')
);
}
+ /**
+ * Return the request property.
+ *
+ * @return \Symfony\Component\HttpFoundation\Request
+ * The Symfony request object
+ */
+ public function getRequest() {
+ return $this->request;
+ }
+
/**
* {@inheritdoc}
*/
@@ -121,18 +161,21 @@ public function render() {
$iiif_address = $this->iiifConfig->get('iiif_server');
if (!is_null($iiif_address) && !empty($iiif_address)) {
// Get the current URL being requested.
- $request_url = $this->request->getSchemeAndHttpHost() . $this->request->getRequestUri();
+ $request_host = $this->request->getSchemeAndHttpHost();
+ $request_url = $this->request->getRequestUri();
// Strip off the last URI component to get the base ID of the URL.
// @todo assumming the view is a path like /node/1/manifest.json
- $url_components = explode('/', $request_url);
+ $url_components = explode('/', trim($request_url, '/'));
array_pop($url_components);
- $iiif_base_id = implode('/', $url_components);
+ $content_path = implode('/', $url_components);
+ $iiif_base_id = $request_host . '/' . $content_path;
+
// @see https://iiif.io/api/presentation/2.1/#manifest
$json += [
'@type' => 'sc:Manifest',
'@id' => $request_url,
// If the View has a title, set the View title as the manifest label.
- 'label' => $this->view->getTitle() ?: 'IIIF Manifest',
+ 'label' => $this->view->getTitle() ?: $this->getEntityTitle($content_path),
'@context' => 'http://iiif.io/api/presentation/2/context.json',
// @see https://iiif.io/api/presentation/2.1/#sequence
'sequences' => [
@@ -156,6 +199,9 @@ public function render() {
$content_type = 'json';
+ // Give other modules a chance to alter the manifest.
+ $this->moduleHandler->alter('islandora_iiif_manifest', $json, $this);
+
return $this->serializer->serialize($json, $content_type, ['views_style_plugin' => $this]);
}
@@ -175,18 +221,28 @@ public function render() {
*/
protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_base_id) {
$canvases = [];
- foreach ($this->options['iiif_tile_field'] as $iiif_tile_field) {
+ foreach (array_filter(array_values($this->options['iiif_tile_field'])) as $iiif_tile_field) {
$viewsField = $this->view->field[$iiif_tile_field];
$entity = $viewsField->getEntity($row);
if (isset($entity->{$viewsField->definition['field_name']})) {
-
/** @var \Drupal\Core\Field\FieldItemListInterface $images */
$images = $entity->{$viewsField->definition['field_name']};
- foreach ($images as $image) {
+ foreach ($images as $i => $image) {
+ if (!$image->entity->access('view')) {
+ // If the user does not have permission to view the file, skip it.
+ continue;
+ }
+
// Create the IIIF URL for this file
// Visiting $iiif_url will resolve to the info.json for the image.
- $file_url = $image->entity->createFileUrl(FALSE);
+ if ($this->iiifConfig->get('use_relative_paths')) {
+ $file_url = ltrim($image->entity->createFileUrl(TRUE), '/');
+ }
+ else {
+ $file_url = $image->entity->createFileUrl(FALSE);
+ }
+
$mime_type = $image->entity->getMimeType();
$iiif_url = rtrim($iiif_address, '/') . '/' . urlencode($file_url);
@@ -194,37 +250,9 @@ 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();
- // Try to fetch the IIIF metadata for the image.
- try {
- $info_json = $this->httpClient->get($iiif_url)->getBody();
- $resource = json_decode($info_json, TRUE);
- $width = $resource['width'];
- $height = $resource['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];
- }
- }
- }
- }
+ [$width, $height] = $this->getCanvasDimensions($iiif_url, $image, $mime_type);
- $canvases[] = [
+ $tmp_canvas = [
// @see https://iiif.io/api/presentation/2.1/#canvas
'@id' => $canvas_id,
'@type' => 'sc:Canvas',
@@ -253,6 +281,24 @@ protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_bas
],
],
];
+
+ if ($ocr_url = $this->getOcrUrl($entity, $row, $i)) {
+ $tmp_canvas['seeAlso'] = [
+ '@id' => $ocr_url,
+ 'format' => 'text/vnd.hocr+html',
+ 'profile' => 'http://kba.cloud/hocr-spec',
+ 'label' => 'hOCR embedded text',
+ ];
+ }
+
+ // Give other modules a chance to alter the canvas.
+ $alter_options = [
+ 'options' => $this->options,
+ 'views_plugin' => $this,
+ ];
+ $this->moduleHandler->alter('islandora_iiif_manifest_canvas', $tmp_canvas, $row, $alter_options);
+
+ $canvases[] = $tmp_canvas;
}
}
}
@@ -260,6 +306,118 @@ protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_bas
return $canvases;
}
+ /**
+ * Try to fetch the IIIF metadata for the image.
+ *
+ * @param string $iiif_url
+ * Base URL of the canvas.
+ * @param \Drupal\Core\Field\FieldItemInterface $image
+ * The image field.
+ * @param string $mime_type
+ * The mime type of the image.
+ *
+ * @return [string]
+ * The width and height of the image.
+ */
+ protected function getCanvasDimensions(string $iiif_url, 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)];
+ }
+
+ try {
+ $info_json = $this->httpClient->get($iiif_url)->getBody();
+ $resource = json_decode($info_json, TRUE);
+ $width = $resource['width'];
+ $height = $resource['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];
+ }
+ }
+ }
+ }
+ return [$width, $height];
+ }
+
+ /**
+ * Retrieves a URL text with positional data such as hOCR.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * The entity at the current row.
+ * @param \Drupal\views\ResultRow $row
+ * Result row.
+ * @param int $delta
+ * The delta in case there are multiple canvases on one media.
+ *
+ * @return string|false
+ * The absolute URL of the current row's structured text,
+ * or FALSE if none.
+ */
+ protected function getOcrUrl(EntityInterface $entity, ResultRow $row, $delta) {
+ $ocr_url = FALSE;
+ $iiif_ocr_file_field = !empty($this->options['iiif_ocr_file_field']) ? array_filter(array_values($this->options['iiif_ocr_file_field'])) : [];
+ $ocrField = count($iiif_ocr_file_field) > 0 ? $this->view->field[$iiif_ocr_file_field[0]] : NULL;
+ if ($ocrField) {
+ $ocr_entity = $ocrField->getEntity($row);
+ $ocr_field_name = $ocrField->definition['field_name'];
+ if (!is_null($ocr_field_name)) {
+ $ocrs = $ocr_entity->{$ocr_field_name};
+ $ocr = isset($ocrs[$delta]) ? $ocrs[$delta] : FALSE;
+ if ($ocr) {
+ $ocr_url = $ocr->entity->createFileUrl(FALSE);
+ }
+ }
+ }
+
+ return $ocr_url;
+ }
+
+ /**
+ * Pull a title from the node or media passed to this view.
+ *
+ * @param string $content_path
+ * The path of the content being requested.
+ *
+ * @return string
+ * The entity's title.
+ */
+ public function getEntityTitle(string $content_path): string {
+ $entity_title = $this->t('IIIF Manifest');
+ try {
+ $params = Url::fromUserInput($content_path)->getRouteParameters();
+ if (isset($params['node'])) {
+ $node = $this->entityTypeManager->getStorage('node')->load($params['node']);
+ $entity_title = $node->getTitle();
+ }
+ elseif (isset($params['media'])) {
+ $media = $this->entityTypeManager->getStorage('media')->load($params['media']);
+ $entity_title = $media->getName();
+ }
+ }
+ catch (\InvalidArgumentException $e) {
+
+ }
+ return $entity_title;
+ }
+
/**
* {@inheritdoc}
*/
@@ -267,6 +425,7 @@ protected function defineOptions() {
$options = parent::defineOptions();
$options['iiif_tile_field'] = ['default' => ''];
+ $options['iiif_ocr_file_field'] = ['default' => ''];
return $options;
}
@@ -322,6 +481,15 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
// otherwise could lock up the form when setting up a View.
'#required' => count($field_options) > 0,
];
+
+ $form['iiif_ocr_file_field'] = [
+ '#title' => $this->t('Structured OCR data file field'),
+ '#type' => 'checkboxes',
+ '#default_value' => $this->options['iiif_ocr_file_field'],
+ '#description' => $this->t('The source of structured OCR text for each entity.'),
+ '#options' => $field_options,
+ '#required' => FALSE,
+ ];
}
/**
diff --git a/modules/islandora_image/islandora_image.info.yml b/modules/islandora_image/islandora_image.info.yml
index 87076462c..277966f38 100644
--- a/modules/islandora_image/islandora_image.info.yml
+++ b/modules/islandora_image/islandora_image.info.yml
@@ -1,8 +1,7 @@
name: 'Islandora Image'
type: module
description: 'Islandora Image derivative actions'
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
package: Islandora
dependencies:
- drupal:islandora
diff --git a/modules/islandora_image/src/Plugin/Action/GenerateImageDerivativeFile.php b/modules/islandora_image/src/Plugin/Action/GenerateImageDerivativeFile.php
index d6f99b584..a6e067740 100644
--- a/modules/islandora_image/src/Plugin/Action/GenerateImageDerivativeFile.php
+++ b/modules/islandora_image/src/Plugin/Action/GenerateImageDerivativeFile.php
@@ -6,7 +6,10 @@
use Drupal\islandora\Plugin\Action\AbstractGenerateDerivativeMediaFile;
/**
- * Emits a Node for generating derivatives event.
+ * Emits a Media for generating derivatives event.
+ *
+ * Attaches the result as a file in an image field on the emitting
+ * Media ("multi-file media").
*
* @Action(
* id = "generate_image_derivative_file",
@@ -24,7 +27,6 @@ public function defaultConfiguration() {
$config['path'] = '[date:custom:Y]-[date:custom:m]/[media:mid]-ImageService.jpg';
$config['mimetype'] = 'application/xml';
$config['queue'] = 'islandora-connector-houdini';
- $config['destination_media_type'] = 'file';
$config['scheme'] = $this->config->get('default_scheme');
return $config;
}
@@ -34,9 +36,30 @@ public function defaultConfiguration() {
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
- $form['mimetype']['#description'] = $this->t('Mimetype to convert to (e.g. application/xml, etc...)');
+ $map = $this->entityFieldManager->getFieldMapByFieldType('image');
+ $file_fields = $map['media'];
+ $file_options = array_combine(array_keys($file_fields), array_keys($file_fields));
+ $file_options = array_merge(['' => ''], $file_options);
+ // @todo figure out how to write to thumbnail, which is not a real field.
+ // see https://github.com/Islandora/islandora/issues/891.
+ unset($file_options['thumbnail']);
+
+ $form['destination_field_name'] = [
+ '#required' => TRUE,
+ '#type' => 'select',
+ '#options' => $file_options,
+ '#title' => $this->t('Destination Image field'),
+ '#default_value' => $this->configuration['destination_field_name'],
+ '#description' => $this->t('This Action stores the derivative in an
+ Image field. If you are creating a TIFF or JP2, instead use
+ "Generate a Derivative File for Media Attachment". Selected target field
+ must be an additional field, not the media\'s main storage field.
+ Selected target field must be present on the media.'),
+ ];
+
$form['mimetype']['#value'] = 'image/jpeg';
- $form['mimetype']['#type'] = 'hidden';
+ $form['mimetype']['#description'] = 'Mimetype to convert to. Must be
+ compatible with the destination image field.';
return $form;
}
diff --git a/modules/islandora_image/tests/src/Functional/GenerateImageDerivativeTest.php b/modules/islandora_image/tests/src/Functional/GenerateImageDerivativeTest.php
index b6e016fc1..44cdda589 100644
--- a/modules/islandora_image/tests/src/Functional/GenerateImageDerivativeTest.php
+++ b/modules/islandora_image/tests/src/Functional/GenerateImageDerivativeTest.php
@@ -68,9 +68,10 @@ public function testGenerateImageDerivativeFromScratch() {
'name[0][value]' => 'Test Media',
'files[field_media_file_0]' => __DIR__ . '/../../fixtures/test_file.txt',
'field_media_of[0][target_id]' => 'Test Node',
- 'field_tags[0][target_id]' => 'Preservation Master',
+ 'field_media_use[0][target_id]' => $this->preservationMasterTerm->label(),
];
- $this->drupalPostForm('media/add/' . $this->testMediaType->id(), $values, $this->t('Save'));
+ $this->drupalGet('media/add/' . $this->testMediaType->id());
+ $this->submitForm($values, $this->t('Save'));
$expected = [
'source_uri' => 'test_file.txt',
diff --git a/modules/islandora_text_extraction/islandora_text_extraction.info.yml b/modules/islandora_text_extraction/islandora_text_extraction.info.yml
index 67687f901..fb768e2ab 100644
--- a/modules/islandora_text_extraction/islandora_text_extraction.info.yml
+++ b/modules/islandora_text_extraction/islandora_text_extraction.info.yml
@@ -1,8 +1,7 @@
name: 'Islandora Text Extraction'
type: module
description: 'Islandora 8 module to connect to Hypercube microservice, and to get text from PDF ingest'
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
package: 'Islandora'
dependencies:
- drupal:islandora
diff --git a/modules/islandora_text_extraction/islandora_text_extraction.module b/modules/islandora_text_extraction/islandora_text_extraction.module
index 5d6f64370..ca330dd42 100644
--- a/modules/islandora_text_extraction/islandora_text_extraction.module
+++ b/modules/islandora_text_extraction/islandora_text_extraction.module
@@ -40,6 +40,10 @@ function islandora_text_extraction_media_presave(MediaInterface $media) {
$file = File::load($file_id);
if ($file) {
$data = file_get_contents($file->getFileUri());
+ // Check if it's already markup like hOCR.
+ if (substr($data, 0, 5) == 'set('field_edited_text', $data);
$media->field_edited_text->format = 'basic_html';
diff --git a/modules/islandora_text_extraction/src/Controller/MediaSourceController.php b/modules/islandora_text_extraction/src/Controller/MediaSourceController.php
index 14c36ebdb..6b8863084 100644
--- a/modules/islandora_text_extraction/src/Controller/MediaSourceController.php
+++ b/modules/islandora_text_extraction/src/Controller/MediaSourceController.php
@@ -5,6 +5,7 @@
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\File\FileSystem;
use Drupal\Core\File\FileSystemInterface;
+use Drupal\file\FileRepository;
use Drupal\media\Entity\Media;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -42,14 +43,24 @@ class MediaSourceController extends ControllerBase {
*/
protected $fileSystem;
+ /**
+ * File repository service.
+ *
+ * @var \Drupal\file\FileRepository
+ */
+ protected $fileRepository;
+
/**
* MediaSourceController constructor.
*
* @param \Drupal\Core\File\FileSystem $fileSystem
* Filesystem service.
+ * @param \Drupal\file\FileRepository $fileRepository
+ * File Repository service.
*/
- public function __construct(FileSystem $fileSystem) {
+ public function __construct(FileSystem $fileSystem, FileRepository $fileRepository) {
$this->fileSystem = $fileSystem;
+ $this->fileRepository = $fileRepository;
}
/**
@@ -63,7 +74,8 @@ public function __construct(FileSystem $fileSystem) {
*/
public static function create(ContainerInterface $container) {
return new static(
- $container->get('file_system')
+ $container->get('file_system'),
+ $container->get('file.repository'),
);
}
@@ -98,7 +110,7 @@ public function attachToMedia(
if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
throw new HttpException(500, "The destination directory does not exist, could not be created, or is not writable");
}
- $file = file_save_data($contents, $content_location, FileSystemInterface::EXISTS_REPLACE);
+ $file = $this->fileRepository->writeData($contents, $content_location, FileSystemInterface::EXISTS_REPLACE);
if ($media->hasField($destination_field)) {
$media->{$destination_field}->setValue([
'target_id' => $file->id(),
@@ -108,7 +120,11 @@ public function attachToMedia(
$this->getLogger('islandora')->warning("Field $destination_field is not defined in Media Type {$media->bundle()}");
}
if ($media->hasField($destination_text_field)) {
- $media->{$destination_text_field}->setValue(nl2br($contents));
+ // @todo The request actually has a malformed parameter string, ?text_format=plain_text?connection_close=true.
+ if (substr($request->query->get('text_format'), 0, 10) == 'plain_text') {
+ $contents = nl2br($contents);
+ }
+ $media->{$destination_text_field}->setValue($contents);
}
else {
$this->getLogger('islandora')->warning("Field $destination_text_field is not defined in Media Type {$media->bundle()}");
diff --git a/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivative.php b/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivative.php
index 745c57727..272e9f01b 100644
--- a/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivative.php
+++ b/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivative.php
@@ -22,9 +22,13 @@ class GenerateOCRDerivative extends AbstractGenerateDerivative {
public function defaultConfiguration() {
$config = parent::defaultConfiguration();
$config['path'] = '[date:custom:Y]-[date:custom:m]/[node:nid]-[term:name].txt';
- $config['mimetype'] = 'application/xml';
+ $config['event'] = 'Generate Derivative';
+ $config['source_term_uri'] = 'http://pcdm.org/use#OriginalFile';
+ $config['derivative_term_uri'] = 'http://pcdm.org/use#ExtractedText';
+ $config['mimetype'] = 'text/plain';
$config['queue'] = 'islandora-connector-ocr';
- $config['destination_media_type'] = 'file';
+ $config['destination_media_type'] = 'extracted_text';
+ $config['scheme'] = 'fedora';
return $config;
}
@@ -33,11 +37,9 @@ public function defaultConfiguration() {
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
- $form['mimetype']['#description'] = $this->t('Mimetype to convert to (e.g. application/xml, etc...)');
- $form['mimetype']['#value'] = 'text/plain';
- $form['mimetype']['#type'] = 'textfield';
- unset($form['args']);
+ $form['args']['#description'] = $this->t("Arguments to send to Tesseract. To generate hOCR, use:
-c tessedit_create_hocr=1 -c hocr_font_info=0
");
+
return $form;
}
diff --git a/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivativeFile.php b/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivativeFile.php
index 3b5e84980..565d7564c 100644
--- a/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivativeFile.php
+++ b/modules/islandora_text_extraction/src/Plugin/Action/GenerateOCRDerivativeFile.php
@@ -8,11 +8,11 @@
use Drupal\islandora\Plugin\Action\AbstractGenerateDerivativeMediaFile;
/**
- * Emits a Node for generating fits derivatives event.
+ * Generates OCR derivatives event.
*
* @Action(
* id = "generate_extracted_text_file",
- * label = @Translation("Generate an Extracted Text derivative file"),
+ * label = @Translation("Generate Extracted Text for Media Attachment"),
* type = "media"
* )
*/
@@ -29,6 +29,7 @@ public function defaultConfiguration() {
$config['destination_media_type'] = 'file';
$config['scheme'] = $this->config->get('default_scheme');
$config['destination_text_field_name'] = '';
+ $config['text_format'] = 'plain_text';
return $config;
}
@@ -38,7 +39,7 @@ public function defaultConfiguration() {
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$map = $this->entityFieldManager->getFieldMapByFieldType('text_long');
$file_fields = $map['media'];
- $field_options = array_combine(array_keys($file_fields), array_keys($file_fields));
+ $field_options = ['none' => $this->t('None')] + array_combine(array_keys($file_fields), array_keys($file_fields));
$form = parent::buildConfigurationForm($form, $form_state);
$form['mimetype']['#description'] = $this->t('Mimetype to convert to (e.g. application/xml, etc...)');
$form['mimetype']['#value'] = 'text/plain';
@@ -48,13 +49,23 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
$last = array_slice($form, count($form) - $position + 1);
$middle['destination_text_field_name'] = [
- '#required' => TRUE,
+ '#required' => FALSE,
'#type' => 'select',
'#options' => $field_options,
'#title' => $this->t('Destination Text field Name'),
'#default_value' => $this->configuration['destination_text_field_name'],
'#description' => $this->t('Text field on Media Type to hold extracted text.'),
];
+ $middle['text_format'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Format'),
+ '#options' => [
+ 'plain_text' => $this->t('Plain text'),
+ 'hocr' => $this->t('hOCR text with positional data'),
+ ],
+ '#default_value' => $this->configuration['text_format'],
+ '#description' => $this->t("The type of text to be returned."),
+ ];
$form = array_merge($first, $middle, $last);
unset($form['args']);
@@ -81,17 +92,29 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$this->configuration['destination_text_field_name'] = $form_state->getValue('destination_text_field_name');
+ $this->configuration['text_format'] = $form_state->getValue('text_format');
+ switch ($form_state->getValue('text_format')) {
+ case 'hocr':
+ $this->configuration['args'] = '-c tessedit_create_hocr=1 -c hocr_font_info=0';
+ break;
+
+ case 'plain_text':
+ $this->configuration['args'] = '';
+ break;
+ }
}
/**
* Override this to return arbitrary data as an array to be json encoded.
*/
protected function generateData(EntityInterface $entity) {
+
$data = parent::generateData($entity);
$route_params = [
'media' => $entity->id(),
'destination_field' => $this->configuration['destination_field_name'],
'destination_text_field' => $this->configuration['destination_text_field_name'],
+ 'text_format' => $this->configuration['text_format'],
];
$data['destination_uri'] = Url::fromRoute('islandora_text_extraction.attach_file_to_media', $route_params)
->setAbsolute()
diff --git a/modules/islandora_text_extraction/src/Plugin/Field/FieldFormatter/OcrTextFormatter.php b/modules/islandora_text_extraction/src/Plugin/Field/FieldFormatter/OcrTextFormatter.php
index 2e0669437..055e76d97 100644
--- a/modules/islandora_text_extraction/src/Plugin/Field/FieldFormatter/OcrTextFormatter.php
+++ b/modules/islandora_text_extraction/src/Plugin/Field/FieldFormatter/OcrTextFormatter.php
@@ -132,8 +132,9 @@ protected function viewValue(FieldItemInterface $item) {
$fileItem = $item->getValue();
$file = $this->entityTypeManager->getStorage('file')->load($fileItem['target_id']);
$contents = file_get_contents($file->getFileUri());
- if (mb_detect_encoding($contents) != 'UTF-8') {
- $contents = utf8_encode($contents);
+ $detected_encoding = mb_detect_encoding($contents);
+ if ($detected_encoding != 'UTF-8') {
+ $contents = mb_convert_encoding($contents, 'UTF-8', $detected_encoding);
}
$contents = nl2br($contents);
return $contents;
diff --git a/modules/islandora_text_extraction/tests/src/Functional/LoadTest.php b/modules/islandora_text_extraction/tests/src/Functional/LoadTest.php
index 31dca62c8..172ae73ab 100644
--- a/modules/islandora_text_extraction/tests/src/Functional/LoadTest.php
+++ b/modules/islandora_text_extraction/tests/src/Functional/LoadTest.php
@@ -17,7 +17,7 @@ class LoadTest extends IslandoraFunctionalTestBase {
*
* @var array
*/
- public static $modules = ['islandora_text_extraction'];
+ protected static $modules = ['islandora_text_extraction'];
/**
* A user with permission to administer site configuration.
@@ -29,7 +29,7 @@ class LoadTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
$this->user = $this->drupalCreateUser(['administer site configuration']);
$this->drupalLogin($this->user);
diff --git a/modules/islandora_text_extraction_defaults/islandora_text_extraction_defaults.info.yml b/modules/islandora_text_extraction_defaults/islandora_text_extraction_defaults.info.yml
index 5b9aa14f7..8596dbd32 100644
--- a/modules/islandora_text_extraction_defaults/islandora_text_extraction_defaults.info.yml
+++ b/modules/islandora_text_extraction_defaults/islandora_text_extraction_defaults.info.yml
@@ -1,8 +1,7 @@
name: 'Islandora Text Extraction Defaults'
type: module
description: 'Default config for the Islandora Text Extraction module.'
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
package: Islandora
dependencies:
- drupal:field
diff --git a/modules/islandora_video/config/schema/islandora_video.schema.yml b/modules/islandora_video/config/schema/islandora_video.schema.yml
index b1d72d7ff..01d284814 100644
--- a/modules/islandora_video/config/schema/islandora_video.schema.yml
+++ b/modules/islandora_video/config/schema/islandora_video.schema.yml
@@ -29,3 +29,6 @@ action.configuration.generate_video_derivative:
path:
type: text
label: 'File path with extension'
+
+field.formatter.settings.islandora_file_video:
+ type: field.formatter.settings.file_video
diff --git a/modules/islandora_video/islandora_video.info.yml b/modules/islandora_video/islandora_video.info.yml
index 48eb82f2f..aa51af113 100644
--- a/modules/islandora_video/islandora_video.info.yml
+++ b/modules/islandora_video/islandora_video.info.yml
@@ -2,7 +2,6 @@ name: 'Islandora Video'
description: 'Islandora video derivative actions'
type: module
package: Islandora
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^9 || ^10
dependencies:
- drupal:islandora
diff --git a/modules/islandora_video/templates/islandora-file-video.html.twig b/modules/islandora_video/templates/islandora-file-video.html.twig
index 7c62badea..e1b8730c3 100644
--- a/modules/islandora_video/templates/islandora-file-video.html.twig
+++ b/modules/islandora_video/templates/islandora-file-video.html.twig
@@ -21,7 +21,7 @@
{% endfor %}
{% if tracks %}
{% for track in tracks %}
-
{% endfor %}
{% endif %}
diff --git a/modules/islandora_video/tests/src/Functional/GenerateVideoDerivativeTest.php b/modules/islandora_video/tests/src/Functional/GenerateVideoDerivativeTest.php
index 8714a2f10..de06ba2f7 100644
--- a/modules/islandora_video/tests/src/Functional/GenerateVideoDerivativeTest.php
+++ b/modules/islandora_video/tests/src/Functional/GenerateVideoDerivativeTest.php
@@ -63,9 +63,10 @@ public function testGenerateVideoDerivativeFromScratch() {
'name[0][value]' => 'Test Media',
'files[field_media_file_0]' => __DIR__ . '/../../fixtures/test_file.txt',
'field_media_of[0][target_id]' => 'Test Node',
- 'field_tags[0][target_id]' => 'Preservation Master',
+ 'field_media_use[0][target_id]' => $this->preservationMasterTerm->label(),
];
- $this->drupalPostForm('media/add/' . $this->testMediaType->id(), $values, $this->t('Save'));
+ $this->drupalGet('media/add/' . $this->testMediaType->id());
+ $this->submitForm($values, $this->t('Save'));
$expected = [
'source_uri' => 'test_file.txt',
diff --git a/phpunit.xml b/phpunit.xml
index a40917813..46e82e78d 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -58,7 +58,7 @@
../modules/contrib/islandora/tests/src/Functional
- ../modules/contrib/isladnora/modules/*/tests/src/Functional
+ ../modules/contrib/islandora/modules/*/tests/src/Functional
../modules/contrib/islandora/tests/src/FunctionalJavascript
diff --git a/src/Commands/IslandoraCommands.php b/src/Commands/IslandoraCommands.php
index 5209546ca..bd47500a5 100644
--- a/src/Commands/IslandoraCommands.php
+++ b/src/Commands/IslandoraCommands.php
@@ -4,7 +4,7 @@
use Consolidation\AnnotatedCommand\CommandData;
use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Session\AccountProxy;
+use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Session\AccountSwitcherInterface;
use Drupal\Core\Session\UserSession;
use Drush\Commands\DrushCommands;
@@ -39,7 +39,7 @@ class IslandoraCommands extends DrushCommands {
/**
* {@inheritdoc}
*/
- public function __construct(EntityTypeManagerInterface $entity_type_manager, AccountProxy $current_user, AccountSwitcherInterface $account_switcher) {
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, AccountProxyInterface $current_user, AccountSwitcherInterface $account_switcher) {
$this->entityTypeManager = $entity_type_manager;
$this->currentUser = $current_user;
$this->accountSwitcher = $account_switcher;
diff --git a/src/Controller/ManageMediaController.php b/src/Controller/ManageMediaController.php
index 56e0b5c50..025d1d9dc 100644
--- a/src/Controller/ManageMediaController.php
+++ b/src/Controller/ManageMediaController.php
@@ -6,6 +6,7 @@
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\RouteMatch;
use Drupal\node\Entity\Node;
+use Drupal\Core\Url;
use Drupal\node\NodeInterface;
/**
@@ -25,7 +26,7 @@ class ManageMediaController extends ManageMembersController {
public function addToNodePage(NodeInterface $node) {
$field = IslandoraUtils::MEDIA_OF_FIELD;
- return $this->generateTypeList(
+ $add_media_list = $this->generateTypeList(
'media',
'media_type',
'entity.media.add_form',
@@ -33,6 +34,21 @@ public function addToNodePage(NodeInterface $node) {
$field,
['query' => ["edit[$field][widget][0][target_id]" => $node->id()]]
);
+
+ $manage_link = Url::fromRoute('entity.media_type.collection')->toRenderArray();
+ $manage_link['#title'] = $this->t('Manage media types');
+ $manage_link['#type'] = 'link';
+ $manage_link['#prefix'] = ' ';
+ $manage_link['#suffix'] = '.';
+
+ return [
+ '#type' => 'markup',
+ '#markup' => $this->t("The following media types can be added because they have the @field
field.", [
+ '@field' => $field,
+ ]),
+ 'manage_link' => $manage_link,
+ 'add_media' => $add_media_list,
+ ];
}
/**
@@ -45,13 +61,19 @@ public function addToNodePage(NodeInterface $node) {
* Whether we can or can't show the "thing".
*/
public function access(RouteMatch $route_match) {
+ // Route match is being used as opposed to slugs as there are a few
+ // admin routes being altered.
+ // @see: \Drupal\islandora\EventSubscriber\AdminViewsRouteSubscriber::alterRoutes().
if ($route_match->getParameters()->has('node')) {
$node = $route_match->getParameter('node');
if (!$node instanceof NodeInterface) {
$node = Node::load($node);
}
- if ($this->utils->isIslandoraType($node->getEntityTypeId(), $node->bundle())) {
- return AccessResult::allowed();
+ // Ensure there's actually a node before referencing it.
+ if ($node) {
+ if ($this->utils->isIslandoraType($node->getEntityTypeId(), $node->bundle())) {
+ return AccessResult::allowed();
+ }
}
}
return AccessResult::forbidden();
diff --git a/src/Controller/ManageMembersController.php b/src/Controller/ManageMembersController.php
index 7f480fb3d..c7b2df6a4 100644
--- a/src/Controller/ManageMembersController.php
+++ b/src/Controller/ManageMembersController.php
@@ -7,6 +7,7 @@
use Drupal\Core\Entity\Controller\EntityController;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Link;
+use Drupal\Core\Url;
use Drupal\islandora\IslandoraUtils;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -28,7 +29,7 @@ class ManageMembersController extends EntityController {
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
- protected $entityFieldManger;
+ protected $entityFieldManager;
/**
* The renderer.
@@ -88,7 +89,8 @@ public static function create(ContainerInterface $container) {
*/
public function addToNodePage(NodeInterface $node) {
$field = IslandoraUtils::MEMBER_OF_FIELD;
- return $this->generateTypeList(
+
+ $add_node_list = $this->generateTypeList(
'node',
'node_type',
'node.add',
@@ -96,6 +98,21 @@ public function addToNodePage(NodeInterface $node) {
$field,
['query' => ["edit[$field][widget][0][target_id]" => $node->id()]]
);
+
+ $manage_link = Url::fromRoute('entity.node_type.collection')->toRenderArray();
+ $manage_link['#title'] = $this->t('Manage content types');
+ $manage_link['#type'] = 'link';
+ $manage_link['#prefix'] = ' ';
+ $manage_link['#suffix'] = '.';
+
+ return [
+ '#type' => 'markup',
+ '#markup' => $this->t("The following content types can be added because they have the @field
field.", [
+ '@field' => $field,
+ ]),
+ 'manage_link' => $manage_link,
+ 'add_node' => $add_node_list,
+ ];
}
/**
diff --git a/src/Controller/MediaSourceController.php b/src/Controller/MediaSourceController.php
index bab43fcfc..3d0e79092 100644
--- a/src/Controller/MediaSourceController.php
+++ b/src/Controller/MediaSourceController.php
@@ -280,8 +280,7 @@ public function attachToMedia(
*/
public function attachToMediaAccess(AccountInterface $account, RouteMatch $route_match) {
$media = $route_match->getParameter('media');
- $node = $this->utils->getParentNode($media);
- return AccessResult::allowedIf($node->access('update', $account) && $account->hasPermission('create media'));
+ return AccessResult::allowedIf($media->access('update', $account));
}
}
diff --git a/src/Event/StompHeaderEvent.php b/src/Event/StompHeaderEvent.php
index d6d93c22c..1381a9209 100644
--- a/src/Event/StompHeaderEvent.php
+++ b/src/Event/StompHeaderEvent.php
@@ -6,7 +6,7 @@
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
-use Symfony\Component\EventDispatcher\Event;
+use Drupal\Component\EventDispatcher\Event;
/**
* Event used to build headers for STOMP.
diff --git a/src/EventGenerator/EmitEvent.php b/src/EventGenerator/EmitEvent.php
index ebec711f4..12700d732 100644
--- a/src/EventGenerator/EmitEvent.php
+++ b/src/EventGenerator/EmitEvent.php
@@ -5,6 +5,7 @@
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
@@ -13,6 +14,7 @@
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\islandora\Event\StompHeaderEvent;
use Drupal\islandora\Event\StompHeaderEventException;
+use Drupal\islandora\Exception\IslandoraDerivativeException;
use Stomp\Exception\StompException;
use Stomp\StatefulStomp;
use Stomp\Transport\Message;
@@ -67,6 +69,13 @@ abstract class EmitEvent extends ConfigurableActionBase implements ContainerFact
*/
protected $messenger;
+ /**
+ * The logger.
+ *
+ * @var \Drupal\Core\Logger\LoggerChannelInterface
+ */
+ protected $logger;
+
/**
* Constructs a EmitEvent action.
*
@@ -88,6 +97,8 @@ abstract class EmitEvent extends ConfigurableActionBase implements ContainerFact
* The messenger.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* Event dispatcher service.
+ * @param \Drupal\Core\Logger\LoggerChannelInterface $channel
+ * Logger channel.
*/
public function __construct(
array $configuration,
@@ -98,7 +109,8 @@ public function __construct(
EventGeneratorInterface $event_generator,
StatefulStomp $stomp,
MessengerInterface $messenger,
- EventDispatcherInterface $event_dispatcher
+ EventDispatcherInterface $event_dispatcher,
+ LoggerChannelInterface $channel
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->account = $account;
@@ -107,6 +119,7 @@ public function __construct(
$this->stomp = $stomp;
$this->messenger = $messenger;
$this->eventDispatcher = $event_dispatcher;
+ $this->logger = $channel;
}
/**
@@ -122,7 +135,8 @@ public static function create(ContainerInterface $container, array $configuratio
$container->get('islandora.eventgenerator'),
$container->get('islandora.stomp'),
$container->get('messenger'),
- $container->get('event_dispatcher')
+ $container->get('event_dispatcher'),
+ $container->get('logger.channel.islandora')
);
}
@@ -132,12 +146,22 @@ public static function create(ContainerInterface $container, array $configuratio
public function execute($entity = NULL) {
// Generate event as stomp message.
try {
+ if (is_null($this->stomp->getClient()->getProtocol())) {
+ // getProtocol() can return NULL but that causes a larger problem.
+ // So attempt to disconnect + connect to re-establish the connection or
+ // throw a StompException.
+ // @see https://github.com/stomp-php/stomp-php/issues/167
+ // @see https://github.com/stomp-php/stomp-php/blob/3a9347a11743d0b79fd60564f356bc3efe40e615/src/Client.php#L429-L434
+ $this->stomp->getClient()->disconnect();
+ $this->stomp->getClient()->connect();
+ }
+
$user = $this->entityTypeManager->getStorage('user')->load($this->account->id());
$data = $this->generateData($entity);
$event = $this->eventDispatcher->dispatch(
- StompHeaderEvent::EVENT_NAME,
- new StompHeaderEvent($entity, $user, $data, $this->getConfiguration())
+ new StompHeaderEvent($entity, $user, $data, $this->getConfiguration()),
+ StompHeaderEvent::EVENT_NAME
);
$message = new Message(
@@ -145,19 +169,27 @@ public function execute($entity = NULL) {
$event->getHeaders()->all()
);
}
+ catch (IslandoraDerivativeException $e) {
+ $this->logger->info($e->getMessage());
+ return;
+ }
catch (StompHeaderEventException $e) {
- \Drupal::logger('islandora')->error($e->getMessage());
- $this->messenger->addMessage($e->getMessage(), 'error');
+ $this->logger->error($e->getMessage());
+ $this->messenger->addError($e->getMessage());
+ return;
+ }
+ catch (StompException $e) {
+ $this->logger->error("Unable to connect to JMS Broker: @msg", ["@msg" => $e->getMessage()]);
+ $this->messenger->addWarning("Unable to connect to JMS Broker, items might not be synchronized to external services.");
return;
}
catch (\RuntimeException $e) {
// Notify the user the event couldn't be generated and abort.
- \Drupal::logger('islandora')->error(
+ $this->logger->error(
$this->t('Error generating event: @msg', ['@msg' => $e->getMessage()])
);
- $this->messenger->addMessage(
- $this->t('Error generating event: @msg', ['@msg' => $e->getMessage()]),
- 'error'
+ $this->messenger->addError(
+ $this->t('Error generating event: @msg', ['@msg' => $e->getMessage()])
);
return;
}
@@ -170,17 +202,16 @@ public function execute($entity = NULL) {
}
catch (StompException $e) {
// Log it.
- \Drupal::logger('islandora')->error(
+ $this->logger->error(
'Error publishing message: @msg',
['@msg' => $e->getMessage()]
);
// Notify user.
- $this->messenger->addMessage(
+ $this->messenger->addError(
$this->t('Error publishing message: @msg',
['@msg' => $e->getMessage()]
- ),
- 'error'
+ )
);
}
}
diff --git a/src/EventGenerator/EventGenerator.php b/src/EventGenerator/EventGenerator.php
index 6b3a4c5cf..4c29e44ba 100644
--- a/src/EventGenerator/EventGenerator.php
+++ b/src/EventGenerator/EventGenerator.php
@@ -147,8 +147,19 @@ public function generateEvent(EntityInterface $entity, UserInterface $user, arra
}
}
- unset($data["event"]);
- unset($data["queue"]);
+ $allowed_keys = [
+ "file_upload_uri",
+ "fedora_uri",
+ "source_uri",
+ "destination_uri",
+ "args",
+ "mimetype",
+ "source_field",
+ ];
+ $keys_to_unset = array_diff(array_keys($data), $allowed_keys);
+ foreach ($keys_to_unset as $key) {
+ unset($data[$key]);
+ }
if (!empty($data)) {
$event["attachment"] = [
@@ -192,6 +203,7 @@ protected function isNewRevision(EntityInterface $entity) {
protected function getRevisionIds(Media $media, EntityStorageInterface $media_storage) {
$result = $media_storage->getQuery()
->allRevisions()
+ ->accessCheck(TRUE)
->condition($media->getEntityType()->getKey('id'), $media->id())
->sort($media->getEntityType()->getKey('revision'), 'DESC')
->execute();
diff --git a/src/EventSubscriber/LinkHeaderSubscriber.php b/src/EventSubscriber/LinkHeaderSubscriber.php
index ce33ce2e1..f7e5725b8 100644
--- a/src/EventSubscriber/LinkHeaderSubscriber.php
+++ b/src/EventSubscriber/LinkHeaderSubscriber.php
@@ -2,6 +2,7 @@
namespace Drupal\islandora\EventSubscriber;
+use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
@@ -13,7 +14,6 @@
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
@@ -312,9 +312,9 @@ protected function generateRestLinks(EntityInterface $entity) {
/**
* Adds resource-specific link headers to appropriate responses.
*
- * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+ * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
* Event containing the response.
*/
- abstract public function onResponse(FilterResponseEvent $event);
+ abstract public function onResponse(ResponseEvent $event);
}
diff --git a/src/EventSubscriber/MediaLinkHeaderSubscriber.php b/src/EventSubscriber/MediaLinkHeaderSubscriber.php
index 3cebbbaae..0f406cf57 100644
--- a/src/EventSubscriber/MediaLinkHeaderSubscriber.php
+++ b/src/EventSubscriber/MediaLinkHeaderSubscriber.php
@@ -2,10 +2,10 @@
namespace Drupal\islandora\EventSubscriber;
+use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Drupal\Core\Url;
use Drupal\media\MediaInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
/**
* Subscribes to MediaLinkHeader Event.
@@ -17,7 +17,7 @@ class MediaLinkHeaderSubscriber extends LinkHeaderSubscriber implements EventSub
/**
* {@inheritdoc}
*/
- public function onResponse(FilterResponseEvent $event) {
+ public function onResponse(ResponseEvent $event) {
$response = $event->getResponse();
$media = $this->getObject($response, 'media');
diff --git a/src/EventSubscriber/NodeLinkHeaderSubscriber.php b/src/EventSubscriber/NodeLinkHeaderSubscriber.php
index e00533f7a..c4cdaea8c 100644
--- a/src/EventSubscriber/NodeLinkHeaderSubscriber.php
+++ b/src/EventSubscriber/NodeLinkHeaderSubscriber.php
@@ -2,9 +2,9 @@
namespace Drupal\islandora\EventSubscriber;
+use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Drupal\node\NodeInterface;
use Drupal\islandora\IslandoraUtils;
-use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
@@ -17,10 +17,10 @@ class NodeLinkHeaderSubscriber extends LinkHeaderSubscriber implements EventSubs
/**
* Adds node-specific link headers to appropriate responses.
*
- * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+ * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
* Event containing the response.
*/
- public function onResponse(FilterResponseEvent $event) {
+ public function onResponse(ResponseEvent $event) {
$response = $event->getResponse();
$node = $this->getObject($response, 'node');
diff --git a/src/Exception/IslandoraDerivativeException.php b/src/Exception/IslandoraDerivativeException.php
new file mode 100644
index 000000000..7efe47736
--- /dev/null
+++ b/src/Exception/IslandoraDerivativeException.php
@@ -0,0 +1,11 @@
+fedora = $fedora;
$this->mimeTypeGuesser = $mime_type_guesser;
+ $this->logger = $logger;
}
/**
@@ -143,14 +159,8 @@ protected function getMetadataFromHeaders(Response $response) {
// NonRDFSource's are considered files. Everything else is a
// directory.
$type = 'dir';
- // phpcs:disable
- if (class_exists(\GuzzleHttp\Psr7\Header::class)) {
- $links = \GuzzleHttp\Psr7\Header::parse($response->getHeader('Link'));
- }
- else {
- $links = \GuzzleHttp\Psr7\parse_header($response->getHeader('Link'));
- }
- // phpcs:enable
+ $links = Header::parse($response->getHeader('Link'));
+
foreach ($links as $link) {
if ($link['rel'] == 'type' && $link[0] == '') {
$type = 'file';
@@ -259,7 +269,7 @@ protected function transformToMetadata($uri) {
*/
public function write($path, $contents, Config $config) {
$headers = [
- 'Content-Type' => $this->mimeTypeGuesser->guess($path),
+ 'Content-Type' => $this->mimeTypeGuesser->guessMimeType($path),
];
if ($this->has($path)) {
$fedora_url = $path;
@@ -274,17 +284,17 @@ public function write($path, $contents, Config $config) {
$headers
);
if (isset($response) && $response->getStatusCode() == 201) {
- \Drupal::logger('fedora_flysystem')->info('Created a version in Fedora for ' . $fedora_url);
+ $this->logger->info('Created a version in Fedora for ' . $fedora_url);
}
else {
- \Drupal::logger('fedora_flysystem')->error(
+ $this->logger->error(
"Client error: `Failed to create a Fedora version of $fedora_url`. Response is " . print_r($response, TRUE)
);
}
}
catch (\Exception $e) {
- \Drupal::logger('fedora_flysystem')->error('Caught exception when creating version: ' . $e->getMessage() . "\n");
+ $this->logger->error('Caught exception when creating version: ' . $e->getMessage() . "\n");
}
}
@@ -386,14 +396,7 @@ private function deleteTombstone($path) {
$return = NULL;
if ($response->getStatusCode() == 410) {
$return = FALSE;
- // phpcs:disable
- if (class_exists(\GuzzleHttp\Psr7\Header::class)) {
- $link_headers = \GuzzleHttp\Psr7\Header::parse($response->getHeader('Link'));
- }
- else {
- $link_headers = \GuzzleHttp\Psr7\parse_header($response->getHeader('Link'));
- }
- // phpcs:enable
+ $link_headers = Header::parse($response->getHeader('Link'));
if ($link_headers) {
$tombstones = array_filter($link_headers, function ($o) {
return (isset($o['rel']) && $o['rel'] == 'hasTombstone');
diff --git a/src/Flysystem/Fedora.php b/src/Flysystem/Fedora.php
index fe7af7bae..0cbf1a12a 100644
--- a/src/Flysystem/Fedora.php
+++ b/src/Flysystem/Fedora.php
@@ -3,6 +3,7 @@
namespace Drupal\islandora\Flysystem;
use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
@@ -12,12 +13,11 @@
use Drupal\jwt\Authentication\Provider\JwtAuth;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\HandlerStack;
-use GuzzleHttp\Client;
-use Islandora\Chullo\IFedoraApi;
use Islandora\Chullo\FedoraApi;
+use Islandora\Chullo\IFedoraApi;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
/**
* Drupal plugin for the Fedora Flysystem adapter.
@@ -38,7 +38,7 @@ class Fedora implements FlysystemPluginInterface, ContainerFactoryPluginInterfac
/**
* Mimetype guesser.
*
- * @var \Symfony\Component\HttpFoundation\File\Mimetype\MimeTypeGuesserInterface
+ * @var \Symfony\Component\Mime\MimeTypeGuesserInterface
*/
protected $mimeTypeGuesser;
@@ -49,24 +49,35 @@ class Fedora implements FlysystemPluginInterface, ContainerFactoryPluginInterfac
*/
protected $languageManager;
+ /**
+ * Logger.
+ *
+ * @var \Drupal\Core\Logger\LoggerChannelInterface
+ */
+ protected $logger;
+
/**
* Constructs a Fedora plugin for Flysystem.
*
* @param \Islandora\Chullo\IFedoraApi $fedora
* Fedora client.
- * @param \Symfony\Component\HttpFoundation\File\Mimetype\MimeTypeGuesserInterface $mime_type_guesser
+ * @param \Symfony\Component\Mime\MimeTypeGuesserInterface $mime_type_guesser
* Mimetype guesser.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* Language manager.
+ * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
+ * The fedora adapter logger channel.
*/
public function __construct(
IFedoraApi $fedora,
MimeTypeGuesserInterface $mime_type_guesser,
- LanguageManagerInterface $language_manager
+ LanguageManagerInterface $language_manager,
+ LoggerChannelInterface $logger
) {
$this->fedora = $fedora;
$this->mimeTypeGuesser = $mime_type_guesser;
$this->languageManager = $language_manager;
+ $this->logger = $logger;
}
/**
@@ -77,17 +88,14 @@ public static function create(ContainerInterface $container, array $configuratio
// Construct guzzle client to middleware that adds JWT.
$stack = HandlerStack::create();
$stack->push(static::addJwt($container->get('jwt.authentication.jwt')));
- $client = new Client([
- 'handler' => $stack,
- 'base_uri' => $configuration['root'],
- ]);
- $fedora = new FedoraApi($client);
+ $fedora = FedoraApi::createWithHandler($configuration['root'], $stack);
// Return it.
return new static(
$fedora,
$container->get('file.mime_type.guesser'),
- $container->get('language_manager')
+ $container->get('language_manager'),
+ $container->get('logger.channel.fedora_flysystem')
);
}
@@ -116,7 +124,7 @@ public static function addJwt(JwtAuth $jwt) {
* {@inheritdoc}
*/
public function getAdapter() {
- return new FedoraAdapter($this->fedora, $this->mimeTypeGuesser);
+ return new FedoraAdapter($this->fedora, $this->mimeTypeGuesser, $this->logger);
}
/**
diff --git a/src/Form/AddChildrenForm.php b/src/Form/AddChildrenForm.php
index 0ff724962..528b42832 100644
--- a/src/Form/AddChildrenForm.php
+++ b/src/Form/AddChildrenForm.php
@@ -229,7 +229,7 @@ public function buildNodeFinished($success, $results, $operations) {
* @param \Drupal\Core\Routing\RouteMatch $route_match
* The current routing match.
*
- * @return \Drupal\Core\Access\AccessResultAllowed|\Drupal\Core\Access\AccessResultForbidden
+ * @return \Drupal\Core\Access\AccessResultInterface
* Whether we can or can't show the "thing".
*/
public function access(RouteMatch $route_match) {
diff --git a/src/Form/AddChildrenWizard/AbstractBatchProcessor.php b/src/Form/AddChildrenWizard/AbstractBatchProcessor.php
new file mode 100644
index 000000000..6193c0c30
--- /dev/null
+++ b/src/Form/AddChildrenWizard/AbstractBatchProcessor.php
@@ -0,0 +1,258 @@
+entityTypeManager = $entity_type_manager;
+ $this->database = $database;
+ $this->currentUser = $current_user;
+ $this->messenger = $messenger;
+ $this->dateFormatter = $date_formatter;
+ }
+
+ /**
+ * Implements callback_batch_operation() for our child addition batch.
+ */
+ public function batchOperation($delta, $info, array $values, &$context) {
+ $transaction = $this->database->startTransaction();
+
+ try {
+ $entities[] = $node = $this->getNode($info, $values);
+ $entities[] = $this->createMedia($node, $info, $values);
+
+ $context['results'] = array_merge_recursive($context['results'], [
+ 'validation_violations' => $this->validationClassification($entities),
+ ]);
+ $context['results']['count'] = ($context['results']['count'] ?? 0) + 1;
+ }
+ catch (HttpExceptionInterface $e) {
+ $transaction->rollBack();
+ throw $e;
+ }
+ catch (\Exception $e) {
+ $transaction->rollBack();
+ throw new HttpException(500, $e->getMessage(), $e);
+ }
+ }
+
+ /**
+ * Loads the file indicated.
+ *
+ * @param mixed $info
+ * Widget values.
+ *
+ * @return \Drupal\file\FileInterface|null
+ * The loaded file.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ protected function getFile($info) : ?FileInterface {
+ return (is_array($info) && isset($info['target_id'])) ?
+ $this->entityTypeManager->getStorage('file')->load($info['target_id']) :
+ NULL;
+ }
+
+ /**
+ * Get the node to which to attach our media.
+ *
+ * @param mixed $info
+ * Info from the widget used to create the request.
+ * @param array $values
+ * Additional form inputs.
+ *
+ * @return \Drupal\node\NodeInterface
+ * The node to which to attach the created media.
+ */
+ abstract protected function getNode($info, array $values) : NodeInterface;
+
+ /**
+ * Get a name to use for bulk-created assets.
+ *
+ * @param mixed $info
+ * Widget values.
+ * @param array $values
+ * Form values.
+ *
+ * @return string
+ * An applicable name.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ protected function getName($info, array $values) : string {
+ $file = $this->getFile($info);
+ return $file ? $file->getFilename() : strtr('Bulk ingest, {date}', [
+ '{date}' => $this->dateFormatter->format(time(), 'long'),
+ ]);
+ }
+
+ /**
+ * Create a media referencing the given file, associated with the given node.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * The node to which the media should be associated.
+ * @param mixed $info
+ * The widget info for the media source field.
+ * @param array $values
+ * Values from the wizard, which should contain at least:
+ * - media_type: The machine name/ID of the media type as which to create
+ * the media
+ * - use: An array of the selected "media use" terms.
+ *
+ * @return \Drupal\media\MediaInterface
+ * The created media entity.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ */
+ protected function createMedia(NodeInterface $node, $info, array $values) : MediaInterface {
+ $taxonomy_term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
+
+ // Create a media with the file attached and also pointing at the node.
+ $field = $this->getField($values);
+
+ $media_values = array_merge(
+ [
+ 'bundle' => $values['media_type'],
+ 'name' => $this->getName($info, $values),
+ IslandoraUtils::MEDIA_OF_FIELD => $node,
+ IslandoraUtils::MEDIA_USAGE_FIELD => ($values['use'] ?
+ $taxonomy_term_storage->loadMultiple($values['use']) :
+ NULL),
+ 'uid' => $this->currentUser->id(),
+ // XXX: Published... no constant?
+ 'status' => 1,
+ ],
+ [
+ $field->getName() => [
+ $info,
+ ],
+ ]
+ );
+ $media = $this->entityTypeManager->getStorage('media')->create($media_values);
+ if ($media->save() !== SAVED_NEW) {
+ throw new \Exception("Failed to create media.");
+ }
+
+ return $media;
+ }
+
+ /**
+ * Helper to bulk process validatable entities.
+ *
+ * @param array $entities
+ * An array of entities to scan for validation violations.
+ *
+ * @return array
+ * An associative array mapping entity type IDs to entity IDs to a count
+ * of validation violations found on then given entity.
+ */
+ protected function validationClassification(array $entities) {
+ $violations = [];
+
+ foreach ($entities as $entity) {
+ $entity_violations = $entity->validate();
+ if ($entity_violations->count() > 0) {
+ $violations[$entity->getEntityTypeId()][$entity->id()] = $entity_violations->count();
+ }
+ }
+
+ return $violations;
+ }
+
+ /**
+ * Implements callback_batch_finished() for our child addition batch.
+ */
+ public function batchProcessFinished($success, $results, $operations): void {
+ if ($success) {
+ foreach ($results['validation_violations'] ?? [] as $entity_type => $info) {
+ foreach ($info as $id => $count) {
+ $this->messenger->addWarning($this->formatPlural(
+ $count,
+ '1 validation error present in bulk created entity of type %type, with ID %id.',
+ '@count validation errors present in bulk created entity of type %type, with ID %id.',
+ [
+ '%type' => $entity_type,
+ ':uri' => Url::fromRoute("entity.{$entity_type}.canonical", [$entity_type => $id])->toString(),
+ '%id' => $id,
+ ]
+ ));
+ }
+ }
+ }
+ else {
+ $this->messenger->addError($this->t('Encountered an error when processing.'));
+ }
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/AbstractFileSelectionForm.php b/src/Form/AddChildrenWizard/AbstractFileSelectionForm.php
new file mode 100644
index 000000000..6aeed8795
--- /dev/null
+++ b/src/Form/AddChildrenWizard/AbstractFileSelectionForm.php
@@ -0,0 +1,157 @@
+entityTypeManager = $container->get('entity_type.manager');
+ $instance->widgetPluginManager = $container->get('plugin.manager.field.widget');
+ $instance->entityFieldManager = $container->get('entity_field.manager');
+ $instance->currentUser = $container->get('current_user');
+
+ $instance->batchProcessor = $container->get(static::BATCH_PROCESSOR);
+
+ return $instance;
+ }
+
+ /**
+ * Helper; get the media type, based off discovering from form state.
+ *
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ *
+ * @return \Drupal\media\MediaTypeInterface
+ * The target media type.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ protected function getMediaTypeFromFormState(FormStateInterface $form_state): MediaTypeInterface {
+ return $this->getMediaType($form_state->getTemporaryValue('wizard'));
+ }
+
+ /**
+ * Helper; get field instance, based off discovering from form state.
+ *
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ *
+ * @return \Drupal\Core\Field\FieldDefinitionInterface
+ * The field definition.
+ */
+ protected function getFieldFromFormState(FormStateInterface $form_state): FieldDefinitionInterface {
+ $cached_values = $form_state->getTemporaryValue('wizard');
+
+ $field = $this->getField($cached_values);
+ $def = $field->getFieldStorageDefinition();
+ if ($def instanceof FieldStorageConfigInterface) {
+ $def->set('cardinality', FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+ }
+ elseif ($def instanceof BaseFieldDefinition) {
+ $def->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+ }
+ else {
+ throw new \Exception('Unable to remove cardinality limit.');
+ }
+
+ return $field;
+ }
+
+ /**
+ * Helper; get widget for the field, based on discovering from form state.
+ *
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ *
+ * @return \Drupal\Core\Field\WidgetInterface
+ * The widget.
+ */
+ protected function getWidgetFromFormState(FormStateInterface $form_state): WidgetInterface {
+ return $this->getWidget($this->getFieldFromFormState($form_state));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state): array {
+ // Using the media type selected in the previous step, grab the
+ // media bundle's "source" field, and create a multi-file upload widget
+ // for it, with the same kind of constraints.
+ $field = $this->getFieldFromFormState($form_state);
+ $items = FieldItemList::createInstance($field, $field->getName(), $this->getMediaTypeFromFormState($form_state)->getTypedData());
+
+ $form['#tree'] = TRUE;
+ $form['#parents'] = [];
+ $widget = $this->getWidgetFromFormState($form_state);
+ $form['files'] = $widget->form(
+ $items,
+ $form,
+ $form_state
+ );
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $cached_values = $form_state->getTemporaryValue('wizard');
+
+ $widget = $this->getWidgetFromFormState($form_state);
+ $builder = (new BatchBuilder())
+ ->setTitle($this->t('Bulk creating...'))
+ ->setInitMessage($this->t('Initializing...'))
+ ->setFinishCallback([$this->batchProcessor, 'batchProcessFinished']);
+ $values = $form_state->getValue($this->getField($cached_values)->getName());
+ $massaged_values = $widget->massageFormValues($values, $form, $form_state);
+ foreach ($massaged_values as $delta => $info) {
+ $builder->addOperation(
+ [$this->batchProcessor, 'batchOperation'],
+ [$delta, $info, $cached_values]
+ );
+ }
+ batch_set($builder->toArray());
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/AbstractForm.php b/src/Form/AddChildrenWizard/AbstractForm.php
new file mode 100644
index 000000000..e9fac3875
--- /dev/null
+++ b/src/Form/AddChildrenWizard/AbstractForm.php
@@ -0,0 +1,125 @@
+nodeId = $this->routeMatch->getParameter('node');
+ $this->currentUser = $current_user;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getParameters() : array {
+ return array_merge(
+ parent::getParameters(),
+ [
+ 'tempstore_id' => static::TEMPSTORE_ID,
+ 'current_user' => \Drupal::service('current_user'),
+ ]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOperations($cached_values) {
+ $ops = [];
+
+ $ops['type_selection'] = [
+ 'title' => $this->t('Type Selection'),
+ 'form' => static::TYPE_SELECTION_FORM,
+ 'values' => [
+ 'node' => $this->nodeId,
+ ],
+ ];
+ $ops['file_selection'] = [
+ 'title' => $this->t('Widget Input for Selected Type'),
+ 'form' => static::FILE_SELECTION_FORM,
+ 'values' => [
+ 'node' => $this->nodeId,
+ ],
+ ];
+
+ return $ops;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNextParameters($cached_values) {
+ return parent::getNextParameters($cached_values) + ['node' => $this->nodeId];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPreviousParameters($cached_values) {
+ return parent::getPreviousParameters($cached_values) + ['node' => $this->nodeId];
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/Access.php b/src/Form/AddChildrenWizard/Access.php
new file mode 100644
index 000000000..0adafde51
--- /dev/null
+++ b/src/Form/AddChildrenWizard/Access.php
@@ -0,0 +1,71 @@
+utils = $utils;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) : self {
+ return new static(
+ $container->get('islandora.utils')
+ );
+ }
+
+ /**
+ * Check if the user can create any "Islandora" nodes and media.
+ *
+ * @param \Drupal\Core\Routing\RouteMatch $route_match
+ * The current routing match.
+ *
+ * @return \Drupal\Core\Access\AccessResultInterface
+ * Whether we can or cannot show the "thing".
+ */
+ public function childAccess(RouteMatch $route_match) : AccessResultInterface {
+ return AccessResult::allowedIf($this->utils->canCreateIslandoraEntity('node', 'node_type'))
+ ->andIf($this->mediaAccess($route_match));
+
+ }
+
+ /**
+ * Check if the user can create any "Islandora" media.
+ *
+ * @param \Drupal\Core\Routing\RouteMatch $route_match
+ * The current routing match.
+ *
+ * @return \Drupal\Core\Access\AccessResultInterface
+ * Whether we can or cannot show the "thing".
+ */
+ public function mediaAccess(RouteMatch $route_match) : AccessResultInterface {
+ return AccessResult::allowedIf($this->utils->canCreateIslandoraEntity('media', 'media_type'));
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/ChildBatchProcessor.php b/src/Form/AddChildrenWizard/ChildBatchProcessor.php
new file mode 100644
index 000000000..084e7816e
--- /dev/null
+++ b/src/Form/AddChildrenWizard/ChildBatchProcessor.php
@@ -0,0 +1,57 @@
+entityTypeManager->getStorage('taxonomy_term');
+ $node_storage = $this->entityTypeManager->getStorage('node');
+ $parent = $node_storage->load($values['node']);
+
+ // Create a node (with the filename?) (and also belonging to the target
+ // node).
+ /** @var \Drupal\node\NodeInterface $node */
+ $node = $node_storage->create([
+ 'type' => $values['bundle'],
+ 'title' => $this->getName($info, $values),
+ IslandoraUtils::MEMBER_OF_FIELD => $parent,
+ 'uid' => $this->currentUser->id(),
+ 'status' => NodeInterface::PUBLISHED,
+ IslandoraUtils::MODEL_FIELD => ($values['model'] ?
+ $taxonomy_term_storage->load($values['model']) :
+ NULL),
+ ]);
+
+ if ($node->save() !== SAVED_NEW) {
+ throw new \Exception("Failed to create node.");
+ }
+
+ return $node;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function batchProcessFinished($success, $results, $operations): void {
+ if ($success) {
+ $this->messenger->addMessage($this->formatPlural(
+ $results['count'],
+ 'Added 1 child node.',
+ 'Added @count child nodes.'
+ ));
+ }
+
+ parent::batchProcessFinished($success, $results, $operations);
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/ChildFileSelectionForm.php b/src/Form/AddChildrenWizard/ChildFileSelectionForm.php
new file mode 100644
index 000000000..9783d0823
--- /dev/null
+++ b/src/Form/AddChildrenWizard/ChildFileSelectionForm.php
@@ -0,0 +1,32 @@
+getTemporaryValue('wizard');
+ $form_state->setRedirectUrl(Url::fromUri("internal:/node/{$cached_values['node']}/members"));
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/ChildForm.php b/src/Form/AddChildrenWizard/ChildForm.php
new file mode 100644
index 000000000..0b9a197f4
--- /dev/null
+++ b/src/Form/AddChildrenWizard/ChildForm.php
@@ -0,0 +1,24 @@
+ $this->currentUser->id(),
+ '{nodeid}' => $this->nodeId,
+ ]);
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/ChildTypeSelectionForm.php b/src/Form/AddChildrenWizard/ChildTypeSelectionForm.php
new file mode 100644
index 000000000..f57959971
--- /dev/null
+++ b/src/Form/AddChildrenWizard/ChildTypeSelectionForm.php
@@ -0,0 +1,157 @@
+nodeBundleOptions === NULL) {
+ $this->nodeBundleOptions = [];
+ $this->nodeBundleHasModelField = [];
+
+ $access_handler = $this->entityTypeManager->getAccessControlHandler('node');
+ foreach ($this->entityTypeBundleInfo->getBundleInfo('node') as $bundle => $info) {
+ $access = $access_handler->createAccess(
+ $bundle,
+ NULL,
+ [],
+ TRUE
+ );
+ $this->cacheableMetadata->addCacheableDependency($access);
+ if (!$access->isAllowed()) {
+ continue;
+ }
+ $this->nodeBundleOptions[$bundle] = $info['label'];
+ $fields = $this->entityFieldManager->getFieldDefinitions('node', $bundle);
+ $this->nodeBundleHasModelField[$bundle] = array_key_exists(IslandoraUtils::MODEL_FIELD, $fields);
+ }
+ }
+
+ return $this->nodeBundleOptions;
+ }
+
+ /**
+ * Generates a mapping of taxonomy term IDs to their names.
+ *
+ * @return \Generator
+ * The mapping of taxonomy term IDs to their names.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ protected function getModelOptions() : \Generator {
+ $terms = $this->entityTypeManager->getStorage('taxonomy_term')
+ ->loadTree('islandora_models', 0, NULL, TRUE);
+ foreach ($terms as $term) {
+ yield $term->id() => $term->getName();
+ }
+ }
+
+ /**
+ * Helper; map node bundles supporting the "has model" field, for #states.
+ *
+ * @return \Generator
+ * Yields associative array mapping the string 'value' to the bundles which
+ * have the given field.
+ */
+ protected function mapModelStates() : \Generator {
+ $this->getNodeBundleOptions();
+ foreach (array_keys(array_filter($this->nodeBundleHasModelField)) as $bundle) {
+ yield ['value' => $bundle];
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ $this->cacheableMetadata = CacheableMetadata::createFromRenderArray($form)
+ ->addCacheContexts([
+ 'url',
+ 'url.query_args',
+ ]);
+ $cached_values = $form_state->getTemporaryValue('wizard');
+
+ $form['bundle'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Content Type'),
+ '#description' => $this->t('Each child created will have this content type.'),
+ '#empty_value' => '',
+ '#default_value' => $cached_values['bundle'] ?? '',
+ '#options' => $this->getNodeBundleOptions(),
+ '#required' => TRUE,
+ ];
+
+ $model_states = iterator_to_array($this->mapModelStates());
+ $form['model'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Model'),
+ '#description' => $this->t('Each child will be tagged with this model.'),
+ '#options' => iterator_to_array($this->getModelOptions()),
+ '#empty_value' => '',
+ '#default_value' => $cached_values['model'] ?? '',
+ '#states' => [
+ 'visible' => [
+ ':input[name="bundle"]' => $model_states,
+ ],
+ 'required' => [
+ ':input[name="bundle"]' => $model_states,
+ ],
+ ],
+ ];
+
+ $this->cacheableMetadata->applyTo($form);
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static function keysToSave() : array {
+ return array_merge(
+ parent::keysToSave(),
+ [
+ 'bundle',
+ 'model',
+ ]
+ );
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/FieldTrait.php b/src/Form/AddChildrenWizard/FieldTrait.php
new file mode 100644
index 000000000..830f95cda
--- /dev/null
+++ b/src/Form/AddChildrenWizard/FieldTrait.php
@@ -0,0 +1,66 @@
+getMediaType($values);
+ $media_source = $media_type->getSource();
+ $source_field = $media_source->getSourceFieldDefinition($media_type);
+
+ $fields = $this->entityFieldManager()->getFieldDefinitions('media', $media_type->id());
+
+ return $fields[$source_field->getFieldStorageDefinition()->getName()] ??
+ $media_source->createSourceField($media_type);
+ }
+
+ /**
+ * Lazy-initialization of the entity field manager service.
+ *
+ * @return \Drupal\Core\Entity\EntityFieldManagerInterface
+ * The entity field manager service.
+ */
+ protected function entityFieldManager() : EntityFieldManagerInterface {
+ if ($this->entityFieldManager === NULL) {
+ $this->setEntityFieldManager(\Drupal::service('entity_field.manager'));
+ }
+ return $this->entityFieldManager;
+ }
+
+ /**
+ * Setter for entity field manager.
+ */
+ public function setEntityFieldManager(EntityFieldManagerInterface $entity_field_manager) : self {
+ $this->entityFieldManager = $entity_field_manager;
+ return $this;
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/MediaBatchProcessor.php b/src/Form/AddChildrenWizard/MediaBatchProcessor.php
new file mode 100644
index 000000000..9a54f03b6
--- /dev/null
+++ b/src/Form/AddChildrenWizard/MediaBatchProcessor.php
@@ -0,0 +1,34 @@
+entityTypeManager->getStorage('node')->load($values['node']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function batchProcessFinished($success, $results, $operations): void {
+ if ($success) {
+ $this->messenger->addMessage($this->formatPlural(
+ $results['count'],
+ 'Added 1 media.',
+ 'Added @count media.'
+ ));
+ }
+
+ parent::batchProcessFinished($success, $results, $operations);
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/MediaFileSelectionForm.php b/src/Form/AddChildrenWizard/MediaFileSelectionForm.php
new file mode 100644
index 000000000..534c73093
--- /dev/null
+++ b/src/Form/AddChildrenWizard/MediaFileSelectionForm.php
@@ -0,0 +1,32 @@
+getTemporaryValue('wizard');
+ $form_state->setRedirectUrl(Url::fromUri("internal:/node/{$cached_values['node']}/media"));
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/MediaForm.php b/src/Form/AddChildrenWizard/MediaForm.php
new file mode 100644
index 000000000..2e6fa2177
--- /dev/null
+++ b/src/Form/AddChildrenWizard/MediaForm.php
@@ -0,0 +1,24 @@
+ $this->currentUser->id(),
+ '{nodeid}' => $this->nodeId,
+ ]);
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/MediaTypeSelectionForm.php b/src/Form/AddChildrenWizard/MediaTypeSelectionForm.php
new file mode 100644
index 000000000..b06d004dc
--- /dev/null
+++ b/src/Form/AddChildrenWizard/MediaTypeSelectionForm.php
@@ -0,0 +1,227 @@
+entityTypeBundleInfo = $container->get('entity_type.bundle.info');
+ $instance->entityTypeManager = $container->get('entity_type.manager');
+ $instance->entityFieldManager = $container->get('entity_field.manager');
+ $instance->utils = $container->get('islandora.utils');
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() : string {
+ return 'islandora_add_media_type_selection';
+ }
+
+ /**
+ * Memoization for ::getMediaBundleOptions().
+ *
+ * @var array|null
+ */
+ protected ?array $mediaBundleOptions = NULL;
+
+ /**
+ * Indicate presence of usage field on media bundles.
+ *
+ * Populated as a side effect in ::getMediaBundleOptions().
+ *
+ * @var array|null
+ */
+ protected ?array $mediaBundleUsageField = NULL;
+
+ /**
+ * Helper; get options for media types.
+ *
+ * @return array
+ * An associative array mapping the machine name of the media type to its
+ * human-readable label.
+ */
+ protected function getMediaBundleOptions() : array {
+ if ($this->mediaBundleOptions === NULL) {
+ $this->mediaBundleOptions = [];
+ $this->mediaBundleUsageField = [];
+
+ $access_handler = $this->entityTypeManager->getAccessControlHandler('media');
+ foreach ($this->entityTypeBundleInfo->getBundleInfo('media') as $bundle => $info) {
+ if (!$this->utils->isIslandoraType('media', $bundle)) {
+ continue;
+ }
+ $access = $access_handler->createAccess(
+ $bundle,
+ NULL,
+ [],
+ TRUE
+ );
+ $this->cacheableMetadata->addCacheableDependency($access);
+ if (!$access->isAllowed()) {
+ continue;
+ }
+ $this->mediaBundleOptions[$bundle] = $info['label'];
+ $fields = $this->entityFieldManager->getFieldDefinitions('media', $bundle);
+ $this->mediaBundleUsageField[$bundle] = array_key_exists(IslandoraUtils::MEDIA_USAGE_FIELD, $fields);
+ }
+ }
+
+ return $this->mediaBundleOptions;
+ }
+
+ /**
+ * Helper; list the terms of the "islandora_media_use" vocabulary.
+ *
+ * @return \Generator
+ * Generates term IDs as keys mapping to term names.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ protected function getMediaUseOptions() : \Generator {
+ /** @var \Drupal\taxonomy\TermInterface[] $terms */
+ $terms = $this->entityTypeManager->getStorage('taxonomy_term')
+ ->loadTree('islandora_media_use', 0, NULL, TRUE);
+
+ foreach ($terms as $term) {
+ yield $term->id() => $term->getName();
+ }
+ }
+
+ /**
+ * Helper; map media types supporting the usage field for use with #states.
+ *
+ * @return \Generator
+ * Yields associative array mapping the string 'value' to the bundles which
+ * have the given field.
+ */
+ protected function mapUseStates(): \Generator {
+ $this->getMediaBundleOptions();
+ foreach (array_keys(array_filter($this->mediaBundleUsageField)) as $bundle) {
+ yield ['value' => $bundle];
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ $this->cacheableMetadata = CacheableMetadata::createFromRenderArray($form)
+ ->addCacheContexts([
+ 'url',
+ 'url.query_args',
+ ]);
+ $cached_values = $form_state->getTemporaryValue('wizard');
+
+ $form['media_type'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Media Type'),
+ '#description' => $this->t('Each media created will have this type.'),
+ '#empty_value' => '',
+ '#default_value' => $cached_values['media_type'] ?? '',
+ '#options' => $this->getMediaBundleOptions(),
+ '#required' => TRUE,
+ ];
+ $use_states = iterator_to_array($this->mapUseStates());
+ $form['use'] = [
+ '#type' => 'checkboxes',
+ '#title' => $this->t('Usage'),
+ '#description' => $this->t('Defined by Portland Common Data Model: Use Extension. "Original File" will trigger creation of derivatives.', [
+ ':url' => 'https://pcdm.org/2015/05/12/use',
+ ]),
+ '#options' => iterator_to_array($this->getMediaUseOptions()),
+ '#default_value' => $cached_values['use'] ?? [],
+ '#states' => [
+ 'visible' => [
+ ':input[name="media_type"]' => $use_states,
+ ],
+ 'required' => [
+ ':input[name="media_type"]' => $use_states,
+ ],
+ ],
+ ];
+
+ $this->cacheableMetadata->applyTo($form);
+ return $form;
+ }
+
+ /**
+ * Helper; enumerate keys to persist in form state.
+ *
+ * @return string[]
+ * The keys to be persisted in our temp value in form state.
+ */
+ protected static function keysToSave() : array {
+ return [
+ 'media_type',
+ 'use',
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $cached_values = $form_state->getTemporaryValue('wizard');
+ foreach (static::keysToSave() as $key) {
+ $cached_values[$key] = $form_state->getValue($key);
+ }
+ $form_state->setTemporaryValue('wizard', $cached_values);
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/MediaTypeTrait.php b/src/Form/AddChildrenWizard/MediaTypeTrait.php
new file mode 100644
index 000000000..36cf6ff2a
--- /dev/null
+++ b/src/Form/AddChildrenWizard/MediaTypeTrait.php
@@ -0,0 +1,58 @@
+entityTypeManager()->getStorage('media_type')->load($values['media_type']);
+ }
+
+ /**
+ * Lazy-initialization of the entity type manager service.
+ *
+ * @return \Drupal\Core\Entity\EntityTypeManagerInterface
+ * The entity type manager service.
+ */
+ protected function entityTypeManager() : EntityTypeManagerInterface {
+ if ($this->entityTypeManager === NULL) {
+ $this->setEntityTypeManager(\Drupal::service('entity_type.manager'));
+ }
+ return $this->entityTypeManager;
+ }
+
+ /**
+ * Setter for the entity type manager service.
+ */
+ public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) : self {
+ $this->entityTypeManager = $entity_type_manager;
+ return $this;
+ }
+
+}
diff --git a/src/Form/AddChildrenWizard/WizardTrait.php b/src/Form/AddChildrenWizard/WizardTrait.php
new file mode 100644
index 000000000..dd56450fa
--- /dev/null
+++ b/src/Form/AddChildrenWizard/WizardTrait.php
@@ -0,0 +1,40 @@
+widgetPluginManager->getInstance([
+ 'field_definition' => $field,
+ 'form_mode' => 'default',
+ 'prepare' => TRUE,
+ ]);
+ }
+
+}
diff --git a/src/Form/ConfirmDeleteMediaAndFile.php b/src/Form/ConfirmDeleteMediaAndFile.php
index 64c1bff3c..a9613871f 100644
--- a/src/Form/ConfirmDeleteMediaAndFile.php
+++ b/src/Form/ConfirmDeleteMediaAndFile.php
@@ -128,6 +128,9 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
// Check for files.
$fields = $this->entityFieldManager->getFieldDefinitions('media', $entity->bundle());
foreach ($fields as $field) {
+ if ($field->getName() == 'thumbnail') {
+ continue;
+ }
$type = $field->getType();
if ($type == 'file' || $type == 'image') {
$target_id = $entity->get($field->getName())->target_id;
@@ -137,8 +140,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$inaccessible_entities[] = $file;
continue;
}
- $delete_files[$file->id()] = $file;
- $total_count++;
+ if (!array_key_exists($file->id(), $delete_files)) {
+ $delete_files[$file->id()] = $file;
+ $total_count++;
+ }
+
}
}
}
diff --git a/src/Form/IslandoraSettingsForm.php b/src/Form/IslandoraSettingsForm.php
index 0ebc0b3cf..ad4c83b3e 100644
--- a/src/Form/IslandoraSettingsForm.php
+++ b/src/Form/IslandoraSettingsForm.php
@@ -2,8 +2,10 @@
namespace Drupal\islandora\Form;
+use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Site\Settings;
@@ -39,6 +41,9 @@ class IslandoraSettingsForm extends ConfigFormBase {
'month',
'year',
];
+ const GEMINI_PSEUDO_FIELD = 'field_gemini_uri';
+ const NODE_DELETE_MEDIA_AND_FILES = 'delete_media_and_files';
+ const REDIRECT_AFTER_MEDIA_SAVE = 'redirect_after_media_save';
/**
* To list the available bundle types.
@@ -54,6 +59,13 @@ class IslandoraSettingsForm extends ConfigFormBase {
*/
private $brokerPassword;
+ /**
+ * The entity type manager service.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ private $entityTypeManager;
+
/**
* Constructs a \Drupal\system\ConfigFormBase object.
*
@@ -61,14 +73,18 @@ class IslandoraSettingsForm extends ConfigFormBase {
* The factory for configuration objects.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The EntityTypeBundleInfo service.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The EntityTypeManager service.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
- EntityTypeBundleInfoInterface $entity_type_bundle_info
+ EntityTypeBundleInfoInterface $entity_type_bundle_info,
+ EntityTypeManagerInterface $entity_type_manager
) {
$this->setConfigFactory($config_factory);
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->brokerPassword = $this->config(self::CONFIG_NAME)->get(self::BROKER_PASSWORD);
+ $this->entityTypeManager = $entity_type_manager;
}
/**
@@ -78,6 +94,7 @@ public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('entity_type.bundle.info'),
+ $container->get('entity_type.manager')
);
}
@@ -112,6 +129,9 @@ public function buildForm(array $form, FormStateInterface $form_state) {
'#type' => 'textfield',
'#title' => $this->t('URL'),
'#default_value' => $config->get(self::BROKER_URL),
+ '#config' => [
+ 'key' => 'islandora.settings:' . self::BROKER_URL,
+ ],
];
$broker_user = $config->get(self::BROKER_USER);
$form['broker_info']['provide_user_creds'] = [
@@ -132,6 +152,9 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$state_selector => ['checked' => TRUE],
],
],
+ '#config' => [
+ 'key' => 'islandora.settings:' . self::BROKER_USER,
+ ],
];
$form['broker_info'][self::BROKER_PASSWORD] = [
'#type' => 'password',
@@ -142,6 +165,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$state_selector => ['checked' => TRUE],
],
],
+ '#config' => [
+ 'key' => 'islandora.settings:' . self::BROKER_PASSWORD,
+ 'secret' => TRUE,
+ ],
];
$form[self::JWT_EXPIRY] = [
'#type' => 'textfield',
@@ -186,10 +213,29 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$fedora_url = NULL;
}
+ $form[self::NODE_DELETE_MEDIA_AND_FILES] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Node Delete with Media and Files'),
+ '#description' => $this->t('Adds a checkbox in the "Delete" tab of islandora objects to delete media and files associated with the object.'
+ ),
+ '#default_value' => (bool) $config->get(self::NODE_DELETE_MEDIA_AND_FILES),
+ ];
+
+ $form[self::REDIRECT_AFTER_MEDIA_SAVE] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Redirect after media save.'),
+ '#description' => $this->t('Redirect to node-specific media list after creation of media.'),
+ '#default_value' => (bool) $config->get(self::REDIRECT_AFTER_MEDIA_SAVE),
+ ];
+
$form[self::FEDORA_URL] = [
'#type' => 'textfield',
'#title' => $this->t('Fedora URL'),
- '#attributes' => ['readonly' => 'readonly'],
+ '#description' => $this->t('Read-only. This value is set in settings.php as the URL for the Fedora flysystem.'),
+ '#attributes' => [
+ 'readonly' => 'readonly',
+ 'disabled' => 'disabled',
+ ],
'#default_value' => $fedora_url,
];
@@ -308,7 +354,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
public function submitForm(array &$form, FormStateInterface $form_state) {
$config = $this->configFactory->getEditable(self::CONFIG_NAME);
- $pseudo_types = array_filter($form_state->getValue(self::GEMINI_PSEUDO));
+ $new_pseudo_types = array_filter($form_state->getValue(self::GEMINI_PSEUDO));
$broker_password = $form_state->getValue(self::BROKER_PASSWORD);
@@ -326,15 +372,59 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
}
}
+ // Check for types being unset and remove the field from them first.
+ $current_pseudo_types = $config->get(self::GEMINI_PSEUDO);
+ $this->updateEntityViewConfiguration($current_pseudo_types, $new_pseudo_types);
+
$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_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_PSEUDO, $pseudo_types)
+ ->set(self::GEMINI_PSEUDO, $new_pseudo_types)
+ ->set(self::NODE_DELETE_MEDIA_AND_FILES, $form_state->getValue(self::NODE_DELETE_MEDIA_AND_FILES))
+ ->set(self::REDIRECT_AFTER_MEDIA_SAVE, $form_state->getValue(self::REDIRECT_AFTER_MEDIA_SAVE))
->save();
parent::submitForm($form, $form_state);
}
+ /**
+ * Removes the Fedora URI field from entity bundles that have be unselected.
+ *
+ * @param array $current_config
+ * The current set of entity types & bundles to have the pseudo field,
+ * format {bundle}:{entity_type}.
+ * @param array $new_config
+ * The new set of entity types & bundles to have the pseudo field, format
+ * {bundle}:{entity_type}.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ */
+ private function updateEntityViewConfiguration(array $current_config, array $new_config) {
+ $removed = array_diff($current_config, $new_config);
+ $added = array_diff($new_config, $current_config);
+ $entity_view_display = $this->entityTypeManager->getStorage('entity_view_display');
+ foreach ($removed as $bundle_type) {
+ [$bundle, $type_id] = explode(":", $bundle_type);
+ $results = $entity_view_display->getQuery()
+ ->condition('bundle', $bundle)
+ ->condition('targetEntityType', $type_id)
+ ->exists('content.' . self::GEMINI_PSEUDO_FIELD . '.region')
+ ->execute();
+ $entities = $entity_view_display->loadMultiple($results);
+ foreach ($entities as $entity) {
+ $entity->removeComponent(self::GEMINI_PSEUDO_FIELD);
+ $entity->save();
+ }
+ }
+ if (count($removed) > 0 || count($added) > 0) {
+ // If we added or cleared a type then clear the extra_fields cache.
+ // @see Drupal/Core/Entity/EntityFieldManager::getExtraFields
+ Cache::invalidateTags(["entity_field_info"]);
+ }
+ }
+
}
diff --git a/src/IslandoraContextManager.php b/src/IslandoraContextManager.php
index 801e3253e..9fd93fbc2 100644
--- a/src/IslandoraContextManager.php
+++ b/src/IslandoraContextManager.php
@@ -13,6 +13,14 @@
*/
class IslandoraContextManager extends ContextManager {
+ /**
+ * Allow the contexts to be reset before evaluation.
+ */
+ protected function resetContextEvaluation() {
+ $this->contexts = [];
+ $this->contextConditionsEvaluated = FALSE;
+ }
+
/**
* Evaluate all context conditions.
*
@@ -22,7 +30,11 @@ class IslandoraContextManager extends ContextManager {
public function evaluateContexts(array $provided = []) {
$this->activeContexts = [];
-
+ // XXX: Ensure that no earlier executed contexts in the request are still
+ // present when being triggered via Islandora's ContextProviders.
+ if (!empty($provided)) {
+ $this->resetContextEvaluation();
+ }
/** @var \Drupal\context\ContextInterface $context */
foreach ($this->getContexts() as $context) {
if ($this->evaluateContextConditions($context, $provided) && !$context->disabled()) {
@@ -48,7 +60,11 @@ public function evaluateContextConditions(ContextInterface $context, array $prov
$conditions = $context->getConditions();
// Apply context to any context aware conditions.
- $this->applyContexts($conditions, $provided);
+ // Abort if the application of contexts has been unsuccessful
+ // similarly to BlockAccessControlHandler::checkAccess().
+ if (!$this->applyContexts($conditions, $provided)) {
+ return FALSE;
+ }
// Set the logic to use when validating the conditions.
$logic = $context->requiresAllConditions()
@@ -76,6 +92,13 @@ public function evaluateContextConditions(ContextInterface $context, array $prov
* TRUE if conditions pass
*/
protected function applyContexts(ConditionPluginCollection &$conditions, array $provided = []) {
+
+ // If no contexts to check, the return should be TRUE.
+ // For example, empty is the same as sitewide condition.
+ if (count($conditions) === 0) {
+ return TRUE;
+ }
+ $passed = FALSE;
foreach ($conditions as $condition) {
if ($condition instanceof ContextAwarePluginInterface) {
try {
@@ -86,14 +109,15 @@ protected function applyContexts(ConditionPluginCollection &$conditions, array $
$contexts = $provided;
}
$this->contextHandler->applyContextMapping($condition, $contexts);
+ $passed = TRUE;
}
catch (ContextException $e) {
- return FALSE;
+ continue;
}
}
}
- return TRUE;
+ return $passed;
}
}
diff --git a/src/IslandoraUtils.php b/src/IslandoraUtils.php
index a4dda6c12..a2df75896 100644
--- a/src/IslandoraUtils.php
+++ b/src/IslandoraUtils.php
@@ -148,6 +148,7 @@ public function getMedia(NodeInterface $node) {
return [];
}
$mids = $this->entityTypeManager->getStorage('media')->getQuery()
+ ->accessCheck(TRUE)
->condition(self::MEDIA_OF_FIELD, $node->id())
->execute();
if (empty($mids)) {
@@ -208,6 +209,7 @@ function ($field) {
// Query for media that reference this file.
$query = $this->entityTypeManager->getStorage('media')->getQuery();
+ $query->accessCheck(TRUE);
$group = $query->orConditionGroup();
foreach ($conditions as $condition) {
$group->condition($condition, $fid);
@@ -252,6 +254,7 @@ public function getTermForUri($uri) {
}
$results = $query
+ ->accessCheck(TRUE)
->condition($orGroup)
->execute();
@@ -498,6 +501,7 @@ public function getMediaReferencingNodeAndTerm(NodeInterface $node, TermInterfac
array_walk($node_fields, $remove_entity);
$query = $this->entityTypeManager->getStorage('media')->getQuery();
+ $query->accessCheck(TRUE);
$taxon_condition = $this->getEntityQueryOrCondition($query, $term_fields, $term->id());
$query->condition($taxon_condition);
$node_condition = $this->getEntityQueryOrCondition($query, $node_fields, $node->id());
@@ -672,4 +676,83 @@ public function canCreateIslandoraEntity($entity_type, $bundle_type) {
return FALSE;
}
+ /**
+ * Recursively finds ancestors of an entity.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity being checked.
+ * @param array $fields
+ * An optional array where the values are the field names to be used for
+ * retrieval.
+ * @param int|bool $max_height
+ * How many levels of checking should be done when retrieving ancestors.
+ *
+ * @return array
+ * An array where the keys and values are the node IDs of the ancestors.
+ */
+ public function findAncestors(ContentEntityInterface $entity, array $fields = [self::MEMBER_OF_FIELD], $max_height = FALSE): array {
+ // XXX: If a negative integer is passed assume it's false.
+ if ($max_height < 0) {
+ $max_height = FALSE;
+ }
+ $context = [
+ 'max_height' => $max_height,
+ 'ancestors' => [],
+ ];
+ $this->findAncestorsByEntityReference($entity, $context, $fields);
+ return $context['ancestors'];
+ }
+
+ /**
+ * Helper that builds up the ancestors.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity being checked.
+ * @param array $context
+ * An array containing:
+ * -ancestors: The ancestors that have been found.
+ * -max_height: How far up the chain to go.
+ * @param array $fields
+ * An optional array where the values are the field names to be used for
+ * retrieval.
+ * @param int $current_height
+ * The current height of the recursion.
+ */
+ protected function findAncestorsByEntityReference(ContentEntityInterface $entity, array &$context, array $fields = [self::MEMBER_OF_FIELD], int $current_height = 1): void {
+ $parents = $this->getParentsByEntityReference($entity, $fields);
+ foreach ($parents as $parent) {
+ if (isset($context['ancestors'][$parent->id()])) {
+ continue;
+ }
+ $context['ancestors'][$parent->id()] = $parent->id();
+ if ($context['max_height'] === FALSE || $current_height < $context['max_height']) {
+ $this->findAncestorsByEntityReference($parent, $context, $fields, $current_height + 1);
+ }
+ }
+ }
+
+ /**
+ * Helper that gets the immediate parents of a node.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity being checked.
+ * @param array $fields
+ * An array where the values are the field names to be used.
+ *
+ * @return array
+ * An array of entity objects keyed by field item deltas.
+ */
+ protected function getParentsByEntityReference(ContentEntityInterface $entity, array $fields): array {
+ $parents = [];
+ foreach ($fields as $field) {
+ if ($entity->hasField($field)) {
+ $reference_field = $entity->get($field);
+ if (!$reference_field->isEmpty()) {
+ $parents = array_merge($parents, $reference_field->referencedEntities());
+ }
+ }
+ }
+ return $parents;
+ }
+
}
diff --git a/src/MediaSource/MediaSourceService.php b/src/MediaSource/MediaSourceService.php
index 9399e3343..533789857 100644
--- a/src/MediaSource/MediaSourceService.php
+++ b/src/MediaSource/MediaSourceService.php
@@ -3,6 +3,7 @@
namespace Drupal\islandora\MediaSource;
use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountInterface;
@@ -113,8 +114,12 @@ public function getSourceFieldName($media_type) {
* @param \Drupal\media\MediaInterface $media
* Media whose source field you are searching for.
*
- * @return \Drupal\file\FileInterface
- * File if it exists
+ * @return \Drupal\file\FileInterface|\Drupal\Core\Entity\EntityInterface|false|null
+ * The first source entity if there is one, generally expected to be of
+ * \Drupal\file\FileInterface. Boolean FALSE if there was no such entity.
+ * NULL if the source field does not refer to Drupal entities (as in, the
+ * field is not a \Drupal\Core\Field\EntityReferenceFieldItemListInterface
+ * implementation).
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
@@ -127,10 +132,13 @@ public function getSourceFile(MediaInterface $media) {
}
// Get the file from the media.
- $files = $media->get($source_field)->referencedEntities();
- $file = reset($files);
+ $source_list = $media->get($source_field);
+ if ($source_list instanceof EntityReferenceFieldItemListInterface) {
+ $files = $source_list->referencedEntities();
+ return reset($files);
+ }
- return $file;
+ return NULL;
}
/**
@@ -268,8 +276,8 @@ public function putToNode(
'uri' => $content_location,
'filename' => $this->fileSystem->basename($content_location),
'filemime' => $mimetype,
- 'status' => FILE_STATUS_PERMANENT,
]);
+ $file->setPermanent();
// Validate file extension.
$source_field_config = $this->entityTypeManager->getStorage('field_config')->load("media.$bundle.$source_field");
@@ -349,8 +357,8 @@ public function putToMedia(
'uri' => $content_location,
'filename' => $this->fileSystem->basename($content_location),
'filemime' => $mimetype,
- 'status' => FILE_STATUS_PERMANENT,
]);
+ $file->setPermanent();
// Validate file extension.
$bundle = $media->bundle();
diff --git a/src/Plugin/Action/AbstractGenerateDerivative.php b/src/Plugin/Action/AbstractGenerateDerivative.php
index b22201e11..b44db477b 100644
--- a/src/Plugin/Action/AbstractGenerateDerivative.php
+++ b/src/Plugin/Action/AbstractGenerateDerivative.php
@@ -5,6 +5,7 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
+use Drupal\islandora\Exception\IslandoraDerivativeException;
/**
* Emits a Node event.
@@ -60,6 +61,13 @@ protected function generateData(EntityInterface $entity) {
throw new \RuntimeException("Could not locate taxonomy term with uri: " . $this->configuration['derivative_term_uri'], 500);
}
+ // See if there is a destination media already set, and abort if it's the
+ // same as the source media. Dont cause an error, just don't continue.
+ $derivative_media = $this->utils->getMediaWithTerm($entity, $derivative_term);
+ if (!is_null($derivative_media) && $derivative_media->id() == $source_media->id()) {
+ throw new IslandoraDerivativeException("Halting derivative, as source and target media are the same. Derivative term: [" . $this->configuration['derivative_term_uri'] . "] Source term: [" . $this->configuration['source_term_uri'] . "] Node id: [" . $entity->id() . "].", 500);
+ }
+
$route_params = [
'node' => $entity->id(),
'media_type' => $this->configuration['destination_media_type'],
diff --git a/src/Plugin/Action/AbstractGenerateDerivativeBase.php b/src/Plugin/Action/AbstractGenerateDerivativeBase.php
index 3c7ff5b68..9d0520a32 100644
--- a/src/Plugin/Action/AbstractGenerateDerivativeBase.php
+++ b/src/Plugin/Action/AbstractGenerateDerivativeBase.php
@@ -5,6 +5,7 @@
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\islandora\IslandoraUtils;
@@ -94,6 +95,8 @@ class AbstractGenerateDerivativeBase extends EmitEvent {
* Field Manager service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* Event dispatcher service.
+ * @param \Drupal\Core\Logger\LoggerChannelInterface $channel
+ * The logger channel.
*/
public function __construct(
array $configuration,
@@ -109,7 +112,8 @@ public function __construct(
MessengerInterface $messenger,
ConfigFactoryInterface $config,
EntityFieldManagerInterface $entity_field_manager,
- EventDispatcherInterface $event_dispatcher
+ EventDispatcherInterface $event_dispatcher,
+ LoggerChannelInterface $channel
) {
$this->utils = $utils;
$this->mediaSource = $media_source;
@@ -126,7 +130,8 @@ public function __construct(
$event_generator,
$stomp,
$messenger,
- $event_dispatcher
+ $event_dispatcher,
+ $channel
);
}
@@ -148,7 +153,8 @@ public static function create(ContainerInterface $container, array $configuratio
$container->get('messenger'),
$container->get('config.factory'),
$container->get('entity_field.manager'),
- $container->get('event_dispatcher')
+ $container->get('event_dispatcher'),
+ $container->get('logger.channel.islandora')
);
}
diff --git a/src/Plugin/Action/AbstractGenerateDerivativeMediaFile.php b/src/Plugin/Action/AbstractGenerateDerivativeMediaFile.php
index 84c064e92..f0974b0c0 100644
--- a/src/Plugin/Action/AbstractGenerateDerivativeMediaFile.php
+++ b/src/Plugin/Action/AbstractGenerateDerivativeMediaFile.php
@@ -7,7 +7,10 @@
use Drupal\Core\Url;
/**
- * Emits a Node for generating derivatives event.
+ * Emits a Media for generating derivatives event.
+ *
+ * Attaches the result as a file in a file field on the emitting
+ * Media ("multi-file media").
*
* @Action(
* id = "generate_derivative_file",
@@ -40,7 +43,7 @@ public function defaultConfiguration() {
protected function generateData(EntityInterface $entity) {
$data = parent::generateData($entity);
if (get_class($entity) != 'Drupal\media\Entity\Media') {
- return;
+ throw new \RuntimeException("Entity {$entity->getEntityTypeId()} {$entity->id()} is not a media", 500);
}
$source_file = $this->mediaSource->getSourceFile($entity);
if (!$source_file) {
@@ -88,19 +91,33 @@ protected function generateData(EntityInterface $entity) {
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
+
$map = $this->entityFieldManager->getFieldMapByFieldType('file');
$file_fields = $map['media'];
$file_options = array_combine(array_keys($file_fields), array_keys($file_fields));
- $file_options = array_merge(['' => ''], $file_options);
+
+ $map = $this->entityFieldManager->getFieldMapByFieldType('image');
+ $image_fields = $map['media'];
+ $image_options = array_combine(array_keys($image_fields), array_keys($image_fields));
+
+ $file_options = array_merge(['' => ''], $file_options, $image_options);
+
+ // @todo figure out how to write to thumbnail, which is not a real field.
+ // see https://github.com/Islandora/islandora/issues/891.
+ unset($file_options['thumbnail']);
+
$form['event']['#disabled'] = 'disabled';
$form['destination_field_name'] = [
'#required' => TRUE,
'#type' => 'select',
'#options' => $file_options,
- '#title' => $this->t('Destination File field Name'),
+ '#title' => $this->t('Destination File field'),
'#default_value' => $this->configuration['destination_field_name'],
- '#description' => $this->t('File field on Media Type to hold derivative. Cannot be the same as source'),
+ '#description' => $this->t('This Action stores a derivative file
+ in a File or Image field on a media. The destination field
+ must be an additional field, not the media\'s main storage field.
+ Selected destination field must be present on the media.'),
];
$form['args'] = [
diff --git a/src/Plugin/Condition/NodeHasAncestor.php b/src/Plugin/Condition/NodeHasAncestor.php
new file mode 100644
index 000000000..ad6f1b99e
--- /dev/null
+++ b/src/Plugin/Condition/NodeHasAncestor.php
@@ -0,0 +1,171 @@
+entityTypeManager = $entity_type_manager;
+ $this->utils = $islandora_utils;
+ }
+
+ /**
+ * {@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('islandora.utils')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration(): array {
+ return parent::defaultConfiguration() + [
+ 'ancestor_nids' => FALSE,
+ 'parent_reference_field' => IslandoraUtils::MEMBER_OF_FIELD,
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $default_nids = FALSE;
+ if ($this->configuration['ancestor_nids']) {
+ $default_nids = array_map(function ($nid) {
+ return $this->entityTypeManager->getStorage('node')->load($nid);
+ }, $this->configuration['ancestor_nids']);
+ }
+ $form['ancestor_nids'] = [
+ '#type' => 'entity_autocomplete',
+ '#title' => $this->t('Parent node(s)'),
+ '#default_value' => $default_nids,
+ '#required' => FALSE,
+ '#description' => $this->t("Can be a collection node, compound object or paged content. Accepts multiple values separated by a comma."),
+ '#target_type' => 'node',
+ '#tags' => TRUE,
+ ];
+
+ $options = [];
+ $reference_fields = $this->entityTypeManager->getStorage('field_storage_config')->loadByProperties([
+ 'type' => 'entity_reference',
+ 'settings' => [
+ 'target_type' => 'node',
+ ],
+ ]);
+ foreach ($reference_fields as $field) {
+ $options[$field->get('field_name')] = $field->get('field_name');
+ }
+ $form['parent_reference_field'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Direct parent reference'),
+ '#options' => $options,
+ '#default_value' => $this->configuration['parent_reference_field'],
+ '#required' => TRUE,
+ '#description' => $this->t('Field that contains the reference to its parent node.'),
+ ];
+
+ return parent::buildConfigurationForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ // Entity autocomplete store things with target IDs, for convenience just
+ // store the plain nid.
+ if (!empty($form_state->getValue('ancestor_nids'))) {
+ $this->configuration['ancestor_nids'] = array_map(function ($nid) {
+ return $nid['target_id'];
+ }, $form_state->getValue('ancestor_nids'));
+ }
+ $this->configuration['parent_reference_field'] = $form_state->getValue('parent_reference_field');
+ parent::submitConfigurationForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function evaluate() {
+ if (empty($this->configuration['ancestor_nids']) && !$this->isNegated()) {
+ return TRUE;
+ }
+
+ $node = $this->getContextValue('node');
+ if (!$node) {
+ return FALSE;
+ }
+
+ $ancestors = $this->utils->findAncestors($node);
+ return !empty(array_intersect($this->configuration['ancestor_nids'], $ancestors));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function summary() {
+ if (!empty($this->configuration['negate'])) {
+ return $this->t('The node does not have node @nid as one of its ancestors.', ['@nid' => $this->configuration['ancestor_nids']]);
+ }
+ else {
+ return $this->t('The node has node @nid as one of its ancestors.', ['@nid' => $this->configuration['ancestor_nids']]);
+ }
+ }
+
+}
diff --git a/src/Plugin/Condition/NodeIsIslandoraObject.php b/src/Plugin/Condition/NodeIsIslandoraObject.php
index 6448cfdcb..06ff62596 100644
--- a/src/Plugin/Condition/NodeIsIslandoraObject.php
+++ b/src/Plugin/Condition/NodeIsIslandoraObject.php
@@ -5,13 +5,14 @@
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Islandora\IslandoraUtils;
/**
* Checks whether node has fields that qualify it as an "Islandora" node.
*
* @Condition(
* id = "node_is_islandora_object",
- * label = @Translation("Node is an Islandora object"),
+ * label = @Translation("Node is an Islandora node"),
* context_definitions = {
* "node" = @ContextDefinition("entity:node", required = TRUE , label = @Translation("node"))
* }
@@ -19,6 +20,30 @@
*/
class NodeIsIslandoraObject extends ConditionPluginBase implements ContainerFactoryPluginInterface {
+ /**
+ * Islandora Utils.
+ *
+ * @var \Drupal\islandora\IslandoraUtils
+ */
+ protected $utils;
+
+ /**
+ * Constructs a Node is Islandora Condition plugin.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin_id for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\islandora\IslandoraUtils $islandora_utils
+ * Islandora utilities.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, IslandoraUtils $islandora_utils) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+ $this->utils = $islandora_utils;
+ }
+
/**
* {@inheritdoc}
*/
@@ -26,7 +51,8 @@ public static function create(ContainerInterface $container, array $configuratio
return new static(
$configuration,
$plugin_id,
- $plugin_definition
+ $plugin_definition,
+ $container->get('islandora.utils')
);
}
@@ -38,8 +64,8 @@ public function evaluate() {
if (!$node) {
return FALSE;
}
- // Islandora objects have these two fields.
- if ($node->hasField('field_model') && $node->hasField('field_member_of')) {
+ // Determine if node is Islandora.
+ if ($this->utils->isIslandoraType('node', $node->bundle())) {
return TRUE;
}
}
@@ -49,10 +75,10 @@ public function evaluate() {
*/
public function summary() {
if (!empty($this->configuration['negate'])) {
- return $this->t('The node is not an Islandora object.');
+ return $this->t('The node is not an Islandora node.');
}
else {
- return $this->t('The node is an Islandora object.');
+ return $this->t('The node is an Islandora node.');
}
}
diff --git a/src/Plugin/Condition/NodeReferencedByNode.php b/src/Plugin/Condition/NodeReferencedByNode.php
index a2abac808..d6db01ea0 100644
--- a/src/Plugin/Condition/NodeReferencedByNode.php
+++ b/src/Plugin/Condition/NodeReferencedByNode.php
@@ -16,7 +16,7 @@
* @Condition(
* id = "node_referenced_by_node",
* label = @Translation("Node is referenced by other nodes"),
- * context = {
+ * context_definitions = {
* "node" = @ContextDefinition("entity:node", required = TRUE , label = @Translation("node"))
* }
* )
@@ -128,6 +128,7 @@ protected function evaluateEntity(EntityInterface $entity) {
$config = FieldStorageConfig::loadByName('node', $reference_field);
if ($config) {
$id_count = \Drupal::entityQuery('node')
+ ->accessCheck(TRUE)
->condition($reference_field, $entity->id())
->count()
->execute();
diff --git a/src/Plugin/Field/FieldFormatter/IslandoraImageFormatter.php b/src/Plugin/Field/FieldFormatter/IslandoraImageFormatter.php
index 6c6e87da4..deb6c7690 100644
--- a/src/Plugin/Field/FieldFormatter/IslandoraImageFormatter.php
+++ b/src/Plugin/Field/FieldFormatter/IslandoraImageFormatter.php
@@ -5,6 +5,7 @@
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\image\Plugin\Field\FieldFormatter\ImageFormatter;
use Drupal\islandora\IslandoraUtils;
@@ -56,6 +57,8 @@ class IslandoraImageFormatter extends ImageFormatter {
* The image style storage.
* @param \Drupal\islandora\IslandoraUtils $utils
* Islandora utils.
+ * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
+ * The File URL Generator.
*/
public function __construct(
$plugin_id,
@@ -67,7 +70,8 @@ public function __construct(
array $third_party_settings,
AccountInterface $current_user,
EntityStorageInterface $image_style_storage,
- IslandoraUtils $utils
+ IslandoraUtils $utils,
+ FileUrlGeneratorInterface $file_url_generator
) {
parent::__construct(
$plugin_id,
@@ -78,7 +82,8 @@ public function __construct(
$view_mode,
$third_party_settings,
$current_user,
- $image_style_storage
+ $image_style_storage,
+ $file_url_generator
);
$this->utils = $utils;
}
@@ -97,7 +102,8 @@ public static function create(ContainerInterface $container, array $configuratio
$configuration['third_party_settings'],
$container->get('current_user'),
$container->get('entity_type.manager')->getStorage('image_style'),
- $container->get('islandora.utils')
+ $container->get('islandora.utils'),
+ $container->get('file_url_generator')
);
}
diff --git a/src/Plugin/views/field/IntegerWeightSelector.php b/src/Plugin/views/field/IntegerWeightSelector.php
index 141d3567b..60892c960 100644
--- a/src/Plugin/views/field/IntegerWeightSelector.php
+++ b/src/Plugin/views/field/IntegerWeightSelector.php
@@ -46,12 +46,13 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
$options[$this->getValue($row)] = $this->getValue($row);
}
- // If we were given some blank values we need to fill
+ // If we were given some blank values, or less than the
+ // total_rows for the view, we need to fill
// out the option list from 1 through the result count
// to make sure we have enough. (Blanks should only appear
// at the beginning of the results list.)
// Also, blank values will break the selector, remove it.
- if (array_key_exists('', $options)) {
+ if (array_key_exists('', $options) || (count($options) < $this->view->total_rows)) {
unset($options['']);
for ($i = 1; $i <= $this->view->total_rows; $i++) {
$options[$i] = $i;
diff --git a/src/Plugin/views/filter/NodeHasMediaUse.php b/src/Plugin/views/filter/NodeHasMediaUse.php
new file mode 100644
index 000000000..3c69bddd5
--- /dev/null
+++ b/src/Plugin/views/filter/NodeHasMediaUse.php
@@ -0,0 +1,94 @@
+ NULL];
+ $options['negated'] = ['default' => FALSE];
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateOptionsForm(&$form, FormStateInterface $form_state) {
+ $uri = $form_state->getValues()['options']['use_uri'];
+ $term = \Drupal::service('islandora.utils')->getTermForUri($uri);
+ if (empty($term)) {
+ $form_state->setError($form['use_uri'], $this->t('Could not find term with URI: "%uri"', ['%uri' => $uri]));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+ $terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(['vid' => 'islandora_media_use']);
+ $uris = [];
+ foreach ($terms as $term) {
+ foreach ($term->get('field_external_uri')->getValue() as $uri) {
+ $uris[$uri['uri']] = $term->label();
+ }
+ }
+
+ $form['use_uri'] = [
+ '#type' => 'select',
+ '#title' => "Media Use Term",
+ '#options' => $uris,
+ '#default_value' => $this->options['use_uri'],
+ '#required' => TRUE,
+ ];
+ $form['negated'] = [
+ '#type' => 'checkbox',
+ '#title' => 'Negated',
+ '#description' => $this->t("Return nodes that don't have this use URI"),
+ '#default_value' => $this->options['negated'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function adminSummary() {
+ $operator = ($this->options['negated']) ? "does not have" : "has";
+ $term = \Drupal::service('islandora.utils')->getTermForUri($this->options['use_uri']);
+ $label = (empty($term)) ? 'BROKEN TERM URI' : $term->label();
+ return "Node {$operator} a '{$label}' media";
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query() {
+ $condition = ($this->options['negated']) ? 'NOT IN' : 'IN';
+ $utils = \Drupal::service('islandora.utils');
+ $term = $utils->getTermForUri($this->options['use_uri']);
+ if (empty($term)) {
+ \Drupal::logger('islandora')->warning('Node Has Media Filter could not find term with URI: "%uri"', ['%uri' => $this->options['use_uri']]);
+ return;
+ }
+ $sub_query = \Drupal::database()->select('media', 'm');
+ $sub_query->join('media__field_media_use', 'use', 'm.mid = use.entity_id');
+ $sub_query->join('media__field_media_of', 'of', 'm.mid = of.entity_id');
+ $sub_query->fields('of', ['field_media_of_target_id'])
+ ->condition('use.field_media_use_target_id', $term->id());
+ $this->query->addWhere(0, 'nid', $sub_query, $condition);
+ }
+
+}
diff --git a/src/Plugin/views/filter/NodeIsIslandora.php b/src/Plugin/views/filter/NodeIsIslandora.php
new file mode 100644
index 000000000..2065c46b9
--- /dev/null
+++ b/src/Plugin/views/filter/NodeIsIslandora.php
@@ -0,0 +1,144 @@
+joinHandler = $join_handler;
+ $this->utils = $islandora_utils;
+ $this->entityTypeBundleInfo = $entity_type_bundle_info;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration, $plugin_id, $plugin_definition,
+ $container->get('plugin.manager.views.join'),
+ $container->get('islandora.utils'),
+ $container->get('entity_type.bundle.info')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function defineOptions() {
+ return [
+ 'negated' => ['default' => FALSE],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+ $types = [];
+ foreach ($this->entityTypeBundleInfo->getBundleInfo('node') as $bundle_id => $bundle) {
+ if ($this->utils->isIslandoraType('node', $bundle_id)) {
+ $types[] = "{$bundle['label']} ($bundle_id)";
+ }
+ }
+ $types_list = implode(', ', $types);
+ $form['info'] = [
+ '#type' => 'item',
+ '#title' => 'Information',
+ '#description' => t("Configured Islandora bundles: @types", ['@types' => $types_list]),
+ ];
+ $form['negated'] = [
+ '#type' => 'checkbox',
+ '#title' => 'Negated',
+ '#description' => $this->t("Return nodes that don't have islandora fields"),
+ '#default_value' => $this->options['negated'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function adminSummary() {
+ $operator = ($this->options['negated']) ? "is not" : "is";
+ return "Node {$operator} an islandora node";
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query() {
+ $types = [];
+ foreach (array_keys($this->entityTypeBundleInfo->getBundleInfo('node')) as $bundle_id) {
+ if ($this->utils->isIslandoraType('node', $bundle_id)) {
+ $types[] = $bundle_id;
+ }
+ }
+ $condition = ($this->options['negated']) ? 'NOT IN' : 'IN';
+ $query_base_table = $this->relationship ?: $this->view->storage->get('base_table');
+
+ $definition = [
+ 'table' => 'node',
+ 'type' => 'LEFT',
+ 'field' => 'nid',
+ 'left_table' => $query_base_table,
+ 'left_field' => 'nid',
+ ];
+ $join = $this->joinHandler->createInstance('standard', $definition);
+ $node_table_alias = $this->query->addTable('node', $this->relationship, $join);
+ $this->query->addWhere($this->options['group'], "$node_table_alias.type", $types, $condition);
+ }
+
+}
diff --git a/tests/fixtures/config/core.entity_form_display.media.test_media_type.default.yml b/tests/fixtures/config/core.entity_form_display.media.test_media_type.default.yml
index 19fe419b6..ea8eac008 100644
--- a/tests/fixtures/config/core.entity_form_display.media.test_media_type.default.yml
+++ b/tests/fixtures/config/core.entity_form_display.media.test_media_type.default.yml
@@ -1,9 +1,11 @@
+uuid: 9151a0fe-7729-4943-b506-dd6f8d12ceac
langcode: en
status: true
dependencies:
config:
+ - field.field.media.test_media_type.field_media_file
- field.field.media.test_media_type.field_media_of
- - field.field.media.test_media_type.field_tags
+ - field.field.media.test_media_type.field_media_use
- media.type.test_media_type
module:
- path
@@ -34,15 +36,17 @@ content:
region: content
settings:
match_operator: CONTAINS
+ match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
- field_tags:
+ field_media_use:
type: entity_reference_autocomplete
weight: 3
region: content
settings:
match_operator: CONTAINS
+ match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
@@ -64,6 +68,7 @@ content:
weight: 4
settings:
match_operator: CONTAINS
+ match_limit: 10
size: 60
placeholder: ''
region: content
diff --git a/tests/fixtures/config/core.entity_form_display.node.test_type.default.yml b/tests/fixtures/config/core.entity_form_display.node.test_type.default.yml
index 2560ec6e7..68724265a 100644
--- a/tests/fixtures/config/core.entity_form_display.node.test_type.default.yml
+++ b/tests/fixtures/config/core.entity_form_display.node.test_type.default.yml
@@ -1,12 +1,13 @@
+uuid: 90a6909f-a2aa-44e8-8b61-4cd54ec6974f
langcode: en
status: true
dependencies:
config:
- field.field.node.test_type.field_member_of
+ - field.field.node.test_type.field_model
- node.type.test_type
module:
- path
- - text
id: node.test_type.default
targetEntityType: node
bundle: test_type
@@ -19,14 +20,25 @@ content:
settings: { }
third_party_settings: { }
field_member_of:
+ type: entity_reference_autocomplete
weight: 122
+ region: content
settings:
match_operator: CONTAINS
+ match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
+ field_model:
type: entity_reference_autocomplete
+ weight: 123
region: content
+ settings:
+ match_operator: CONTAINS
+ match_limit: 10
+ size: 60
+ placeholder: ''
+ third_party_settings: { }
langcode:
type: language_select
weight: 2
@@ -42,24 +54,24 @@ content:
third_party_settings: { }
promote:
type: boolean_checkbox
- settings:
- display_label: true
weight: 15
region: content
+ settings:
+ display_label: true
third_party_settings: { }
status:
type: boolean_checkbox
- settings:
- display_label: true
weight: 120
region: content
+ settings:
+ display_label: true
third_party_settings: { }
sticky:
type: boolean_checkbox
- settings:
- display_label: true
weight: 16
region: content
+ settings:
+ display_label: true
third_party_settings: { }
title:
type: string_textfield
@@ -72,10 +84,11 @@ content:
uid:
type: entity_reference_autocomplete
weight: 5
+ region: content
settings:
match_operator: CONTAINS
+ match_limit: 10
size: 60
placeholder: ''
- region: content
third_party_settings: { }
hidden: { }
diff --git a/tests/fixtures/config/core.entity_form_display.node.test_type.secondary.yml b/tests/fixtures/config/core.entity_form_display.node.test_type.secondary.yml
index b1fdb88ec..f8f05beb1 100644
--- a/tests/fixtures/config/core.entity_form_display.node.test_type.secondary.yml
+++ b/tests/fixtures/config/core.entity_form_display.node.test_type.secondary.yml
@@ -1,12 +1,12 @@
+uuid: e24c2b3c-60e4-4ff5-99cb-80e5e67e7b04
langcode: en
status: true
dependencies:
config:
+ - core.entity_form_mode.node.secondary
- field.field.node.test_type.field_member_of
+ - field.field.node.test_type.field_model
- node.type.test_type
- module:
- - path
- - text
id: node.test_type.secondary
targetEntityType: node
bundle: test_type
@@ -23,6 +23,8 @@ content:
hidden:
created: true
field_media: true
+ field_member_of: true
+ field_model: true
field_node: true
langcode: true
path: true
diff --git a/tests/fixtures/config/core.entity_form_mode.node.secondary.yml b/tests/fixtures/config/core.entity_form_mode.node.secondary.yml
index 07f45bbe2..e1fc76345 100644
--- a/tests/fixtures/config/core.entity_form_mode.node.secondary.yml
+++ b/tests/fixtures/config/core.entity_form_mode.node.secondary.yml
@@ -1,9 +1,10 @@
+uuid: d9f22219-ff4c-48cc-a98a-6ccaad7a880d
langcode: en
status: true
dependencies:
module:
- node
id: node.secondary
-label: Secondary
+label: Secondary
targetEntityType: node
-cache: true
+cache: true
\ No newline at end of file
diff --git a/tests/fixtures/config/core.entity_view_display.node.test_type.default.yml b/tests/fixtures/config/core.entity_view_display.node.test_type.default.yml
index cf798265b..e4414e611 100644
--- a/tests/fixtures/config/core.entity_view_display.node.test_type.default.yml
+++ b/tests/fixtures/config/core.entity_view_display.node.test_type.default.yml
@@ -1,11 +1,12 @@
+uuid: 36f4aecf-0e14-4281-a213-ca7d129da52a
langcode: en
status: true
dependencies:
config:
- field.field.node.test_type.field_member_of
+ - field.field.node.test_type.field_model
- node.type.test_type
module:
- - text
- user
id: node.test_type.default
targetEntityType: node
@@ -13,14 +14,24 @@ bundle: test_type
mode: default
content:
field_member_of:
- weight: 102
+ type: entity_reference_label
label: above
settings:
link: true
third_party_settings: { }
+ weight: 102
+ region: content
+ field_model:
type: entity_reference_label
+ label: above
+ settings:
+ link: true
+ third_party_settings: { }
+ weight: 103
region: content
links:
+ settings: { }
+ third_party_settings: { }
weight: 100
region: content
hidden:
diff --git a/tests/fixtures/config/core.entity_view_display.node.test_type.teaser.yml b/tests/fixtures/config/core.entity_view_display.node.test_type.teaser.yml
index d67060f72..f72954281 100644
--- a/tests/fixtures/config/core.entity_view_display.node.test_type.teaser.yml
+++ b/tests/fixtures/config/core.entity_view_display.node.test_type.teaser.yml
@@ -1,19 +1,25 @@
-uuid: 0308339a-a9e5-4a04-8ce2-9f62ed504e34
+uuid: b337f462-8e64-4853-be65-9e03b94515bf
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
+ - field.field.node.test_type.field_member_of
+ - field.field.node.test_type.field_model
- node.type.test_type
module:
- - text
- user
id: node.test_type.teaser
targetEntityType: node
bundle: test_type
mode: teaser
content:
+ links:
+ settings: { }
+ third_party_settings: { }
+ weight: 100
+ region: content
hidden:
- body: true
- links: true
- langcode: true
+ field_member_of: true
+ field_model: true
+ langcode: true
\ No newline at end of file
diff --git a/tests/fixtures/config/rest.resource.entity.file.yml b/tests/fixtures/config/rest.resource.entity.file.yml
index 6a136c3cb..dbd6bb62c 100644
--- a/tests/fixtures/config/rest.resource.entity.file.yml
+++ b/tests/fixtures/config/rest.resource.entity.file.yml
@@ -1,3 +1,4 @@
+uuid: 11c4e25e-6b06-4270-b934-243e4f4aade1
langcode: en
status: true
dependencies:
@@ -26,3 +27,4 @@ configuration:
supported_auth:
- basic_auth
- jwt_auth
+ - cookie
diff --git a/tests/fixtures/config/rest.resource.entity.media.yml b/tests/fixtures/config/rest.resource.entity.media.yml
index 3ed0286e7..cd89243d3 100644
--- a/tests/fixtures/config/rest.resource.entity.media.yml
+++ b/tests/fixtures/config/rest.resource.entity.media.yml
@@ -1,3 +1,4 @@
+uuid: 9a5633b1-6a1a-40b2-8482-c24cf44122ff
langcode: en
status: true
dependencies:
@@ -5,7 +6,7 @@ dependencies:
- basic_auth
- jsonld
- jwt
- - media_entity
+ - media
- serialization
- user
id: entity.media
diff --git a/tests/fixtures/config/rest.resource.entity.node.yml b/tests/fixtures/config/rest.resource.entity.node.yml
index e7d4c7cc3..a3e253e2e 100644
--- a/tests/fixtures/config/rest.resource.entity.node.yml
+++ b/tests/fixtures/config/rest.resource.entity.node.yml
@@ -1,3 +1,4 @@
+uuid: 08a90469-0355-4b41-a4d6-cb6b53072b8c
langcode: en
status: true
dependencies:
diff --git a/tests/fixtures/config/rest.resource.entity.taxonomy_term.yml b/tests/fixtures/config/rest.resource.entity.taxonomy_term.yml
index 25b6fbb26..16d96c3ef 100644
--- a/tests/fixtures/config/rest.resource.entity.taxonomy_term.yml
+++ b/tests/fixtures/config/rest.resource.entity.taxonomy_term.yml
@@ -1,3 +1,4 @@
+uuid: 7534e393-12a7-498c-a4a3-a7bbe4ff9a5d
langcode: en
status: true
dependencies:
diff --git a/tests/modules/integer_weight_test_views/integer_weight_test_views.info.yml b/tests/modules/integer_weight_test_views/integer_weight_test_views.info.yml
index 7e2981427..77b12ec37 100644
--- a/tests/modules/integer_weight_test_views/integer_weight_test_views.info.yml
+++ b/tests/modules/integer_weight_test_views/integer_weight_test_views.info.yml
@@ -2,7 +2,7 @@ name: 'Integer weight test views'
type: module
description: 'Provides default views for integer weight views tests.'
package: Testing
-core_version_requirement: ^8 || ^9
+core_version_requirement: ^8 || ^9 || ^10
dependencies:
- drupal:node
- drupal:views
diff --git a/tests/src/Functional/AddChildTest.php b/tests/src/Functional/AddChildTest.php
index 9fc2f9e23..ed5282c73 100644
--- a/tests/src/Functional/AddChildTest.php
+++ b/tests/src/Functional/AddChildTest.php
@@ -9,13 +9,19 @@
*/
class AddChildTest extends IslandoraFunctionalTestBase {
+ /**
+ * The taxonomy term representing "Collection" items.
+ *
+ * @var \Drupal\taxonomy\TermInterface
+ */
+ protected $collectionTerm;
+
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
- $this->parent =
$this->collectionTerm = $this->container->get('entity_type.manager')->getStorage('taxonomy_term')->create([
'name' => 'Collection',
'vid' => $this->testVocabulary->id(),
diff --git a/tests/src/Functional/AddMediaToNodeTest.php b/tests/src/Functional/AddMediaToNodeTest.php
index 329097757..4b0b62c52 100644
--- a/tests/src/Functional/AddMediaToNodeTest.php
+++ b/tests/src/Functional/AddMediaToNodeTest.php
@@ -31,7 +31,7 @@ class AddMediaToNodeTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
$this->node = $this->container->get('entity_type.manager')->getStorage('node')->create([
diff --git a/tests/src/Functional/ContentEntityTypeTest.php b/tests/src/Functional/ContentEntityTypeTest.php
index 362ff7fbb..5ed229486 100644
--- a/tests/src/Functional/ContentEntityTypeTest.php
+++ b/tests/src/Functional/ContentEntityTypeTest.php
@@ -52,7 +52,8 @@ public function testContentEntityType() {
'name[0][value]' => 'Test Media',
'files[field_media_file_0]' => __DIR__ . '/../../fixtures/test_file.txt',
];
- $this->drupalPostForm('media/add/' . $this->testMediaType->id(), $values, $this->t('Save'));
+ $this->drupalGet('media/add/' . $this->testMediaType->id());
+ $this->submitForm($values, $this->t('Save'));
$this->assertSession()->pageTextNotContains("Hello World!");
}
diff --git a/tests/src/Functional/DeleteMediaTest.php b/tests/src/Functional/DeleteMediaTest.php
index f112c7006..c52eca31f 100644
--- a/tests/src/Functional/DeleteMediaTest.php
+++ b/tests/src/Functional/DeleteMediaTest.php
@@ -16,7 +16,7 @@ class DeleteMediaTest extends IslandoraFunctionalTestBase {
*
* @var array
*/
- public static $modules = [
+ protected static $modules = [
'media_test_views',
'context_ui',
'field_ui',
@@ -47,11 +47,18 @@ class DeleteMediaTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
+ if (version_compare(\Drupal::VERSION, '10.1', '>=')) {
+ $permissions = ['create media', 'delete any media', 'delete any file'];
+ }
+ else {
+ $permissions = ['create media', 'delete any media'];
+ }
+
// Create a test user.
- $this->account = $this->createUser(['create media', 'delete any media']);
+ $this->account = $this->createUser($permissions);
list($this->file, $this->media) = $this->makeMediaAndFile($this->account);
}
diff --git a/tests/src/Functional/DeleteNodeWithMediaAndFile.php b/tests/src/Functional/DeleteNodeWithMediaAndFile.php
new file mode 100644
index 000000000..5ee19b7ce
--- /dev/null
+++ b/tests/src/Functional/DeleteNodeWithMediaAndFile.php
@@ -0,0 +1,104 @@
+drupalCreateUser([
+ 'delete any media',
+ 'create media',
+ 'view media',
+ 'bypass node access',
+ 'access files overview',
+ 'administer site configuration',
+ ]);
+ $this->drupalLogin($account);
+
+ $assert_session = $this->assertSession();
+
+ $testImageMediaType = $this->createMediaType('image', ['id' => 'test_image_media_type']);
+ $testImageMediaType->save();
+
+ $this->createEntityReferenceField('media', $testImageMediaType->id(), 'field_media_of', 'Media Of', 'node', 'default', [], 2);
+
+ $node = $this->container->get('entity_type.manager')->getStorage('node')->create([
+ 'type' => 'test_type',
+ 'title' => 'node',
+ ]);
+ $node->save();
+
+ // Make an image for the Media.
+ $file = $this->container->get('entity_type.manager')->getStorage('file')->create([
+ 'uid' => $account->id(),
+ 'uri' => "public://test.jpeg",
+ 'filename' => "test.jpeg",
+ 'filemime' => "image/jpeg",
+ ]);
+ $file->setPermanent();
+ $file->save();
+
+ $this->drupalGet("node/1/delete");
+ $assert_session->pageTextNotContains('Delete all associated medias and nodes');
+
+ // Make the media, and associate it with the image and node.
+ $media1 = $this->container->get('entity_type.manager')->getStorage('media')->create([
+ 'bundle' => $testImageMediaType->id(),
+ 'name' => 'Media1',
+ 'field_media_image' =>
+ [
+ 'target_id' => $file->id(),
+ 'alt' => 'Some Alt',
+ 'title' => 'Some Title',
+ ],
+ 'field_media_of' => ['target_id' => $node->id()],
+ ]);
+ $media1->save();
+
+ $media2 = $this->container->get('entity_type.manager')->getStorage('media')->create([
+ 'bundle' => $testImageMediaType->id(),
+ 'name' => 'Media2',
+ 'field_media_image' =>
+ [
+ 'target_id' => $file->id(),
+ 'alt' => 'Some Alt',
+ 'title' => 'Some Title',
+ ],
+ 'field_media_of' => ['target_id' => $node->id()],
+ ]);
+ $media2->save();
+
+ $this->drupalGet("admin/config/islandora/core");
+ $assert_session->pageTextContains('Node Delete with Media and Files');
+ \Drupal::configFactory()->getEditable('islandora.settings')->set('delete_media_and_files', TRUE)->save();
+
+ $delete = ['delete_associated_content' => TRUE];
+
+ $this->drupalGet("node/1/delete");
+ $assert_session->pageTextContains('Media1');
+ $assert_session->pageTextContains('Media2');
+ $this->submitForm($delete, 'Delete');
+
+ $assert_session->pageTextContains($media1->id());
+ $assert_session->pageTextContains($media2->id());
+
+ $this->drupalGet("media/1/delete");
+ $assert_session->pageTextContains('Page not found');
+
+ $this->drupalGet("media/2/delete");
+ $assert_session->pageTextContains('Page not found');
+
+ $this->drupalGet("/admin/content/files");
+ $assert_session->pageTextNotContains('test.jpeg');
+
+ }
+
+}
diff --git a/tests/src/Functional/DerivativeReactionTest.php b/tests/src/Functional/DerivativeReactionTest.php
index e1b1c8276..00e0e5ae5 100644
--- a/tests/src/Functional/DerivativeReactionTest.php
+++ b/tests/src/Functional/DerivativeReactionTest.php
@@ -19,7 +19,7 @@ class DerivativeReactionTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
$this->node = $this->container->get('entity_type.manager')->getStorage('node')->create([
@@ -52,7 +52,8 @@ public function testExecuteDerivativeReaction() {
'files[field_media_file_0]' => __DIR__ . '/../../fixtures/test_file.txt',
'field_media_of[0][target_id]' => 'Test Node',
];
- $this->drupalPostForm('media/add/' . $this->testMediaType->id(), $values, $this->t('Save'));
+ $this->drupalGet('media/add/' . $this->testMediaType->id());
+ $this->submitForm($values, $this->t('Save'));
// field_media_of is set and there's a file, so derivatives should fire.
$this->assertSession()->pageTextContains("Hello World!");
diff --git a/tests/src/Functional/GenerateDerivativeTestBase.php b/tests/src/Functional/GenerateDerivativeTestBase.php
index 0f67d5919..c5ec9701c 100644
--- a/tests/src/Functional/GenerateDerivativeTestBase.php
+++ b/tests/src/Functional/GenerateDerivativeTestBase.php
@@ -29,7 +29,7 @@ abstract class GenerateDerivativeTestBase extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
$this->createUserAndLogin();
diff --git a/tests/src/Functional/IndexingTest.php b/tests/src/Functional/IndexingTest.php
index e995329d7..ff2152817 100644
--- a/tests/src/Functional/IndexingTest.php
+++ b/tests/src/Functional/IndexingTest.php
@@ -12,7 +12,7 @@ class IndexingTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
// Create an action that dsm's "Goodbye, Cruel World!".
@@ -63,9 +63,10 @@ public function testIndexing() {
// Add the Goodbye World reaction.
$this->addPresetReaction('test', 'delete', 'goodbye_world');
+ $this->drupalGet("$url/delete");
// Delete the node.
- $this->drupalPostForm("$url/delete", [], $this->t('Delete'));
+ $this->submitForm([], $this->t('Delete'));
$this->assertSession()->statusCodeEquals(200);
// Confirm Goodbye, Cruel World! is printed to the screen.
diff --git a/tests/src/Functional/IslandoraFunctionalTestBase.php b/tests/src/Functional/IslandoraFunctionalTestBase.php
index c154c5c23..016788d05 100644
--- a/tests/src/Functional/IslandoraFunctionalTestBase.php
+++ b/tests/src/Functional/IslandoraFunctionalTestBase.php
@@ -88,7 +88,7 @@ class IslandoraFunctionalTestBase extends BrowserTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
// Delete the node rest config that's bootstrapped with Drupal.
@@ -278,11 +278,13 @@ protected function createPreservationMasterTag() {
* Creates a test context.
*/
protected function createContext($label, $name) {
- $this->drupalPostForm('admin/structure/context/add', [
+ $this->drupalGet('admin/structure/context/add');
+ $values = [
'label' => $label,
'name' => $name,
- ],
- $this->t('Save'));
+ ];
+ $this->submitForm($values, 'Save');
+
$this->assertSession()->statusCodeEquals(200);
}
@@ -312,7 +314,8 @@ protected function addPresetReaction($context_id, $reaction_type, $action_id) {
* Create a new node by posting its add form.
*/
protected function postNodeAddForm($bundle_id, $values, $button_text) {
- $this->drupalPostForm("node/add/$bundle_id", $values, $this->t('@text', ['@text' => $button_text]));
+ $this->drupalGet("node/add/$bundle_id");
+ $this->submitForm($values, $this->t('@text', ['@text' => $button_text]));
$this->assertSession()->statusCodeEquals(200);
}
@@ -320,7 +323,8 @@ protected function postNodeAddForm($bundle_id, $values, $button_text) {
* Create a new node by posting its add form.
*/
protected function postTermAddForm($taxomony_id, $values, $button_text) {
- $this->drupalPostForm("admin/structure/taxonomy/manage/$taxomony_id/add", $values, $this->t('@text', ['@text' => $button_text]));
+ $this->drupalGet("admin/structure/taxonomy/manage/$taxomony_id/add");
+ $this->submitForm($values, $this->t('@text', ['@text' => $button_text]));
$this->assertSession()->statusCodeEquals(200);
}
@@ -328,7 +332,8 @@ protected function postTermAddForm($taxomony_id, $values, $button_text) {
* Edits a node by posting its edit form.
*/
protected function postEntityEditForm($entity_url, $values, $button_text) {
- $this->drupalPostForm("$entity_url/edit", $values, $this->t('@text', ['@text' => $button_text]));
+ $this->drupalGet("$entity_url/edit");
+ $this->submitForm($values, $this->t('@text', ['@text' => $button_text]));
$this->assertSession()->statusCodeEquals(200);
}
@@ -433,8 +438,8 @@ protected function makeMediaAndFile(AccountInterface $account) {
'uri' => "public://test_file.txt",
'filename' => "test_file.txt",
'filemime' => "text/plain",
- 'status' => FILE_STATUS_PERMANENT,
]);
+ $file->setPermanent();
$file->save();
// Get the source field for the media.
diff --git a/tests/src/Functional/IslandoraImageFormatterTest.php b/tests/src/Functional/IslandoraImageFormatterTest.php
index 2793cf49b..1b40f7a8d 100644
--- a/tests/src/Functional/IslandoraImageFormatterTest.php
+++ b/tests/src/Functional/IslandoraImageFormatterTest.php
@@ -27,13 +27,19 @@ public function testIslandoraImageFormatter() {
$testImageMediaType = $this->createMediaType('image', ['id' => 'test_image_media_type']);
$testImageMediaType->save();
$this->createEntityReferenceField('media', $testImageMediaType->id(), 'field_media_of', 'Media Of', 'node', 'default', [], 2);
-
// Set the display mode to use the islandora_image formatter.
// Also, only show the image on display to remove clutter.
$display_options = [
'type' => 'islandora_image',
- 'settings' => ['image_style' => NULL, 'image_link' => 'content'],
+ 'settings' => [
+ 'image_style' => '',
+ 'image_link' => 'content',
+ 'image_loading' => [
+ 'attribute' => 'eager',
+ ],
+ ],
];
+
$display = $this->container->get('entity_display.repository')->getViewDisplay('media', $testImageMediaType->id(), 'default');
$display->setComponent('field_media_image', $display_options)
->removeComponent('created')
@@ -47,15 +53,14 @@ public function testIslandoraImageFormatter() {
'title' => 'Test Node',
]);
$node->save();
-
// Make a image for the Media.
$file = $this->container->get('entity_type.manager')->getStorage('file')->create([
'uid' => $account->id(),
'uri' => "public://test.jpeg",
'filename' => "test.jpeg",
'filemime' => "image/jpeg",
- 'status' => FILE_STATUS_PERMANENT,
]);
+ $file->setPermanent();
$file->save();
// Make the media, and associate it with the image and node.
@@ -87,7 +92,7 @@ public function testIslandoraImageFormatter() {
':title' => 'Some Title',
]
);
- $this->assertEqual(count($elements), 1, 'Image linked to content formatter displaying points to Node and not Media.');
+ $this->assertEquals(count($elements), 1, 'Image linked to content formatter displaying points to Node and not Media.');
}
}
diff --git a/tests/src/Functional/IslandoraSettingsFormTest.php b/tests/src/Functional/IslandoraSettingsFormTest.php
index 92cfc6a29..80a327af1 100644
--- a/tests/src/Functional/IslandoraSettingsFormTest.php
+++ b/tests/src/Functional/IslandoraSettingsFormTest.php
@@ -14,7 +14,7 @@ class IslandoraSettingsFormTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
// Create a test user.
@@ -36,20 +36,25 @@ public function testJwtExpiry() {
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains("JWT Expiry");
$this->assertSession()->fieldValueEquals('edit-jwt-expiry', '+2 hour');
+ $this->drupalGet('/admin/config/islandora/core');
// Blank is not allowed.
- $this->drupalPostForm('/admin/config/islandora/core', ['edit-jwt-expiry' => ""], $this->t('Save configuration'));
+ $this->submitForm(['edit-jwt-expiry' => ""], $this->t('Save configuration'));
$this->assertSession()->pageTextContainsOnce('"" is not a valid time or interval expression.');
+ $this->drupalGet('/admin/config/islandora/core');
// Negative is not allowed.
- $this->drupalPostForm('/admin/config/islandora/core', ['edit-jwt-expiry' => "-2 hours"], $this->t('Save configuration'));
+ $this->submitForm(['edit-jwt-expiry' => "-2 hours"], $this->t('Save configuration'));
$this->assertSession()->pageTextContainsOnce('Time or interval expression cannot be negative');
+ $this->drupalGet('/admin/config/islandora/core');
// Must include an integer value.
- $this->drupalPostForm('/admin/config/islandora/core', ['edit-jwt-expiry' => "last hour"], $this->t('Save configuration'));
+ $this->submitForm(['edit-jwt-expiry' => "last hour"], $this->t('Save configuration'));
$this->assertSession()->pageTextContainsOnce('No numeric interval specified, for example "1 day"');
+ $this->drupalGet('/admin/config/islandora/core');
// Must have an accepted interval.
- $this->drupalPostForm('/admin/config/islandora/core', ['edit-jwt-expiry' => "1 fortnight"], $this->t('Save configuration'));
+ $this->submitForm(['edit-jwt-expiry' => "1 fortnight"], $this->t('Save configuration'));
$this->assertSession()->pageTextContainsOnce('No time interval found, please include one of');
+ $this->drupalGet('/admin/config/islandora/core');
// Test a valid setting.
- $this->drupalPostForm('/admin/config/islandora/core', ['edit-jwt-expiry' => "2 weeks"], $this->t('Save configuration'));
+ $this->submitForm(['edit-jwt-expiry' => "2 weeks"], $this->t('Save configuration'));
$this->assertSession()->pageTextContainsOnce('The configuration options have been saved.');
}
diff --git a/tests/src/Functional/JsonldSelfReferenceReactionTest.php b/tests/src/Functional/JsonldSelfReferenceReactionTest.php
index f3c882718..1b4e24ec7 100644
--- a/tests/src/Functional/JsonldSelfReferenceReactionTest.php
+++ b/tests/src/Functional/JsonldSelfReferenceReactionTest.php
@@ -2,6 +2,8 @@
namespace Drupal\Tests\islandora\Functional;
+use function GuzzleHttp\json_decode;
+
/**
* Class MappingUriPredicateReactionTest.
*
@@ -10,10 +12,17 @@
*/
class JsonldSelfReferenceReactionTest extends IslandoraFunctionalTestBase {
+ /**
+ * An RDF Mapping object.
+ *
+ * @var \Drupal\rdf\Entity\RdfMapping
+ */
+ protected $rdfMapping;
+
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
$types = ['schema:Thing'];
@@ -61,7 +70,7 @@ public function testMappingReaction() {
$contents = $this->drupalGet($url . '?_format=jsonld');
$this->assertSession()->statusCodeEquals(200);
- $json = \GuzzleHttp\json_decode($contents, TRUE);
+ $json = json_decode($contents, TRUE);
$this->assertArrayHasKey('http://purl.org/dc/terms/title',
$json['@graph'][0], 'Missing dcterms:title key');
$this->assertEquals(
@@ -103,7 +112,7 @@ public function testMappingReaction() {
drupal_flush_all_caches();
$new_contents = $this->drupalGet($url . '?_format=jsonld');
- $json = \GuzzleHttp\json_decode($new_contents, TRUE);
+ $json = json_decode($new_contents, TRUE);
$this->assertEquals(
'Test Node',
$json['@graph'][0]['http://purl.org/dc/terms/title'][0]['@value'],
@@ -123,7 +132,7 @@ public function testMappingReaction() {
$this->assertSession()
->pageTextContains("The context $context_name has been saved");
$new_contents = $this->drupalGet($url . '?_format=jsonld');
- $json = \GuzzleHttp\json_decode($new_contents, TRUE);
+ $json = json_decode($new_contents, TRUE);
$this->assertEquals(
'Test Node',
$json['@graph'][0]['http://purl.org/dc/terms/title'][0]['@value'],
@@ -161,7 +170,7 @@ public function testMappingReactionForMedia() {
$contents = $this->drupalGet($media_url . '?_format=jsonld');
$this->assertSession()->statusCodeEquals(200);
- $json = \GuzzleHttp\json_decode($contents, TRUE);
+ $json = json_decode($contents, TRUE);
$this->assertEquals(
"$media_url?_format=jsonld",
$json['@graph'][0]['@id'],
@@ -186,7 +195,7 @@ public function testMappingReactionForMedia() {
drupal_flush_all_caches();
$new_contents = $this->drupalGet($media_url . '?_format=jsonld');
- $json = \GuzzleHttp\json_decode($new_contents, TRUE);
+ $json = json_decode($new_contents, TRUE);
$this->assertEquals(
"$media_url?_format=jsonld",
$json['@graph'][0]['http://www.iana.org/assignments/relation/describedby'][0]['@id'],
diff --git a/tests/src/Functional/JsonldTypeAlterReactionTest.php b/tests/src/Functional/JsonldTypeAlterReactionTest.php
index e5d21abc6..75ae41dd3 100644
--- a/tests/src/Functional/JsonldTypeAlterReactionTest.php
+++ b/tests/src/Functional/JsonldTypeAlterReactionTest.php
@@ -2,6 +2,8 @@
namespace Drupal\Tests\islandora\Functional;
+use function GuzzleHttp\json_decode;
+
/**
* Tests Jsonld Alter Reaction.
*
@@ -20,17 +22,45 @@ public function testMappingReaction() {
'administer node fields',
]);
$this->drupalLogin($account);
+ $this->drupalGet('admin/structure/types/manage/test_type/fields/add-field');
// Add the typed predicate we will select in the reaction config.
// Taken from FieldUiTestTrait->fieldUIAddNewField.
- $this->drupalPostForm('admin/structure/types/manage/test_type/fields/add-field', [
- 'new_storage_type' => 'string',
- 'label' => 'Typed Predicate',
- 'field_name' => 'type_predicate',
- ], $this->t('Save and continue'));
- $this->drupalPostForm(NULL, [], $this->t('Save field settings'));
- $this->drupalPostForm(NULL, [], $this->t('Save settings'));
- $this->assertRaw('field_type_predicate', 'Redirected to "Manage fields" page.');
+ if (version_compare(\Drupal::VERSION, '10.2.x-dev', 'lt')) {
+ $this->submitForm([
+ 'new_storage_type' => 'string',
+ 'label' => 'Typed Predicate',
+ 'field_name' => 'type_predicate',
+ ], 'Save and continue');
+ $this->submitForm([], $this->t('Save field settings'));
+ }
+ else {
+ $this->getSession()->getPage()->selectFieldOption('new_storage_type', 'plain_text');
+ // First need to submit the form with the elements displayed
+ // on initial page load. The form is using AJAX to send a second element
+ // after we selected the radio button above
+ // we can instead get the second element by submitting the form
+ // and having it throw an error since the required field is missing.
+ // @todo refactor this as a functional javascript test.
+ $this->submitForm([
+ 'new_storage_type' => 'plain_text',
+ 'label' => 'Typed Predicate',
+ 'field_name' => 'type_predicate',
+ ], $this->t('Continue'));
+
+ // Now we can proceed, selecting the plain text (i.e. string)
+ // for the second element now that the element is displayed after
+ // the initial form submission.
+ $this->getSession()->getPage()->selectFieldOption('group_field_options_wrapper', 'string');
+ $this->submitForm([
+ 'new_storage_type' => 'plain_text',
+ 'label' => 'Typed Predicate',
+ 'field_name' => 'type_predicate',
+ 'group_field_options_wrapper' => 'string',
+ ], $this->t('Continue'));
+ }
+ $this->submitForm([], $this->t('Save settings'));
+ $this->assertSession()->responseContains('field_type_predicate');
// Add the test node.
$this->postNodeAddForm('test_type', [
@@ -46,7 +76,7 @@ public function testMappingReaction() {
$contents = $this->drupalGet($url . '?_format=jsonld');
$this->assertSession()->statusCodeEquals(200);
- $json = \GuzzleHttp\json_decode($contents, TRUE);
+ $json = json_decode($contents, TRUE);
$this->assertArrayHasKey('@type',
$json['@graph'][0], 'Missing @type');
$this->assertEquals(
@@ -81,7 +111,7 @@ public function testMappingReaction() {
// Check for the new @type from the field_type_predicate value.
$new_contents = $this->drupalGet($url . '?_format=jsonld');
- $json = \GuzzleHttp\json_decode($new_contents, TRUE);
+ $json = json_decode($new_contents, TRUE);
$this->assertTrue(
in_array('http://schema.org/Organization', $json['@graph'][0]['@type']),
'Missing altered @type value of http://schema.org/Organization'
diff --git a/tests/src/Functional/LinkHeaderTest.php b/tests/src/Functional/LinkHeaderTest.php
index 7cb741d55..98b36c68b 100644
--- a/tests/src/Functional/LinkHeaderTest.php
+++ b/tests/src/Functional/LinkHeaderTest.php
@@ -42,7 +42,7 @@ class LinkHeaderTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
$account = $this->createUserAndLogin();
diff --git a/tests/src/Functional/MediaSourceUpdateTest.php b/tests/src/Functional/MediaSourceUpdateTest.php
index fdea6aef9..3938e9b47 100644
--- a/tests/src/Functional/MediaSourceUpdateTest.php
+++ b/tests/src/Functional/MediaSourceUpdateTest.php
@@ -35,7 +35,7 @@ class MediaSourceUpdateTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
// Make a user with appropriate permissions.
@@ -52,8 +52,8 @@ public function setUp() {
'uri' => "public://test_file.txt",
'filename' => "test_file.txt",
'filemime' => "text/plain",
- 'status' => FILE_STATUS_PERMANENT,
]);
+ $this->file->setPermanent();
$this->file->save();
// Get the source field for the media.
diff --git a/tests/src/Functional/NodeHasTermTest.php b/tests/src/Functional/NodeHasTermTest.php
index eff5b5c34..2b4ee16f8 100644
--- a/tests/src/Functional/NodeHasTermTest.php
+++ b/tests/src/Functional/NodeHasTermTest.php
@@ -13,7 +13,7 @@ class NodeHasTermTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
diff --git a/tests/src/Functional/ViewModeAlterReactionTest.php b/tests/src/Functional/ViewModeAlterReactionTest.php
index 72cdfe447..19660bda1 100644
--- a/tests/src/Functional/ViewModeAlterReactionTest.php
+++ b/tests/src/Functional/ViewModeAlterReactionTest.php
@@ -26,7 +26,7 @@ class ViewModeAlterReactionTest extends IslandoraFunctionalTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
// Node to be referenced via member of.
diff --git a/tests/src/FunctionalJavascript/IntegerWeightTest.php b/tests/src/FunctionalJavascript/IntegerWeightTest.php
index ba289aa49..2572c191e 100644
--- a/tests/src/FunctionalJavascript/IntegerWeightTest.php
+++ b/tests/src/FunctionalJavascript/IntegerWeightTest.php
@@ -80,7 +80,7 @@ class IntegerWeightTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(
diff --git a/tests/src/Kernel/EventGeneratorTest.php b/tests/src/Kernel/EventGeneratorTest.php
index c423cda3f..34a5ae925 100644
--- a/tests/src/Kernel/EventGeneratorTest.php
+++ b/tests/src/Kernel/EventGeneratorTest.php
@@ -41,7 +41,7 @@ class EventGeneratorTest extends IslandoraKernelTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
// Create a test user.
@@ -49,7 +49,7 @@ public function setUp() {
$test_type = NodeType::create([
'type' => 'test_type',
- 'label' => 'Test Type',
+ 'name' => 'Test Type',
]);
$test_type->save();
@@ -116,7 +116,6 @@ public function testGenerateDeleteEvent() {
['event' => 'delete', 'queue' => 'islandora-indexing-fcrepo-delete']
);
$msg = json_decode($json, TRUE);
- $msg = json_decode($json, TRUE);
$this->assertBasicStructure($msg);
$this->assertTrue($msg["type"] == "Delete", "Event must be of type 'Delete'.");
diff --git a/tests/src/Kernel/FedoraAdapterTest.php b/tests/src/Kernel/FedoraAdapterTest.php
index 10c5beb11..6d8fee284 100644
--- a/tests/src/Kernel/FedoraAdapterTest.php
+++ b/tests/src/Kernel/FedoraAdapterTest.php
@@ -2,12 +2,15 @@
namespace Drupal\Tests\islandora\Kernel;
+use Prophecy\PhpUnit\ProphecyTrait;
+use GuzzleHttp\Psr7\Utils;
+use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\islandora\Flysystem\Adapter\FedoraAdapter;
use GuzzleHttp\Psr7\Response;
use Islandora\Chullo\IFedoraApi;
use League\Flysystem\Config;
use Prophecy\Argument;
-use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
/**
* Tests the Fedora adapter for Flysystem.
@@ -17,6 +20,31 @@
*/
class FedoraAdapterTest extends IslandoraKernelTestBase {
+ use ProphecyTrait;
+ /**
+ * A mimetype guesser prophecy.
+ *
+ * @var \Prophecy\Prophecy\ObjectProphecy
+ */
+ private $mimeGuesser;
+
+ /**
+ * A logger prophecy.
+ *
+ * @var \Prophecy\Prophecy\ObjectProphecy
+ */
+ private $logger;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp(): void {
+ parent::setUp();
+ $this->mimeGuesser = $this->prophesize(MimeTypeGuesserInterface::class)
+ ->reveal();
+ $this->logger = $this->prophesize(LoggerChannelInterface::class)->reveal();
+ }
+
/**
* Shared functionality for an adapter.
*/
@@ -32,13 +60,8 @@ protected function createAdapterBase() {
]);
$prophecy->getHeader('Content-Type')->willReturn(['text/plain']);
$prophecy->getHeader('Content-Length')->willReturn([strlen("DERP")]);
- // phpcs:disable
- if (class_exists(\GuzzleHttp\Psr7\Utils::class)) {
- $prophecy->getBody()->willReturn(\GuzzleHttp\Psr7\Utils::streamFor("DERP"));
- } else {
- $prophecy->getBody()->willReturn(\GuzzleHttp\Psr7\stream_for("DERP"));
- }
- // phpcs:enable
+ $prophecy->getBody()->willReturn(Utils::streamFor("DERP"));
+
return $prophecy;
}
@@ -55,10 +78,7 @@ protected function createAdapterForFail() {
$prophecy->getResource('', ['Connection' => 'close'])->willReturn($response);
$api = $prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -73,10 +93,7 @@ protected function createAdapterForFile() {
$prophecy->getResource('', ['Connection' => 'close'])->willReturn($response);
$api = $prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -98,10 +115,7 @@ protected function createAdapterForDirectory() {
$prophecy->getResourceHeaders('', ['Connection' => 'close'])->willReturn($response);
$api = $prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -126,10 +140,7 @@ protected function createAdapterForWrite() {
$api = $fedora_prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -149,10 +160,7 @@ protected function createAdapterForWriteFail() {
$api = $fedora_prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -180,10 +188,7 @@ protected function createAdapterForCreateDir() {
$api = $fedora_prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -199,10 +204,7 @@ protected function createAdapterForDelete() {
$fedora_prophecy->getResourceHeaders('', ['Connection' => 'close'])->willReturn($prophecy->reveal());
$api = $fedora_prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -218,10 +220,7 @@ protected function createAdapterForDeleteFail() {
$api = $fedora_prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -236,7 +235,7 @@ protected function createAdapterForDeleteWithTombstone() {
$head_prophecy = $this->prophesize(Response::class);
$head_prophecy->getStatusCode()->willReturn(410);
$head_prophecy->getHeader('Link')
- ->willReturn('; rel="hasTombstone"');
+ ->willReturn(['; rel="hasTombstone"']);
$tombstone_prophecy = $this->prophesize(Response::class);
$tombstone_prophecy->getStatusCode()->willReturn(204);
@@ -249,10 +248,7 @@ protected function createAdapterForDeleteWithTombstone() {
$api = $fedora_prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -267,7 +263,7 @@ protected function createAdapterForDeleteWithTombstoneFail() {
$head_prophecy = $this->prophesize(Response::class);
$head_prophecy->getStatusCode()->willReturn(410);
$head_prophecy->getHeader('Link')
- ->willReturn('; rel="hasTombstone"');
+ ->willReturn(['; rel="hasTombstone"']);
$tombstone_prophecy = $this->prophesize(Response::class);
$tombstone_prophecy->getStatusCode()->willReturn(500);
@@ -280,10 +276,7 @@ protected function createAdapterForDeleteWithTombstoneFail() {
$api = $fedora_prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- return new FedoraAdapter($api, $mime_guesser);
+ return new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
}
/**
@@ -361,7 +354,7 @@ public function testRead() {
$metadata = $adapter->read('');
$this->assertFileMetadata($metadata);
- $this->assertTrue($metadata['contents'] == "DERP", "Expecting 'content' of 'DERP', received '${metadata['contents']}'");
+ $this->assertTrue($metadata['contents'] == "DERP", "Expecting 'content' of 'DERP', received '{$metadata['contents']}'");
}
/**
@@ -644,10 +637,7 @@ public function testRename() {
$api = $fedora_prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- $adapter = new FedoraAdapter($api, $mime_guesser);
+ $adapter = new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
$this->assertTrue($adapter->rename('', '') == TRUE, "rename() must return TRUE on success");
}
@@ -664,10 +654,7 @@ public function testCreateDirFail() {
$api = $fedora_prophecy->reveal();
- $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)
- ->reveal();
-
- $adapter = new FedoraAdapter($api, $mime_guesser);
+ $adapter = new FedoraAdapter($api, $this->mimeGuesser, $this->logger);
$this->assertTrue($adapter->createDir('', $this->prophesize(Config::class)
->reveal()) == FALSE, "createDir() must return FALSE on fail");
diff --git a/tests/src/Kernel/FedoraPluginTest.php b/tests/src/Kernel/FedoraPluginTest.php
index 674915078..92a8137ca 100644
--- a/tests/src/Kernel/FedoraPluginTest.php
+++ b/tests/src/Kernel/FedoraPluginTest.php
@@ -2,11 +2,13 @@
namespace Drupal\Tests\islandora\Kernel;
+use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\islandora\Flysystem\Fedora;
-use League\Flysystem\AdapterInterface;
use Islandora\Chullo\IFedoraApi;
+use League\Flysystem\AdapterInterface;
+use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Http\Message\ResponseInterface;
-use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
/**
* Tests the Fedora plugin for Flysystem.
@@ -16,6 +18,8 @@
*/
class FedoraPluginTest extends IslandoraKernelTestBase {
+ use ProphecyTrait;
+
/**
* Mocks up a plugin.
*/
@@ -32,8 +36,9 @@ protected function createPlugin($return_code) {
$mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal();
$language_manager = $this->container->get('language_manager');
+ $logger = $this->prophesize(LoggerChannelInterface::class)->reveal();
- return new Fedora($api, $mime_guesser, $language_manager);
+ return new Fedora($api, $mime_guesser, $language_manager, $logger);
}
/**
diff --git a/tests/src/Kernel/IslandoraKernelTestBase.php b/tests/src/Kernel/IslandoraKernelTestBase.php
index 5a95cb682..1c98db3e9 100644
--- a/tests/src/Kernel/IslandoraKernelTestBase.php
+++ b/tests/src/Kernel/IslandoraKernelTestBase.php
@@ -12,7 +12,7 @@ abstract class IslandoraKernelTestBase extends KernelTestBase {
/**
* {@inheritdoc}
*/
- public static $modules = [
+ protected static $modules = [
'system',
'user',
'field',
@@ -43,7 +43,7 @@ abstract class IslandoraKernelTestBase extends KernelTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
// Bootstrap minimal Drupal environment to run the tests.
diff --git a/tests/src/Kernel/JwtEventSubscriberTest.php b/tests/src/Kernel/JwtEventSubscriberTest.php
index f97eab9fb..9493ab78a 100644
--- a/tests/src/Kernel/JwtEventSubscriberTest.php
+++ b/tests/src/Kernel/JwtEventSubscriberTest.php
@@ -2,6 +2,7 @@
namespace Drupal\Tests\islandora\Kernel;
+use Prophecy\PhpUnit\ProphecyTrait;
use Drupal\jwt\Authentication\Event\JwtAuthGenerateEvent;
use Drupal\jwt\Authentication\Event\JwtAuthValidEvent;
use Drupal\jwt\Authentication\Event\JwtAuthValidateEvent;
@@ -19,6 +20,7 @@
*/
class JwtEventSubscriberTest extends IslandoraKernelTestBase {
+ use ProphecyTrait;
use UserCreationTrait;
/**
@@ -31,7 +33,7 @@ class JwtEventSubscriberTest extends IslandoraKernelTestBase {
/**
* {@inheritdoc}
*/
- public function setUp() {
+ public function setUp(): void {
parent::setUp();
$this->user = $this->createUser();