From cb8bb07238d1822370aca73e7011fdea9235c501 Mon Sep 17 00:00:00 2001 From: dannylamb Date: Wed, 15 Aug 2018 14:56:46 -0300 Subject: [PATCH] Flysystem (#92) * Flysystem adapter and REST API accepts Content-Location with a stream wrapper * First pass is working now * Config dump * typo in composer.json * Testing install * Tests. Coding standards * Adding token as a depenedency * Unneccessary config * Adding drupal/token to composer.json * Applying the file's uri using the mapping predicate alter. * Tests vs coding standards whack-a-mole * Sneaky image resize bug * Test refactor to quiesce phpcpd. How you like that @bradspry? * _slightly_ better error reporting for tests * Moar test updates * phpcbf to the rescue * Config export * Fixing the Canoncial typo once and for all * Better trimming * Collapsing 'Is' style conditions into a singular 'ContentEntityType' condition * Coding standards * Was referencing is_node in a test * Unbreaking core block placement * Copy pasta fix and removing conditions I obliterated from the schema file. --- composer.json | 5 +- config/schema/islandora.schema.yml | 40 +- islandora.info.yml | 2 + islandora.services.yml | 4 +- ...e.base_field_override.media.audio.name.yml | 19 + ...re.base_field_override.media.file.name.yml | 19 + ...e.base_field_override.media.image.name.yml | 19 + ...e.base_field_override.media.video.name.yml | 19 + ...d.storage.media.field_media_audio_file.yml | 4 +- .../field.storage.media.field_media_file.yml | 4 +- .../field.storage.media.field_media_image.yml | 4 +- ...d.storage.media.field_media_video_file.yml | 4 +- .../field.storage.media.field_mime_type.yml | 2 +- .../config/install/filehash.settings.yml | 5 + .../language.content_settings.media.audio.yml | 2 + .../language.content_settings.media.file.yml | 2 + .../language.content_settings.media.image.yml | 2 + .../language.content_settings.media.video.yml | 2 + ...ge.content_settings.taxonomy_term.tags.yml | 2 + .../system.action.delete_file_from_fedora.yml | 6 +- .../system.action.index_file_in_fedora.yml | 4 +- .../install/views.view.file_checksum.yml | 1 + .../islandora_core_feature.info.yml | 1 + .../context.context.external_media.yml | 46 ++ ...a.yml => context.context.fedora_media.yml} | 21 +- ...ml => context.context.files_in_fedora.yml} | 17 +- .../context.context.repository_content.yml | 4 + .../context.context.taxonomy_terms.yml | 8 +- ...erride.node.islandora_object.menu_link.yml | 19 + ...d_override.node.islandora_object.title.yml | 17 + ...ntity_form_display.media.audio.default.yml | 3 + ...entity_form_display.media.file.default.yml | 3 + ...ntity_form_display.media.image.default.yml | 3 + ...ntity_form_display.media.video.default.yml | 3 + ..._display.node.islandora_object.default.yml | 3 + ...content_settings.node.islandora_object.yml | 2 + ..._service_file_from_preservation_master.yml | 2 + ...a_thumbnail_from_an_image_service_file.yml | 2 + src/Controller/MediaSourceController.php | 14 +- src/EventGenerator/EmitEvent.php | 3 +- src/EventGenerator/EventGenerator.php | 102 ++-- src/Flysystem/Adapter/FedoraAdapter.php | 324 +++++++++++ src/Flysystem/Fedora.php | 124 ++++ src/IslandoraUtils.php | 31 +- src/MediaSource/MediaSourceService.php | 131 ++--- src/Plugin/Action/EmitFileEvent.php | 98 +++- src/Plugin/Action/EmitMediaEvent.php | 91 ++- src/Plugin/Condition/ContentEntityType.php | 105 ++++ src/Plugin/Condition/FileUsesFilesystem.php | 168 ++++++ src/Plugin/Condition/IsFile.php | 35 -- src/Plugin/Condition/IsMedia.php | 35 -- src/Plugin/Condition/IsNode.php | 35 -- src/Plugin/Condition/IsTerm.php | 43 -- src/Plugin/Condition/MediaHasTerm.php | 4 + src/Plugin/Condition/MediaUsesFilesystem.php | 116 ++++ src/Plugin/Condition/NodeHasTerm.php | 5 +- src/Plugin/Condition/ParentNodeHasTerm.php | 4 + .../MappingUriPredicateReaction.php | 32 +- tests/src/Functional/AddMediaToNodeTest.php | 25 +- ...NodeTest.php => ContentEntityTypeTest.php} | 15 +- tests/src/Functional/EmitNodeEventTest.php | 11 +- tests/src/Functional/IsFileTest.php | 63 -- tests/src/Functional/IsMediaTest.php | 56 -- tests/src/Functional/IsTermTest.php | 51 -- tests/src/Kernel/FedoraAdapterTest.php | 549 ++++++++++++++++++ tests/src/Kernel/FedoraPluginTest.php | 62 ++ tests/src/Kernel/IslandoraKernelTestBase.php | 1 + 67 files changed, 2102 insertions(+), 556 deletions(-) create mode 100644 modules/islandora_core_feature/config/install/core.base_field_override.media.audio.name.yml create mode 100644 modules/islandora_core_feature/config/install/core.base_field_override.media.file.name.yml create mode 100644 modules/islandora_core_feature/config/install/core.base_field_override.media.image.name.yml create mode 100644 modules/islandora_core_feature/config/install/core.base_field_override.media.video.name.yml create mode 100644 modules/islandora_core_feature/config/install/filehash.settings.yml create mode 100644 modules/islandora_demo_feature/config/install/context.context.external_media.yml rename modules/islandora_demo_feature/config/install/{context.context.media.yml => context.context.fedora_media.yml} (59%) rename modules/islandora_demo_feature/config/install/{context.context.files.yml => context.context.files_in_fedora.yml} (68%) create mode 100644 modules/islandora_demo_feature/config/install/core.base_field_override.node.islandora_object.menu_link.yml create mode 100644 modules/islandora_demo_feature/config/install/core.base_field_override.node.islandora_object.title.yml create mode 100644 src/Flysystem/Adapter/FedoraAdapter.php create mode 100644 src/Flysystem/Fedora.php create mode 100644 src/Plugin/Condition/ContentEntityType.php create mode 100644 src/Plugin/Condition/FileUsesFilesystem.php delete mode 100644 src/Plugin/Condition/IsFile.php delete mode 100644 src/Plugin/Condition/IsMedia.php delete mode 100644 src/Plugin/Condition/IsNode.php delete mode 100644 src/Plugin/Condition/IsTerm.php create mode 100644 src/Plugin/Condition/MediaUsesFilesystem.php rename tests/src/Functional/{IsNodeTest.php => ContentEntityTypeTest.php} (70%) delete mode 100644 tests/src/Functional/IsFileTest.php delete mode 100644 tests/src/Functional/IsMediaTest.php delete mode 100644 tests/src/Functional/IsTermTest.php create mode 100644 tests/src/Kernel/FedoraAdapterTest.php create mode 100644 tests/src/Kernel/FedoraPluginTest.php diff --git a/composer.json b/composer.json index 80998154..a00fcff6 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,10 @@ "drupal/migrate_plus" : "4.0-beta3", "drupal/migrate_tools" : "4.0-beta3", "drupal/migrate_source_csv" : "^2.1", - "drupal/permissions_by_term" : "^1.51" + "drupal/permissions_by_term" : "^1.51", + "drupal/token" : "^1.3", + "drupal/flysystem" : "^1.0", + "islandora/chullo" : "dev-master" }, "require-dev": { "phpunit/phpunit": "^6", diff --git a/config/schema/islandora.schema.yml b/config/schema/islandora.schema.yml index 350fc302..ad59a82c 100644 --- a/config/schema/islandora.schema.yml +++ b/config/schema/islandora.schema.yml @@ -64,22 +64,6 @@ action.configuration.delete_media_and_file: type: action_configuration_default label: 'Delete media and file' -condition.plugin.is_node: - type: condition.plugin - mapping: - -condition.plugin.is_media: - type: condition.plugin - mapping: - -condition.plugin.is_file: - type: condition.plugin - mapping: - -condition.plugin.is_term: - type: condition.plugin - mapping: - condition.plugin.node_has_term: type: condition.plugin mapping: @@ -100,3 +84,27 @@ condition.plugin.parent_node_has_term: uri: type: text label: 'Taxonomy Term URI' + +condition.plugin.file_uses_filesystem: + type: condition.plugin + mapping: + filesystems: + type: sequence + sequence: + type: string + +condition.plugin.media_uses_filesystem: + type: condition.plugin + mapping: + filesystems: + type: sequence + sequence: + type: string + +condition.plugin.content_entity_type: + type: condition.plugin + mapping: + types: + type: sequence + sequence: + type: string diff --git a/islandora.info.yml b/islandora.info.yml index f186b577..0c0dbf0f 100644 --- a/islandora.info.yml +++ b/islandora.info.yml @@ -29,3 +29,5 @@ dependencies: - migrate_tools - migrate_source_csv - content_translation + - flysystem + - token diff --git a/islandora.services.yml b/islandora.services.yml index 1159f0a3..12bbbdb7 100644 --- a/islandora.services.yml +++ b/islandora.services.yml @@ -47,7 +47,7 @@ services: - { name: 'context_provider' } islandora.media_source_service: class: Drupal\islandora\MediaSource\MediaSourceService - arguments: ['@entity_type.manager', '@current_user', '@stream_wrapper_manager', '@language_manager', '@token', '@entity.query'] + arguments: ['@entity_type.manager', '@current_user', '@language_manager', '@entity.query', '@file_system'] islandora.utils: class: Drupal\islandora\IslandoraUtils - arguments: ['@entity_type.manager', '@entity_field.manager', '@entity.query', '@context.manager', '@stream_wrapper_manager'] + arguments: ['@entity_type.manager', '@entity_field.manager', '@entity.query', '@context.manager', '@flysystem_factory'] diff --git a/modules/islandora_core_feature/config/install/core.base_field_override.media.audio.name.yml b/modules/islandora_core_feature/config/install/core.base_field_override.media.audio.name.yml new file mode 100644 index 00000000..6d4e96ae --- /dev/null +++ b/modules/islandora_core_feature/config/install/core.base_field_override.media.audio.name.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - media.type.audio +id: media.audio.name +field_name: name +entity_type: media +bundle: audio +label: Name +description: '' +required: true +translatable: true +default_value: + - + value: '' +default_value_callback: '' +settings: { } +field_type: string diff --git a/modules/islandora_core_feature/config/install/core.base_field_override.media.file.name.yml b/modules/islandora_core_feature/config/install/core.base_field_override.media.file.name.yml new file mode 100644 index 00000000..ba40efd8 --- /dev/null +++ b/modules/islandora_core_feature/config/install/core.base_field_override.media.file.name.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - media.type.file +id: media.file.name +field_name: name +entity_type: media +bundle: file +label: Name +description: '' +required: true +translatable: true +default_value: + - + value: '' +default_value_callback: '' +settings: { } +field_type: string diff --git a/modules/islandora_core_feature/config/install/core.base_field_override.media.image.name.yml b/modules/islandora_core_feature/config/install/core.base_field_override.media.image.name.yml new file mode 100644 index 00000000..a7e36651 --- /dev/null +++ b/modules/islandora_core_feature/config/install/core.base_field_override.media.image.name.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - media.type.image +id: media.image.name +field_name: name +entity_type: media +bundle: image +label: Name +description: '' +required: true +translatable: true +default_value: + - + value: '' +default_value_callback: '' +settings: { } +field_type: string diff --git a/modules/islandora_core_feature/config/install/core.base_field_override.media.video.name.yml b/modules/islandora_core_feature/config/install/core.base_field_override.media.video.name.yml new file mode 100644 index 00000000..4a43e072 --- /dev/null +++ b/modules/islandora_core_feature/config/install/core.base_field_override.media.video.name.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - media.type.video +id: media.video.name +field_name: name +entity_type: media +bundle: video +label: Name +description: '' +required: true +translatable: true +default_value: + - + value: '' +default_value_callback: '' +settings: { } +field_type: string diff --git a/modules/islandora_core_feature/config/install/field.storage.media.field_media_audio_file.yml b/modules/islandora_core_feature/config/install/field.storage.media.field_media_audio_file.yml index 2421e500..c7216d26 100644 --- a/modules/islandora_core_feature/config/install/field.storage.media.field_media_audio_file.yml +++ b/modules/islandora_core_feature/config/install/field.storage.media.field_media_audio_file.yml @@ -12,10 +12,10 @@ field_name: field_media_audio_file entity_type: media type: file settings: - target_type: file display_field: false display_default: false - uri_scheme: public + uri_scheme: fedora + target_type: file module: file locked: false cardinality: 1 diff --git a/modules/islandora_core_feature/config/install/field.storage.media.field_media_file.yml b/modules/islandora_core_feature/config/install/field.storage.media.field_media_file.yml index b38a1ae0..f2cd5430 100644 --- a/modules/islandora_core_feature/config/install/field.storage.media.field_media_file.yml +++ b/modules/islandora_core_feature/config/install/field.storage.media.field_media_file.yml @@ -13,10 +13,10 @@ field_name: field_media_file entity_type: media type: file settings: - uri_scheme: public - target_type: file display_field: false display_default: false + uri_scheme: fedora + target_type: file module: file locked: false cardinality: 1 diff --git a/modules/islandora_core_feature/config/install/field.storage.media.field_media_image.yml b/modules/islandora_core_feature/config/install/field.storage.media.field_media_image.yml index 22c3c4ee..52a1ccd3 100644 --- a/modules/islandora_core_feature/config/install/field.storage.media.field_media_image.yml +++ b/modules/islandora_core_feature/config/install/field.storage.media.field_media_image.yml @@ -14,8 +14,9 @@ field_name: field_media_image entity_type: media type: image settings: + uri_scheme: fedora default_image: - uuid: null + uuid: '' alt: '' title: '' width: null @@ -23,7 +24,6 @@ settings: target_type: file display_field: false display_default: false - uri_scheme: public module: image locked: false cardinality: 1 diff --git a/modules/islandora_core_feature/config/install/field.storage.media.field_media_video_file.yml b/modules/islandora_core_feature/config/install/field.storage.media.field_media_video_file.yml index 0c02aef1..df28b0df 100644 --- a/modules/islandora_core_feature/config/install/field.storage.media.field_media_video_file.yml +++ b/modules/islandora_core_feature/config/install/field.storage.media.field_media_video_file.yml @@ -12,10 +12,10 @@ field_name: field_media_video_file entity_type: media type: file settings: - target_type: file display_field: false display_default: false - uri_scheme: public + uri_scheme: fedora + target_type: file module: file locked: false cardinality: 1 diff --git a/modules/islandora_core_feature/config/install/field.storage.media.field_mime_type.yml b/modules/islandora_core_feature/config/install/field.storage.media.field_mime_type.yml index 9ade24b4..8f458941 100644 --- a/modules/islandora_core_feature/config/install/field.storage.media.field_mime_type.yml +++ b/modules/islandora_core_feature/config/install/field.storage.media.field_mime_type.yml @@ -17,7 +17,7 @@ settings: module: core locked: false cardinality: 1 -translatable: false +translatable: true indexes: { } 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 new file mode 100644 index 00000000..aa9c188d --- /dev/null +++ b/modules/islandora_core_feature/config/install/filehash.settings.yml @@ -0,0 +1,5 @@ +algos: + sha1: sha1 + md5: '0' + sha256: '0' +dedupe: false diff --git a/modules/islandora_core_feature/config/install/language.content_settings.media.audio.yml b/modules/islandora_core_feature/config/install/language.content_settings.media.audio.yml index 6af4a1bc..c55d16c9 100644 --- a/modules/islandora_core_feature/config/install/language.content_settings.media.audio.yml +++ b/modules/islandora_core_feature/config/install/language.content_settings.media.audio.yml @@ -8,6 +8,8 @@ dependencies: third_party_settings: content_translation: enabled: true + bundle_settings: + untranslatable_fields_hide: '0' id: media.audio target_entity_type_id: media target_bundle: audio diff --git a/modules/islandora_core_feature/config/install/language.content_settings.media.file.yml b/modules/islandora_core_feature/config/install/language.content_settings.media.file.yml index 3dccf123..e52f830e 100644 --- a/modules/islandora_core_feature/config/install/language.content_settings.media.file.yml +++ b/modules/islandora_core_feature/config/install/language.content_settings.media.file.yml @@ -8,6 +8,8 @@ dependencies: third_party_settings: content_translation: enabled: true + bundle_settings: + untranslatable_fields_hide: '0' id: media.file target_entity_type_id: media target_bundle: file diff --git a/modules/islandora_core_feature/config/install/language.content_settings.media.image.yml b/modules/islandora_core_feature/config/install/language.content_settings.media.image.yml index 653a78e6..5c943aa4 100644 --- a/modules/islandora_core_feature/config/install/language.content_settings.media.image.yml +++ b/modules/islandora_core_feature/config/install/language.content_settings.media.image.yml @@ -8,6 +8,8 @@ dependencies: third_party_settings: content_translation: enabled: true + bundle_settings: + untranslatable_fields_hide: '0' id: media.image target_entity_type_id: media target_bundle: image diff --git a/modules/islandora_core_feature/config/install/language.content_settings.media.video.yml b/modules/islandora_core_feature/config/install/language.content_settings.media.video.yml index 26a7fa51..d92fc6b2 100644 --- a/modules/islandora_core_feature/config/install/language.content_settings.media.video.yml +++ b/modules/islandora_core_feature/config/install/language.content_settings.media.video.yml @@ -8,6 +8,8 @@ dependencies: third_party_settings: content_translation: enabled: true + bundle_settings: + untranslatable_fields_hide: '0' id: media.video target_entity_type_id: media target_bundle: video diff --git a/modules/islandora_core_feature/config/install/language.content_settings.taxonomy_term.tags.yml b/modules/islandora_core_feature/config/install/language.content_settings.taxonomy_term.tags.yml index 60f0ee64..d8ce77b8 100644 --- a/modules/islandora_core_feature/config/install/language.content_settings.taxonomy_term.tags.yml +++ b/modules/islandora_core_feature/config/install/language.content_settings.taxonomy_term.tags.yml @@ -8,6 +8,8 @@ dependencies: third_party_settings: content_translation: enabled: false + bundle_settings: + untranslatable_fields_hide: '0' id: taxonomy_term.tags target_entity_type_id: taxonomy_term target_bundle: tags diff --git a/modules/islandora_core_feature/config/install/system.action.delete_file_from_fedora.yml b/modules/islandora_core_feature/config/install/system.action.delete_file_from_fedora.yml index 6fba1ee0..d16b1524 100644 --- a/modules/islandora_core_feature/config/install/system.action.delete_file_from_fedora.yml +++ b/modules/islandora_core_feature/config/install/system.action.delete_file_from_fedora.yml @@ -7,9 +7,9 @@ dependencies: module: - islandora id: delete_file_from_fedora -label: 'Delete File from Fedora' +label: 'Delete Fedora File from Gemini' type: file plugin: emit_file_event configuration: - queue: islandora-indexing-fcrepo-delete - event: delete + queue: islandora-indexing-fcrepo-file-delete + event: Delete diff --git a/modules/islandora_core_feature/config/install/system.action.index_file_in_fedora.yml b/modules/islandora_core_feature/config/install/system.action.index_file_in_fedora.yml index 6dba92f5..83a7ac33 100644 --- a/modules/islandora_core_feature/config/install/system.action.index_file_in_fedora.yml +++ b/modules/islandora_core_feature/config/install/system.action.index_file_in_fedora.yml @@ -7,9 +7,9 @@ dependencies: module: - islandora id: index_file_in_fedora -label: 'Index File in Fedora' +label: 'Index Fedora File in Gemini' type: file plugin: emit_file_event configuration: queue: islandora-indexing-fcrepo-file - event: update + event: Create 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 53533b2d..b498ba66 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 @@ -6,6 +6,7 @@ dependencies: - islandora_core_feature module: - file + - filehash - rest - serialization id: file_checksum diff --git a/modules/islandora_core_feature/islandora_core_feature.info.yml b/modules/islandora_core_feature/islandora_core_feature.info.yml index 75a92f4b..dd4c7ea4 100644 --- a/modules/islandora_core_feature/islandora_core_feature.info.yml +++ b/modules/islandora_core_feature/islandora_core_feature.info.yml @@ -10,6 +10,7 @@ dependencies: - features - field - file + - filehash - image - islandora - jsonld diff --git a/modules/islandora_demo_feature/config/install/context.context.external_media.yml b/modules/islandora_demo_feature/config/install/context.context.external_media.yml new file mode 100644 index 00000000..dd02c87e --- /dev/null +++ b/modules/islandora_demo_feature/config/install/context.context.external_media.yml @@ -0,0 +1,46 @@ +langcode: en +status: true +dependencies: + module: + - islandora +name: external_media +label: 'External Media' +group: Islandora +description: 'Reactions for media whose source are outside of Fedora' +requireAllConditions: true +disabled: false +conditions: + media_uses_filesystem: + id: media_uses_filesystem + filesystems: + fedora: fedora + negate: 1 + uuid: 4f3c414a-8c94-464c-a4b9-5d3eb2b35e92 + context_mapping: + media: '@islandora.media_route_context_provider:media' + content_entity_type: + id: content_entity_type + types: + media: media + negate: 0 + uuid: c10985ef-16ef-4571-89ad-1a0926c83b83 + context_mapping: + media: '@islandora.media_route_context_provider:media' +reactions: + index: + id: index + actions: + index_media_in_triplestore: index_media_in_triplestore + index_node_in_fedora: index_node_in_fedora + saved: false + delete: + id: delete + actions: + delete_media_from_triplestore: delete_media_from_triplestore + delete_node_from_fedora: delete_node_from_fedora + saved: false + islandora_map_uri_predicate: + id: islandora_map_uri_predicate + drupal_uri_predicate: 'schema:sameAs' + saved: false +weight: 0 diff --git a/modules/islandora_demo_feature/config/install/context.context.media.yml b/modules/islandora_demo_feature/config/install/context.context.fedora_media.yml similarity index 59% rename from modules/islandora_demo_feature/config/install/context.context.media.yml rename to modules/islandora_demo_feature/config/install/context.context.fedora_media.yml index bca5cf39..22dd3943 100644 --- a/modules/islandora_demo_feature/config/install/context.context.media.yml +++ b/modules/islandora_demo_feature/config/install/context.context.fedora_media.yml @@ -1,22 +1,21 @@ langcode: en status: true dependencies: - enforced: - module: - - islandora_demo_feature module: - islandora -name: media -label: Media +name: fedora_media +label: 'Fedora Media' group: Islandora -description: 'All repository media' +description: 'Reactions for media whose source is in Fedora' requireAllConditions: false disabled: false conditions: - is_media: - id: is_media + media_uses_filesystem: + id: media_uses_filesystem + filesystems: + fedora: fedora negate: 0 - uuid: d43b2007-33d2-4503-b4a7-e3597f1e94de + uuid: 7754b373-7734-42ac-ba38-21b8574b60d6 context_mapping: media: '@islandora.media_route_context_provider:media' reactions: @@ -31,4 +30,8 @@ reactions: actions: delete_media_from_triplestore: delete_media_from_triplestore saved: false + islandora_map_uri_predicate: + id: islandora_map_uri_predicate + drupal_uri_predicate: 'schema:sameAs' + saved: false weight: 0 diff --git a/modules/islandora_demo_feature/config/install/context.context.files.yml b/modules/islandora_demo_feature/config/install/context.context.files_in_fedora.yml similarity index 68% rename from modules/islandora_demo_feature/config/install/context.context.files.yml rename to modules/islandora_demo_feature/config/install/context.context.files_in_fedora.yml index 7b68870a..88a3a038 100644 --- a/modules/islandora_demo_feature/config/install/context.context.files.yml +++ b/modules/islandora_demo_feature/config/install/context.context.files_in_fedora.yml @@ -1,22 +1,21 @@ langcode: en status: true dependencies: - enforced: - module: - - islandora_demo_feature module: - islandora -name: files -label: Files +name: files_in_fedora +label: 'Fedora Files' group: Islandora -description: 'All repository files' +description: 'Files in Fedora' requireAllConditions: false disabled: false conditions: - is_file: - id: is_file + file_uses_filesystem: + id: file_uses_filesystem + filesystems: + fedora: fedora negate: 0 - uuid: 19b1b4e1-ea50-41f8-ab5b-8afb83eb5588 + uuid: ea9d2661-2dc1-4480-bc9b-3fedeceba5f9 context_mapping: file: '@islandora.file_route_context_provider:file' reactions: diff --git a/modules/islandora_demo_feature/config/install/context.context.repository_content.yml b/modules/islandora_demo_feature/config/install/context.context.repository_content.yml index de062112..f425d2d2 100644 --- a/modules/islandora_demo_feature/config/install/context.context.repository_content.yml +++ b/modules/islandora_demo_feature/config/install/context.context.repository_content.yml @@ -35,4 +35,8 @@ reactions: delete_node_from_fedora: delete_node_from_fedora delete_node_from_triplestore: delete_node_from_triplestore saved: false + islandora_map_uri_predicate: + id: islandora_map_uri_predicate + drupal_uri_predicate: 'schema:sameAs' + saved: false weight: 0 diff --git a/modules/islandora_demo_feature/config/install/context.context.taxonomy_terms.yml b/modules/islandora_demo_feature/config/install/context.context.taxonomy_terms.yml index 0a5611e5..1aeec4ae 100644 --- a/modules/islandora_demo_feature/config/install/context.context.taxonomy_terms.yml +++ b/modules/islandora_demo_feature/config/install/context.context.taxonomy_terms.yml @@ -13,10 +13,12 @@ description: 'All taxonomy terms' requireAllConditions: false disabled: false conditions: - is_term: - id: is_term + content_entity_type: + id: content_entity_type + types: + taxonomy_term: taxonomy_term negate: 0 - uuid: 71ad4847-3f48-4b16-b355-364672bdfc49 + uuid: cd01ce46-58a9-4d0e-8643-66f981b2c137 context_mapping: taxonomy_term: '@islandora.taxonomy_term_route_context_provider:taxonomy_term' reactions: diff --git a/modules/islandora_demo_feature/config/install/core.base_field_override.node.islandora_object.menu_link.yml b/modules/islandora_demo_feature/config/install/core.base_field_override.node.islandora_object.menu_link.yml new file mode 100644 index 00000000..af6b8e98 --- /dev/null +++ b/modules/islandora_demo_feature/config/install/core.base_field_override.node.islandora_object.menu_link.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - node.type.islandora_object +id: node.islandora_object.menu_link +field_name: menu_link +entity_type: node +bundle: islandora_object +label: 'Menu link' +description: 'Computed menu link for the node (only available during node saving).' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: default + handler_settings: { } +field_type: entity_reference diff --git a/modules/islandora_demo_feature/config/install/core.base_field_override.node.islandora_object.title.yml b/modules/islandora_demo_feature/config/install/core.base_field_override.node.islandora_object.title.yml new file mode 100644 index 00000000..8e314020 --- /dev/null +++ b/modules/islandora_demo_feature/config/install/core.base_field_override.node.islandora_object.title.yml @@ -0,0 +1,17 @@ +langcode: en +status: true +dependencies: + config: + - node.type.islandora_object +id: node.islandora_object.title +field_name: title +entity_type: node +bundle: islandora_object +label: Title +description: '' +required: true +translatable: true +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/modules/islandora_demo_feature/config/install/core.entity_form_display.media.audio.default.yml b/modules/islandora_demo_feature/config/install/core.entity_form_display.media.audio.default.yml index 4f12df02..3f803f2d 100644 --- a/modules/islandora_demo_feature/config/install/core.entity_form_display.media.audio.default.yml +++ b/modules/islandora_demo_feature/config/install/core.entity_form_display.media.audio.default.yml @@ -75,6 +75,9 @@ content: weight: 7 region: content third_party_settings: { } + translation: + weight: 10 + region: content uid: type: entity_reference_autocomplete weight: 4 diff --git a/modules/islandora_demo_feature/config/install/core.entity_form_display.media.file.default.yml b/modules/islandora_demo_feature/config/install/core.entity_form_display.media.file.default.yml index 3ba8df6e..e3ce7a33 100644 --- a/modules/islandora_demo_feature/config/install/core.entity_form_display.media.file.default.yml +++ b/modules/islandora_demo_feature/config/install/core.entity_form_display.media.file.default.yml @@ -75,6 +75,9 @@ content: weight: 7 region: content third_party_settings: { } + translation: + weight: 10 + region: content uid: type: entity_reference_autocomplete weight: 4 diff --git a/modules/islandora_demo_feature/config/install/core.entity_form_display.media.image.default.yml b/modules/islandora_demo_feature/config/install/core.entity_form_display.media.image.default.yml index dd3c1d49..2c42df45 100644 --- a/modules/islandora_demo_feature/config/install/core.entity_form_display.media.image.default.yml +++ b/modules/islandora_demo_feature/config/install/core.entity_form_display.media.image.default.yml @@ -79,6 +79,9 @@ content: weight: 7 region: content third_party_settings: { } + translation: + weight: 10 + region: content uid: type: entity_reference_autocomplete weight: 4 diff --git a/modules/islandora_demo_feature/config/install/core.entity_form_display.media.video.default.yml b/modules/islandora_demo_feature/config/install/core.entity_form_display.media.video.default.yml index 752c363e..2d7ef651 100644 --- a/modules/islandora_demo_feature/config/install/core.entity_form_display.media.video.default.yml +++ b/modules/islandora_demo_feature/config/install/core.entity_form_display.media.video.default.yml @@ -75,6 +75,9 @@ content: weight: 7 region: content third_party_settings: { } + translation: + weight: 10 + region: content uid: type: entity_reference_autocomplete weight: 4 diff --git a/modules/islandora_demo_feature/config/install/core.entity_form_display.node.islandora_object.default.yml b/modules/islandora_demo_feature/config/install/core.entity_form_display.node.islandora_object.default.yml index 4bf27e52..5fa9b17e 100644 --- a/modules/islandora_demo_feature/config/install/core.entity_form_display.node.islandora_object.default.yml +++ b/modules/islandora_demo_feature/config/install/core.entity_form_display.node.islandora_object.default.yml @@ -90,6 +90,9 @@ content: size: 60 placeholder: '' third_party_settings: { } + translation: + weight: 10 + region: content uid: type: entity_reference_autocomplete weight: 4 diff --git a/modules/islandora_demo_feature/config/install/language.content_settings.node.islandora_object.yml b/modules/islandora_demo_feature/config/install/language.content_settings.node.islandora_object.yml index dd28a5a9..48572fe7 100644 --- a/modules/islandora_demo_feature/config/install/language.content_settings.node.islandora_object.yml +++ b/modules/islandora_demo_feature/config/install/language.content_settings.node.islandora_object.yml @@ -8,6 +8,8 @@ dependencies: third_party_settings: content_translation: enabled: true + bundle_settings: + untranslatable_fields_hide: '0' id: node.islandora_object target_entity_type_id: node target_bundle: islandora_object diff --git a/modules/islandora_demo_feature/config/install/system.action.generate_a_service_file_from_preservation_master.yml b/modules/islandora_demo_feature/config/install/system.action.generate_a_service_file_from_preservation_master.yml index 3c380e1a..58eaf85e 100644 --- a/modules/islandora_demo_feature/config/install/system.action.generate_a_service_file_from_preservation_master.yml +++ b/modules/islandora_demo_feature/config/install/system.action.generate_a_service_file_from_preservation_master.yml @@ -17,3 +17,5 @@ configuration: derivative_term_uri: 'http://pcdm.org/use#ServiceFile' mimetype: image/jpeg args: '' + scheme: public + path: '[date:custom:Y]-[date:custom:m]/[node:nid]-[term:name].jpg' diff --git a/modules/islandora_demo_feature/config/install/system.action.generate_a_thumbnail_from_an_image_service_file.yml b/modules/islandora_demo_feature/config/install/system.action.generate_a_thumbnail_from_an_image_service_file.yml index 73d52560..4109dd4e 100644 --- a/modules/islandora_demo_feature/config/install/system.action.generate_a_thumbnail_from_an_image_service_file.yml +++ b/modules/islandora_demo_feature/config/install/system.action.generate_a_thumbnail_from_an_image_service_file.yml @@ -17,3 +17,5 @@ configuration: derivative_term_uri: 'http://pcdm.org/use#ThumbnailImage' mimetype: image/jpeg args: '-thumbnail 100x100' + scheme: public + path: '[date:custom:Y]-[date:custom:m]/[node:nid]-[term:name].jpg' diff --git a/src/Controller/MediaSourceController.php b/src/Controller/MediaSourceController.php index 5bb7f3ad..28377ddc 100644 --- a/src/Controller/MediaSourceController.php +++ b/src/Controller/MediaSourceController.php @@ -143,17 +143,7 @@ class MediaSourceController extends ControllerBase { throw new BadRequestHttpException("Missing Content-Type header"); } - $content_disposition = $request->headers->get('Content-Disposition', ""); - - if (empty($content_disposition)) { - throw new BadRequestHttpException("Missing Content-Disposition header"); - } - - $matches = []; - if (!preg_match('/attachment; filename="(.*)"/', $content_disposition, $matches)) { - throw new BadRequestHttpException("Malformed Content-Disposition header"); - } - $filename = $matches[1]; + $content_location = $request->headers->get('Content-Location', ""); // Since we create both a Media and its File, // start a transaction. @@ -166,7 +156,7 @@ class MediaSourceController extends ControllerBase { $taxonomy_term, $request->getContent(TRUE), $content_type, - $filename + $content_location ); // We return the media if it was newly created. diff --git a/src/EventGenerator/EmitEvent.php b/src/EventGenerator/EmitEvent.php index f76f97ba..32922e61 100644 --- a/src/EventGenerator/EmitEvent.php +++ b/src/EventGenerator/EmitEvent.php @@ -4,6 +4,7 @@ namespace Drupal\islandora\EventGenerator; use Drupal\Core\Access\AccessResult; use Drupal\Core\Action\ConfigurableActionBase; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManager; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -160,7 +161,7 @@ abstract class EmitEvent extends ConfigurableActionBase implements ContainerFact /** * Override this function to control what gets encoded as a json note. */ - protected function generateData($entity) { + protected function generateData(EntityInterface $entity) { return $this->configuration; } diff --git a/src/EventGenerator/EventGenerator.php b/src/EventGenerator/EventGenerator.php index 653e3b2c..edc32183 100644 --- a/src/EventGenerator/EventGenerator.php +++ b/src/EventGenerator/EventGenerator.php @@ -21,6 +21,15 @@ class EventGenerator implements EventGeneratorInterface { $user_url = $user->toUrl()->setAbsolute()->toString(); + if ($entity instanceof FileInterface) { + $entity_url = $entity->url(); + $mimetype = $entity->getMimeType(); + } + else { + $entity_url = $entity->toUrl()->setAbsolute()->toString(); + $mimetype = 'text/html'; + } + $event = [ "@context" => "https://www.w3.org/ns/activitystreams", "actor" => [ @@ -38,7 +47,15 @@ class EventGenerator implements EventGeneratorInterface { ], "object" => [ "id" => "urn:uuid:{$entity->uuid()}", - "url" => $entity instanceof FileInterface ? $this->generateFileLinks($entity) : $this->generateRestLinks($entity), + "url" => [ + [ + "name" => "Canonical", + "type" => "Link", + "href" => $entity_url, + "mediaType" => $mimetype, + "rel" => "canonical", + ], + ], ], ]; @@ -53,6 +70,24 @@ class EventGenerator implements EventGeneratorInterface { $event["summary"] = ucfirst($data["event"]) . " a " . ucfirst($entity_type); } + // Add REST links for non-file entities. + if ($entity_type != 'file') { + $event['object']['url'][] = [ + "name" => "JSON", + "type" => "Link", + "href" => "$entity_url?_format=json", + "mediaType" => "application/json", + "rel" => "alternate", + ]; + $event['object']['url'][] = [ + "name" => "JSONLD", + "type" => "Link", + "href" => "$entity_url?_format=jsonld", + "mediaType" => "application/ld+json", + "rel" => "alternate", + ]; + } + unset($data["event"]); unset($data["queue"]); @@ -67,69 +102,4 @@ class EventGenerator implements EventGeneratorInterface { return json_encode($event); } - /** - * Generates file urls. - * - * @param \Drupal\file\FileInterface $file - * The file. - * - * @return array - * AS2 Links. - */ - protected function generateFileLinks(FileInterface $file) { - $file_url = $file->url(); - $checksum_url = Url::fromRoute('view.file_checksum.rest_export_1', ['file' => $file->id()]) - ->setAbsolute() - ->toString(); - - return [ - [ - "name" => "File", - "type" => "Link", - "href" => "$file_url", - "mediaType" => $file->getMimeType(), - ], - [ - "name" => "Checksum", - "type" => "Link", - "href" => "$checksum_url?_format=json", - "mediaType" => "application/json", - ], - ]; - } - - /** - * Generates REST urls. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - * - * @return array - * AS2 Links. - */ - protected function generateRestLinks(EntityInterface $entity) { - $url = $entity->toUrl()->setAbsolute()->toString(); - return [ - [ - "name" => "Canoncial", - "type" => "Link", - "href" => "$url", - "mediaType" => "text/html", - "rel" => "canonical", - ], - [ - "name" => "JSONLD", - "type" => "Link", - "href" => "$url?_format=jsonld", - "mediaType" => "application/ld+json", - ], - [ - "name" => "JSON", - "type" => "Link", - "href" => "$url?_format=json", - "mediaType" => "application/json", - ], - ]; - } - } diff --git a/src/Flysystem/Adapter/FedoraAdapter.php b/src/Flysystem/Adapter/FedoraAdapter.php new file mode 100644 index 00000000..8fd91ca3 --- /dev/null +++ b/src/Flysystem/Adapter/FedoraAdapter.php @@ -0,0 +1,324 @@ +fedora = $fedora; + $this->mimeTypeGuesser = $mime_type_guesser; + } + + /** + * {@inheritdoc} + */ + public function has($path) { + $response = $this->fedora->getResourceHeaders($path); + return $response->getStatusCode() == 200; + } + + /** + * {@inheritdoc} + */ + public function read($path) { + $meta = $this->readStream($path); + + if (!$meta) { + return FALSE; + } + + if (isset($meta['stream'])) { + $meta['contents'] = stream_get_contents($meta['stream']); + fclose($meta['stream']); + unset($meta['stream']); + } + + return $meta; + } + + /** + * {@inheritdoc} + */ + public function readStream($path) { + $response = $this->fedora->getResource($path); + + if ($response->getStatusCode() != 200) { + return FALSE; + } + + $meta = $this->getMetadataFromHeaders($response); + $meta['path'] = $path; + + if ($meta['type'] == 'file') { + $meta['stream'] = StreamWrapper::getResource($response->getBody()); + } + + return $meta; + } + + /** + * {@inheritdoc} + */ + public function getMetadata($path) { + $response = $this->fedora->getResourceHeaders($path); + + if ($response->getStatusCode() != 200) { + return FALSE; + } + + $meta = $this->getMetadataFromHeaders($response); + $meta['path'] = $path; + return $meta; + } + + /** + * {@inheritdoc} + */ + public function getSize($path) { + return $this->getMetadata($path); + } + + /** + * {@inheritdoc} + */ + public function getMimetype($path) { + return $this->getMetadata($path); + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($path) { + return $this->getMetadata($path); + } + + /** + * Gets metadata from response headers. + * + * @param \GuzzleHttp\Psr7\Response $response + * Response. + */ + protected function getMetadataFromHeaders(Response $response) { + $last_modified = \DateTime::createFromFormat( + \DateTime::RFC1123, + $response->getHeader('Last-Modified')[0] + ); + + // NonRDFSource's are considered files. Everything else is a + // directory. + $type = 'dir'; + $links = Psr7\parse_header($response->getHeader('Link')); + foreach ($links as $link) { + if ($link['rel'] == 'type' && $link[0] == '') { + $type = 'file'; + break; + } + } + + $meta = [ + 'type' => $type, + 'timestamp' => $last_modified->getTimestamp(), + ]; + + if ($type == 'file') { + $meta['size'] = $response->getHeader('Content-Length')[0]; + $meta['mimetype'] = $response->getHeader('Content-Type')[0]; + } + + return $meta; + } + + /** + * {@inheritdoc} + */ + public function listContents($directory = '', $recursive = FALSE) { + // Strip leading and trailing whitespace and /'s. + $normalized = trim($directory, ' \t\n\r\0\x0B/'); + + // Exit early if it's a file. + $meta = $this->getMetadata($normalized); + if ($meta['type'] == 'file') { + return []; + } + // Get the resource from Fedora. + $response = $this->fedora->getResource($normalized, ['Accept' => 'application/ld+json']); + $jsonld = (string) $response->getBody(); + $graph = json_decode($jsonld, TRUE); + + $uri = $this->fedora->getBaseUri() . $normalized; + + // Hack it out of the graph. + // There may be more than one resource returned. + $resource = []; + foreach ($graph as $elem) { + if (isset($elem['@id']) && $elem['@id'] == $uri) { + $resource = $elem; + break; + } + } + + // Exit early if resource doesn't contain other resources. + if (!isset($resource['http://www.w3.org/ns/ldp#contains'])) { + return []; + } + + // Collapse uris to a single array. + $contained = array_map( + function ($elem) { + return $elem['@id']; + }, + $resource['http://www.w3.org/ns/ldp#contains'] + ); + + // Exit early if not recursive. + if (!$recursive) { + // Transform results to their flysystem metadata. + return array_map( + [$this, 'transformToMetadata'], + $contained + ); + } + + // Recursively get containment for ancestors. + $ancestors = []; + + foreach ($contained as $child_uri) { + $child_directory = explode($this->fedora->getBaseUri(), $child_uri)[1]; + $ancestors = array_merge($this->listContents($child_directory, $recursive), $ancestors); + } + + // // Transform results to their flysystem metadata. + return array_map( + [$this, 'transformToMetadata'], + array_merge($ancestors, $contained) + ); + } + + /** + * Normalizes data for listContents(). + * + * @param string $uri + * Uri. + */ + protected function transformToMetadata($uri) { + if (is_array($uri)) { + return $uri; + } + $exploded = explode($this->fedora->getBaseUri(), $uri); + return $this->getMetadata($exploded[1]); + } + + /** + * {@inheritdoc} + */ + public function write($path, $contents, Config $config) { + $headers = [ + 'Content-Type' => $this->mimeTypeGuesser->guess($path), + ]; + + $response = $this->fedora->saveResource( + $path, + $contents, + $headers + ); + + $code = $response->getStatusCode(); + if (!in_array($code, [201, 204])) { + return FALSE; + } + + return $this->getMetadata($path); + } + + /** + * {@inheritdoc} + */ + public function writeStream($path, $contents, Config $config) { + return $this->write($path, $contents, $config); + } + + /** + * {@inheritdoc} + */ + public function update($path, $contents, Config $config) { + return $this->write($path, $contents, $config); + } + + /** + * {@inheritdoc} + */ + public function updateStream($path, $contents, Config $config) { + return $this->write($path, $contents, $config); + } + + /** + * {@inheritdoc} + */ + public function delete($path) { + $response = $this->fedora->deleteResource($path); + + $code = $response->getStatusCode(); + return in_array($code, [204, 404]); + } + + /** + * {@inheritdoc} + */ + public function deleteDir($dirname) { + return $this->delete($dirname); + } + + /** + * {@inheritdoc} + */ + public function rename($path, $newpath) { + if ($this->copy($path, $newpath)) { + return $this->delete($path); + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function createDir($dirname, Config $config) { + $response = $this->fedora->saveResource( + $dirname + ); + + $code = $response->getStatusCode(); + if (!in_array($code, [201, 204])) { + return FALSE; + } + + return $this->getMetadata($dirname); + } + +} diff --git a/src/Flysystem/Fedora.php b/src/Flysystem/Fedora.php new file mode 100644 index 00000000..772e89e6 --- /dev/null +++ b/src/Flysystem/Fedora.php @@ -0,0 +1,124 @@ +fedora = $fedora; + $this->mimeTypeGuesser = $mime_type_guesser; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + // Construct Authorization header using jwt token. + $jwt = $container->get('jwt.authentication.jwt'); + $auth = 'Bearer ' . $jwt->generateToken(); + + // Construct guzzle client to middleware that adds the header. + $stack = HandlerStack::create(); + $stack->push(static::addHeader('Authorization', $auth)); + $client = new Client([ + 'handler' => $stack, + 'base_uri' => $configuration['root'], + ]); + $fedora = new FedoraApi($client); + + // Return it. + return new static( + $fedora, + $container->get('file.mime_type.guesser') + ); + } + + /** + * Guzzle middleware to add a header to outgoing requests. + * + * @param string $header + * Header name. + * @param string $value + * Header value. + */ + public static function addHeader($header, $value) { + return function (callable $handler) use ($header, $value) { + return function ( + RequestInterface $request, + array $options + ) use ( +$handler, + $header, + $value +) { + $request = $request->withHeader($header, $value); + return $handler($request, $options); + }; + }; + } + + /** + * {@inheritdoc} + */ + public function getAdapter() { + return new FedoraAdapter($this->fedora, $this->mimeTypeGuesser); + } + + /** + * {@inheritdoc} + */ + public function ensure($force = FALSE) { + // Check fedora root for sanity. + $response = $this->fedora->getResourceHeaders(''); + + if ($response->getStatusCode() != 200) { + return [[ + 'severity' => RfcLogLevel::ERROR, + 'message' => '%url returned %status', + 'context' => [ + '%url' => $this->fedora->getBaseUri(), + '%status' => $response->getStatusCode(), + ], + ], + ]; + } + + return []; + } + +} diff --git a/src/IslandoraUtils.php b/src/IslandoraUtils.php index acac02b8..41fd44ba 100644 --- a/src/IslandoraUtils.php +++ b/src/IslandoraUtils.php @@ -7,8 +7,9 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityFieldManager; use Drupal\Core\Entity\EntityTypeManager; use Drupal\Core\Entity\Query\QueryFactory; -use Drupal\Core\StreamWrapper\StreamWrapperManager; +use Drupal\Core\Site\Settings; use Drupal\file\FileInterface; +use Drupal\flysystem\FlysystemFactory; use Drupal\islandora\ContextProvider\NodeContextProvider; use Drupal\islandora\ContextProvider\MediaContextProvider; use Drupal\islandora\ContextProvider\FileContextProvider; @@ -55,11 +56,11 @@ class IslandoraUtils { protected $contextManager; /** - * Stream wrapper manager. + * Flysystem factory. * - * @var \Drupal\Core\StreamWrapper\StreamWrapperManager + * @var \Drupal\flysystem\FlysystemFactory */ - protected $streamWrapperManager; + protected $flysystemFactory; /** * Constructor. @@ -72,21 +73,21 @@ class IslandoraUtils { * Entity query. * @param \Drupal\context\ContextManager $context_manager * Context manager. - * @param \Drupal\Core\StreamWrapper\StreamWrapperManager $stream_wrapper_manager - * Stream wrapper manager. + * @param \Drupal\flysystem\FlysystemFactory $flysystem_factory + * Flysystem factory. */ public function __construct( EntityTypeManager $entity_type_manager, EntityFieldManager $entity_field_manager, QueryFactory $entity_query, ContextManager $context_manager, - StreamWrapperManager $stream_wrapper_manager + FlysystemFactory $flysystem_factory ) { $this->entityTypeManager = $entity_type_manager; $this->entityFieldManager = $entity_field_manager; $this->entityQuery = $entity_query; $this->contextManager = $context_manager; - $this->streamWrapperManager = $stream_wrapper_manager; + $this->flysystemFactory = $flysystem_factory; } /** @@ -367,4 +368,18 @@ class IslandoraUtils { return FALSE; } + /** + * Returns a list of all available filesystem schemes. + * + * @return String[] + * List of all available filesystem schemes. + */ + public function getFilesystemSchemes() { + $schemes = ['public']; + if (!empty(Settings::get('file_private_path'))) { + $schemes[] = 'private'; + } + return array_merge($schemes, $this->flysystemFactory->getSchemes()); + } + } diff --git a/src/MediaSource/MediaSourceService.php b/src/MediaSource/MediaSourceService.php index 7e836d96..01b6b1f3 100644 --- a/src/MediaSource/MediaSourceService.php +++ b/src/MediaSource/MediaSourceService.php @@ -2,13 +2,11 @@ namespace Drupal\islandora\MediaSource; -use Drupal\Component\Render\PlainTextOutput; use Drupal\Core\Entity\EntityTypeManager; use Drupal\Core\Entity\Query\QueryFactory; +use Drupal\Core\File\FileSystem; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\Core\StreamWrapper\StreamWrapperManager; -use Drupal\Core\Utility\Token; use Drupal\file\FileInterface; use Drupal\media\MediaInterface; use Drupal\media\MediaTypeInterface; @@ -37,13 +35,6 @@ class MediaSourceService { */ protected $account; - /** - * Stream wrapper manager. - * - * @var \Drupal\Core\StreamWrapper\StreamWrapperManager - */ - protected $streamWrapperManager; - /** * Language manager. * @@ -52,18 +43,18 @@ class MediaSourceService { protected $languageManager; /** - * Token service. + * Entity query. * - * @var \Drupal\Core\Utility\Token + * @var \Drupal\Core\Entity\Query\QueryFactory */ - protected $token; + protected $entityQuery; /** - * Entity query. + * File system service. * - * @var \Drupal\Core\Entity\Query\QueryFactory + * @var \Drupal\Core\File\FileSystem */ - protected $entityQuery; + protected $fileSystem; /** * Constructor. @@ -72,29 +63,25 @@ class MediaSourceService { * The entity type manager. * @param \Drupal\Core\Session\AccountInterface $account * The current user. - * @param \Drupal\Core\StreamWrapper\StreamWrapperManager $stream_wrapper_manager - * Stream wrapper manager. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * Language manager. - * @param \Drupal\Core\Utility\Token $token - * Token service. * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query * Entity query. + * @param \Drupal\Core\File\FileSystem $file_system + * File system service. */ public function __construct( EntityTypeManager $entity_type_manager, AccountInterface $account, - StreamWrapperManager $stream_wrapper_manager, LanguageManagerInterface $language_manager, - Token $token, - QueryFactory $entity_query + QueryFactory $entity_query, + FileSystem $file_system ) { $this->entityTypeManager = $entity_type_manager; $this->account = $account; - $this->streamWrapperManager = $stream_wrapper_manager; $this->languageManager = $language_manager; - $this->token = $token; $this->entityQuery = $entity_query; + $this->fileSystem = $file_system; } /** @@ -120,6 +107,32 @@ class MediaSourceService { return $type_configuration['source_field']; } + /** + * Gets the value of a source field for a Media. + * + * @param \Drupal\media\MediaInterface $media + * Media whose source field you are searching for. + * + * @return \Drupal\file\FileInterface + * File if it exists + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function getSourceFile(MediaInterface $media) { + // Get the source field for the media type. + $source_field = $this->getSourceFieldName($media->bundle()); + + if (empty($source_field)) { + throw new NotFoundHttpException("Source field not set for {$media->bundle()} media"); + } + + // Get the file from the media. + $files = $media->get($source_field)->referencedEntities(); + $file = reset($files); + + return $file; + } + /** * Updates a media's source field with the supplied resource. * @@ -137,31 +150,24 @@ class MediaSourceService { $resource, $mimetype ) { - // Get the source field for the media type. $source_field = $this->getSourceFieldName($media->bundle()); - - if (empty($source_field)) { - throw new NotFoundHttpException("Source field not set for {$media->bundle()} media"); - } - - // Get the file from the media. - $files = $media->get($source_field)->referencedEntities(); - $file = reset($files); + $file = $this->getSourceFile($media); // Update it. $this->updateFile($file, $resource, $mimetype); + $file->save(); // Set fields provided by type plugin and mapped in bundle configuration // for the media. foreach ($media->bundle->entity->getFieldMap() as $source => $destination) { if ($media->hasField($destination) && $value = $media->getSource()->getMetadata($media, $source)) { $media->set($destination, $value); - // Ensure width and height are updated on File reference when it's an - // image. Otherwise you run into scaling problems when updating images - // with different sizes. - if ($source == 'width' || $source == 'height') { - $media->get($source_field)->first()->set($source, $value); - } + } + // Ensure width and height are updated on File reference when it's an + // image. Otherwise you run into scaling problems when updating images + // with different sizes. + if ($source == 'width' || $source == 'height') { + $media->get($source_field)->first()->set($source, $value); } } @@ -180,11 +186,13 @@ class MediaSourceService { */ protected function updateFile(FileInterface $file, $resource, $mimetype = NULL) { $uri = $file->getFileUri(); - $file_stream_wrapper = $this->streamWrapperManager->getViaUri($uri); - $path = ""; - $file_stream_wrapper->stream_open($uri, 'w', STREAM_REPORT_ERRORS, $path); - $file_stream = $file_stream_wrapper->stream_cast(STREAM_CAST_AS_STREAM); - $content_length = stream_copy_to_stream($resource, $file_stream); + + $destination = fopen($uri, 'wb'); + if (!$destination) { + throw new HttpException(500, "File $uri could not be opened to write."); + } + + $content_length = stream_copy_to_stream($resource, $destination); if ($content_length === FALSE) { throw new HttpException(500, "Request body could not be copied to $uri"); @@ -192,8 +200,8 @@ class MediaSourceService { if ($content_length === 0) { // Clean up the newly created, empty file. - $file_stream_wrapper->unlink($uri); - throw new BadRequestHttpException("The request contents are empty."); + unlink($uri); + throw new HttpException(400, "No bytes were copied to $uri"); } if (!empty($mimetype)) { @@ -217,8 +225,8 @@ class MediaSourceService { * New file contents as a resource. * @param string $mimetype * New mimetype of contents. - * @param string $filename - * New filename for contents. + * @param string $content_location + * Drupal/PHP stream wrapper for where to upload the binary. * * @throws HttpException */ @@ -228,9 +236,8 @@ class MediaSourceService { TermInterface $taxonomy_term, $resource, $mimetype, - $filename + $content_location ) { - $existing = $this->entityQuery->get('media') ->condition('field_media_of', $node->id()) ->condition('field_tags', $taxonomy_term->id()) @@ -256,22 +263,11 @@ class MediaSourceService { throw new NotFoundHttpException("Source field not set for $bundle media"); } - // Load its config to get file extensions and upload path. - $source_field_config = $this->entityTypeManager->getStorage('field_config')->load("media.$bundle.$source_field"); - - // Construct the destination uri. - $directory = $source_field_config->getSetting('file_directory'); - $directory = trim($directory, '/'); - $directory = PlainTextOutput::renderFromHtml($this->token->replace($directory, ['node' => $node])); - $scheme = file_default_scheme(); - $destination_directory = "$scheme://$directory"; - $destination = "$destination_directory/$filename"; - // Construct the File. $file = $this->entityTypeManager->getStorage('file')->create([ 'uid' => $this->account->id(), - 'uri' => $destination, - 'filename' => $filename, + 'uri' => $content_location, + 'filename' => $this->fileSystem->basename($content_location), 'filemime' => $mimetype, 'status' => FILE_STATUS_PERMANENT, ]); @@ -285,7 +281,8 @@ class MediaSourceService { throw new BadRequestHttpException("Invalid file extension. Valid types are $valid_extensions"); } - if (!file_prepare_directory($destination_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { + $directory = $this->fileSystem->dirname($content_location); + if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { throw new HttpException(500, "The destination directory does not exist, could not be created, or is not writable"); } @@ -297,7 +294,7 @@ class MediaSourceService { $media_struct = [ 'bundle' => $bundle, 'uid' => $this->account->id(), - 'name' => $filename, + 'name' => $file->getFilename(), 'langcode' => $this->languageManager->getDefaultLanguage()->getId(), "$source_field" => [ 'target_id' => $file->id(), @@ -312,7 +309,7 @@ class MediaSourceService { // Set alt text. if ($source_field_config->getSetting('alt_field') && $source_field_config->getSetting('alt_field_required')) { - $media_struct[$source_field]['alt'] = $filename; + $media_struct[$source_field]['alt'] = $file->getFilename; } $media = $this->entityTypeManager->getStorage('media')->create($media_struct); diff --git a/src/Plugin/Action/EmitFileEvent.php b/src/Plugin/Action/EmitFileEvent.php index 105863a5..8f4ebe4c 100644 --- a/src/Plugin/Action/EmitFileEvent.php +++ b/src/Plugin/Action/EmitFileEvent.php @@ -2,7 +2,16 @@ namespace Drupal\islandora\Plugin\Action; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeManager; +use Drupal\Core\File\FileSystem; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Site\Settings; +use Drupal\jwt\Authentication\Provider\JwtAuth; use Drupal\islandora\EventGenerator\EmitEvent; +use Drupal\islandora\EventGenerator\EventGeneratorInterface; +use Stomp\StatefulStomp; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Emits a File event. @@ -13,4 +22,91 @@ use Drupal\islandora\EventGenerator\EmitEvent; * type = "file" * ) */ -class EmitFileEvent extends EmitEvent {} +class EmitFileEvent extends EmitEvent { + + /** + * File system service. + * + * @var \Drupal\Core\File\FileSystem + */ + protected $fileSystem; + + /** + * Constructs a EmitEvent action. + * + * @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\Core\Session\AccountInterface $account + * Current user. + * @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager + * Entity type manager. + * @param \Drupal\islandora\EventGenerator\EventGeneratorInterface $event_generator + * EventGenerator service to serialize AS2 events. + * @param \Stomp\StatefulStomp $stomp + * Stomp client. + * @param \Drupal\jwt\Authentication\Provider\JwtAuth $auth + * JWT Auth client. + * @param \Drupal\Core\File\FileSystem $file_system + * File system service. + */ + public function __construct( + array $configuration, + $plugin_id, + $plugin_definition, + AccountInterface $account, + EntityTypeManager $entity_type_manager, + EventGeneratorInterface $event_generator, + StatefulStomp $stomp, + JwtAuth $auth, + FileSystem $file_system + ) { + parent::__construct( + $configuration, + $plugin_id, + $plugin_definition, + $account, + $entity_type_manager, + $event_generator, + $stomp, + $auth + ); + $this->fileSystem = $file_system; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('current_user'), + $container->get('entity_type.manager'), + $container->get('islandora.eventgenerator'), + $container->get('islandora.stomp'), + $container->get('jwt.authentication.jwt'), + $container->get('file_system') + ); + } + + /** + * {@inheritdoc} + */ + protected function generateData(EntityInterface $entity) { + $uri = $entity->getFileUri(); + $scheme = $this->fileSystem->uriScheme($uri); + $flysystem_config = Settings::get('flysystem'); + + $data = parent::generateData($entity); + if (isset($flysystem_config[$scheme]) && $flysystem_config[$scheme]['driver'] == 'fedora') { + $data['fedora_uri'] = str_replace("$scheme://", $flysystem_config[$scheme]['config']['root'], $uri); + } + return $data; + } + +} diff --git a/src/Plugin/Action/EmitMediaEvent.php b/src/Plugin/Action/EmitMediaEvent.php index 48042e01..7b366da0 100644 --- a/src/Plugin/Action/EmitMediaEvent.php +++ b/src/Plugin/Action/EmitMediaEvent.php @@ -2,7 +2,15 @@ namespace Drupal\islandora\Plugin\Action; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeManager; +use Drupal\Core\Session\AccountInterface; +use Drupal\jwt\Authentication\Provider\JwtAuth; use Drupal\islandora\EventGenerator\EmitEvent; +use Drupal\islandora\EventGenerator\EventGeneratorInterface; +use Drupal\islandora\MediaSource\MediaSourceService; +use Stomp\StatefulStomp; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Emits a Media event. @@ -13,4 +21,85 @@ use Drupal\islandora\EventGenerator\EmitEvent; * type = "media" * ) */ -class EmitMediaEvent extends EmitEvent {} +class EmitMediaEvent extends EmitEvent { + + /** + * Media source service. + * + * @var \Drupal\islandora\MediaSource\MediaSourceService + */ + protected $mediaSource; + + /** + * Constructs a EmitEvent action. + * + * @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\Core\Session\AccountInterface $account + * Current user. + * @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager + * Entity type manager. + * @param \Drupal\islandora\EventGenerator\EventGeneratorInterface $event_generator + * EventGenerator service to serialize AS2 events. + * @param \Stomp\StatefulStomp $stomp + * Stomp client. + * @param \Drupal\jwt\Authentication\Provider\JwtAuth $auth + * JWT Auth client. + * @param \Drupal\islandora\MediaSource\MediaSourceService $media_source + * Media source service. + */ + public function __construct( + array $configuration, + $plugin_id, + $plugin_definition, + AccountInterface $account, + EntityTypeManager $entity_type_manager, + EventGeneratorInterface $event_generator, + StatefulStomp $stomp, + JwtAuth $auth, + MediaSourceService $media_source + ) { + parent::__construct( + $configuration, + $plugin_id, + $plugin_definition, + $account, + $entity_type_manager, + $event_generator, + $stomp, + $auth + ); + $this->mediaSource = $media_source; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('current_user'), + $container->get('entity_type.manager'), + $container->get('islandora.eventgenerator'), + $container->get('islandora.stomp'), + $container->get('jwt.authentication.jwt'), + $container->get('islandora.media_source_service') + ); + } + + /** + * {@inheritdoc} + */ + protected function generateData(EntityInterface $entity) { + $data = parent::generateData($entity); + $data['source_field'] = $this->mediaSource->getSourceFieldName($entity->bundle()); + return $data; + } + +} diff --git a/src/Plugin/Condition/ContentEntityType.php b/src/Plugin/Condition/ContentEntityType.php new file mode 100644 index 00000000..71379f94 --- /dev/null +++ b/src/Plugin/Condition/ContentEntityType.php @@ -0,0 +1,105 @@ + $this->t('Content Entity Types'), + '#type' => 'checkboxes', + '#options' => [ + 'node' => $this->t('Node'), + 'media' => $this->t('Media'), + 'file' => $this->t('File'), + 'taxonomy_term' => $this->t('Taxonomy Term'), + ], + '#default_value' => $this->configuration['types'], + ]; + return parent::buildConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['types'] = array_filter($form_state->getValue('types')); + parent::submitConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + if (empty($this->configuration['types']) && !$this->isNegated()) { + return TRUE; + } + + foreach ($this->configuration['types'] as $type) { + if ($this->getContext($type)->hasContextValue()) { + $entity = $this->getContextValue($type); + if ($entity && $entity->getEntityTypeId() == $type) { + return TRUE; + } + } + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function summary() { + if (count($this->configuration['types']) > 1) { + $types = $this->configuration['types']; + $last = array_pop($types); + $types = implode(', ', $types); + return $this->t( + 'The content entity is a @types or @last', + [ + '@types' => $types, + '@last' => $last, + ] + ); + } + $type = reset($this->configuration['types']); + return $this->t( + 'The content entity is a @type', + [ + '@type' => $type, + ] + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array_merge( + ['types' => []], + parent::defaultConfiguration() + ); + } + +} diff --git a/src/Plugin/Condition/FileUsesFilesystem.php b/src/Plugin/Condition/FileUsesFilesystem.php new file mode 100644 index 00000000..99377db4 --- /dev/null +++ b/src/Plugin/Condition/FileUsesFilesystem.php @@ -0,0 +1,168 @@ +utils = $utils; + $this->fileSystem = $file_system; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('islandora.utils'), + $container->get('file_system') + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $schemes = $this->utils->getFilesystemSchemes(); + $options = array_combine($schemes, $schemes); + + $form['filesystems'] = [ + '#title' => $this->t('Filesystems'), + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => $this->configuration['filesystems'], + ]; + return parent::buildConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['filesystems'] = array_filter($form_state->getValue('filesystems')); + parent::submitConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function summary() { + if (count($this->configuration['filesystems']) > 1) { + $filesystems = $this->configuration['filesystems']; + $last = array_pop($filesystems); + $filesystems = implode(', ', $filesystems); + return $this->t( + 'The file uses @filesystems or @last', + [ + '@filesystems' => $filesystems, + '@last' => $last, + ] + ); + } + $filesystem = reset($this->configuration['filesystems']); + return $this->t( + 'The file uses @filesystem', + [ + '@filesystem' => $filesystem, + ] + ); + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + if (empty($this->configuration['filesystems']) && !$this->isNegated()) { + return TRUE; + } + + $file = $this->getContextValue('file'); + return $this->evaluateFile($file); + } + + /** + * The actual evaluate function. + * + * @param \Drupal\file\FileInterface $file + * File. + * + * @return bool + * TRUE on success. + */ + protected function evaluateFile(FileInterface $file) { + $uri = $file->getFileUri(); + $scheme = $this->fileSystem->uriScheme($uri); + return !empty($this->configuration['filesystems'][$scheme]); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array_merge( + ['filesystems' => []], + parent::defaultConfiguration() + ); + } + +} diff --git a/src/Plugin/Condition/IsFile.php b/src/Plugin/Condition/IsFile.php deleted file mode 100644 index 2006b1a7..00000000 --- a/src/Plugin/Condition/IsFile.php +++ /dev/null @@ -1,35 +0,0 @@ -t('The entity is a File'); - } - - /** - * {@inheritdoc} - */ - public function evaluate() { - return $this->getContextValue('file') instanceof FileInterface; - } - -} diff --git a/src/Plugin/Condition/IsMedia.php b/src/Plugin/Condition/IsMedia.php deleted file mode 100644 index 6576d7d7..00000000 --- a/src/Plugin/Condition/IsMedia.php +++ /dev/null @@ -1,35 +0,0 @@ -t('The entity is a Media'); - } - - /** - * {@inheritdoc} - */ - public function evaluate() { - return $this->getContextValue('media') instanceof MediaInterface; - } - -} diff --git a/src/Plugin/Condition/IsNode.php b/src/Plugin/Condition/IsNode.php deleted file mode 100644 index c7b45131..00000000 --- a/src/Plugin/Condition/IsNode.php +++ /dev/null @@ -1,35 +0,0 @@ -t('The entity is a Node'); - } - - /** - * {@inheritdoc} - */ - public function evaluate() { - return $this->getContextValue('node') instanceof NodeInterface; - } - -} diff --git a/src/Plugin/Condition/IsTerm.php b/src/Plugin/Condition/IsTerm.php deleted file mode 100644 index e6ca5c17..00000000 --- a/src/Plugin/Condition/IsTerm.php +++ /dev/null @@ -1,43 +0,0 @@ -t('The entity is a Taxonomy Term'); - } - - /** - * {@inheritdoc} - */ - public function getContextMapping() { - $this->configuration['context_mapping'] = ['taxonomy_term' => '@islandora.taxonomy_term_route_context_provider:taxonomy_term']; - return parent::getContextMapping(); - } - - /** - * {@inheritdoc} - */ - public function evaluate() { - return $this->getContextValue('taxonomy_term') instanceof TermInterface; - } - -} diff --git a/src/Plugin/Condition/MediaHasTerm.php b/src/Plugin/Condition/MediaHasTerm.php index 1af6f752..67d4db3c 100644 --- a/src/Plugin/Condition/MediaHasTerm.php +++ b/src/Plugin/Condition/MediaHasTerm.php @@ -19,6 +19,10 @@ class MediaHasTerm extends NodeHasTerm { * {@inheritdoc} */ public function evaluate() { + if (empty($this->configuration['uri']) && !$this->isNegated()) { + return TRUE; + } + $media = $this->getContextValue('media'); if (!$media) { return FALSE; diff --git a/src/Plugin/Condition/MediaUsesFilesystem.php b/src/Plugin/Condition/MediaUsesFilesystem.php new file mode 100644 index 00000000..d7b5f9db --- /dev/null +++ b/src/Plugin/Condition/MediaUsesFilesystem.php @@ -0,0 +1,116 @@ +mediaSource = $media_source; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('islandora.utils'), + $container->get('file_system'), + $container->get('islandora.media_source_service') + ); + } + + /** + * {@inheritdoc} + */ + public function summary() { + if (count($this->configuration['filesystems']) > 1) { + $filesystems = $this->configuration['filesystems']; + $last = array_pop($filesystems); + $filesystems = implode(', ', $filesystems); + return $this->t( + 'The media uses @filesystems or @last', + [ + '@filesystems' => $filesystems, + '@last' => $last, + ] + ); + } + $filesystem = reset($this->configuration['filesystems']); + return $this->t( + 'The media uses @filesystem', + [ + '@filesystem' => $filesystem, + ] + ); + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + if (empty($this->configuration['filesystems']) && !$this->isNegated()) { + return TRUE; + } + + $media = $this->getContextValue('media'); + $file = $this->mediaSource->getSourceFile($media); + if (!$file) { + return FALSE; + } + return $this->evaluateFile($file); + } + +} diff --git a/src/Plugin/Condition/NodeHasTerm.php b/src/Plugin/Condition/NodeHasTerm.php index 9b96098c..3b79450a 100644 --- a/src/Plugin/Condition/NodeHasTerm.php +++ b/src/Plugin/Condition/NodeHasTerm.php @@ -94,7 +94,6 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI '#type' => 'entity_autocomplete', '#title' => $this->t('Term'), '#tags' => TRUE, - '#required' => TRUE, '#default_value' => $default, '#target_type' => 'taxonomy_term', ]; @@ -124,6 +123,10 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI * {@inheritdoc} */ public function evaluate() { + if (empty($this->configuration['uri']) && !$this->isNegated()) { + return TRUE; + } + $node = $this->getContextValue('node'); if (!$node) { return FALSE; diff --git a/src/Plugin/Condition/ParentNodeHasTerm.php b/src/Plugin/Condition/ParentNodeHasTerm.php index 371c373e..098a80c9 100644 --- a/src/Plugin/Condition/ParentNodeHasTerm.php +++ b/src/Plugin/Condition/ParentNodeHasTerm.php @@ -19,6 +19,10 @@ class ParentNodeHasTerm extends NodeHasTerm { * {@inheritdoc} */ public function evaluate() { + if (empty($this->configuration['uri']) && !$this->isNegated()) { + return TRUE; + } + $media = $this->getContextValue('media'); if (!$media) { return FALSE; diff --git a/src/Plugin/ContextReaction/MappingUriPredicateReaction.php b/src/Plugin/ContextReaction/MappingUriPredicateReaction.php index fc25fbdc..e393f7e6 100644 --- a/src/Plugin/ContextReaction/MappingUriPredicateReaction.php +++ b/src/Plugin/ContextReaction/MappingUriPredicateReaction.php @@ -4,8 +4,12 @@ namespace Drupal\islandora\Plugin\ContextReaction; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\islandora\ContextReaction\NormalizerAlterReaction; +use Drupal\islandora\MediaSource\MediaSourceService; use Drupal\jsonld\Normalizer\NormalizerBase; +use Drupal\media\MediaInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Map URI to predicate context reaction. @@ -15,10 +19,32 @@ use Drupal\jsonld\Normalizer\NormalizerBase; * label = @Translation("Map URI to predicate") * ) */ -class MappingUriPredicateReaction extends NormalizerAlterReaction { +class MappingUriPredicateReaction extends NormalizerAlterReaction implements ContainerFactoryPluginInterface { const URI_PREDICATE = 'drupal_uri_predicate'; + protected $mediaSource; + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, MediaSourceService $media_source) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->mediaSource = $media_source; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('islandora.media_source_service') + ); + } + /** * {@inheritdoc} */ @@ -43,6 +69,10 @@ class MappingUriPredicateReaction extends NormalizerAlterReaction { if (isset($normalized['@graph']) && is_array($normalized['@graph'])) { foreach ($normalized['@graph'] as &$graph) { if (isset($graph['@id']) && $graph['@id'] == $url) { + if ($entity instanceof MediaInterface) { + $file = $this->mediaSource->getSourceFile($entity); + $url = $file->url('canonical', ['absolute' => TRUE]); + } if (isset($graph[$drupal_predicate])) { if (!is_array($graph[$drupal_predicate])) { if ($graph[$drupal_predicate] == $url) { diff --git a/tests/src/Functional/AddMediaToNodeTest.php b/tests/src/Functional/AddMediaToNodeTest.php index 0e9b21b2..856f004f 100644 --- a/tests/src/Functional/AddMediaToNodeTest.php +++ b/tests/src/Functional/AddMediaToNodeTest.php @@ -84,7 +84,7 @@ class AddMediaToNodeTest extends IslandoraFunctionalTestBase { 'http_errors' => FALSE, 'headers' => [ 'Content-Type' => 'text/plain', - 'Content-Disposition' => 'attachment; filename="test_file.txt"', + 'Content-Location' => 'public://test_file.txt', ], 'body' => $file_contents, ]; @@ -124,14 +124,14 @@ class AddMediaToNodeTest extends IslandoraFunctionalTestBase { 'auth' => [$account->getUsername(), $account->pass_raw], 'http_errors' => FALSE, 'headers' => [ - 'Content-Disposition' => 'attachment; filename="test_file.txt"', + 'Content-Location' => 'public://test_file.txt', ], 'body' => $file_contents, ]; $response = $client->request('PUT', $add_to_node_url, $options); $this->assertTrue($response->getStatusCode() == 400, "Expected 400, received {$response->getStatusCode()}"); - // Request without Content-Disposition header should fail with 400. + // Request without Content-Location header should fail with 400. $options = [ 'auth' => [$account->getUsername(), $account->pass_raw], 'http_errors' => FALSE, @@ -143,26 +143,13 @@ class AddMediaToNodeTest extends IslandoraFunctionalTestBase { $response = $client->request('PUT', $add_to_node_url, $options); $this->assertTrue($response->getStatusCode() == 400, "Expected 400, received {$response->getStatusCode()}"); - // Request with malformed Content-Disposition header should fail with 400. - $options = [ - 'auth' => [$account->getUsername(), $account->pass_raw], - 'http_errors' => FALSE, - 'headers' => [ - 'Content-Type' => 'text/plain', - 'Content-Disposition' => 'garbage; filename="test_file.txt"', - ], - 'body' => $file_contents, - ]; - $response = $client->request('PUT', $add_to_node_url, $options); - $this->assertTrue($response->getStatusCode() == 400, "Expected 400, received {$response->getStatusCode()}"); - // Request without body should fail with 400. $options = [ 'auth' => [$account->getUsername(), $account->pass_raw], 'http_errors' => FALSE, 'headers' => [ 'Content-Type' => 'text/plain', - 'Content-Disposition' => 'attachment; filename="test_file.txt"', + 'Content-Location' => 'public://test_file.txt', ], ]; $response = $client->request('PUT', $add_to_node_url, $options); @@ -174,7 +161,7 @@ class AddMediaToNodeTest extends IslandoraFunctionalTestBase { 'http_errors' => FALSE, 'headers' => [ 'Content-Type' => 'text/plain', - 'Content-Disposition' => 'attachment; filename="test_file.txt"', + 'Content-Location' => 'public://test_file.txt', ], 'body' => $file_contents, ]; @@ -217,7 +204,7 @@ class AddMediaToNodeTest extends IslandoraFunctionalTestBase { 'http_errors' => FALSE, 'headers' => [ 'Content-Type' => 'text/plain', - 'Content-Disposition' => 'attachment; filename="test_file.txt"', + 'Content-Location' => 'public://test_file.txt', ], 'body' => $file_contents, ]; diff --git a/tests/src/Functional/IsNodeTest.php b/tests/src/Functional/ContentEntityTypeTest.php similarity index 70% rename from tests/src/Functional/IsNodeTest.php rename to tests/src/Functional/ContentEntityTypeTest.php index 198db1db..caf11de3 100644 --- a/tests/src/Functional/IsNodeTest.php +++ b/tests/src/Functional/ContentEntityTypeTest.php @@ -3,11 +3,11 @@ namespace Drupal\Tests\islandora\Functional; /** - * Tests the IsNode condition. + * Tests the ContentEntityType condition. * * @group islandora */ -class IsNodeTest extends IslandoraFunctionalTestBase { +class ContentEntityTypeTest extends IslandoraFunctionalTestBase { /** * @covers \Drupal\islandora\ContextProvider\NodeContextProvider::__construct @@ -16,13 +16,15 @@ class IsNodeTest extends IslandoraFunctionalTestBase { * @covers \Drupal\islandora\ContextProvider\MediaContextProvider::getRuntimeContexts * @covers \Drupal\islandora\IslandoraContextManager::evaluateContexts * @covers \Drupal\islandora\IslandoraContextManager::applyContexts - * @covers \Drupal\islandora\Plugin\Condition\IsNode::evaluate + * @covers \Drupal\islandora\Plugin\Condition\ContentEntityType::buildConfigurationForm + * @covers \Drupal\islandora\Plugin\Condition\ContentEntityType::submitConfigurationForm + * @covers \Drupal\islandora\Plugin\Condition\ContentEntityType::evaluate * @covers \Drupal\islandora\PresetReaction\PresetReaction::buildConfigurationForm * @covers \Drupal\islandora\PresetReaction\PresetReaction::submitConfigurationForm * @covers \Drupal\islandora\PresetReaction\PresetReaction::execute * @covers \Drupal\islandora\IslandoraServiceProvider::alter */ - public function testIsNode() { + public function testContentEntityType() { // Create a test user. $account = $this->drupalCreateUser([ 'bypass node access', @@ -34,7 +36,10 @@ class IsNodeTest extends IslandoraFunctionalTestBase { $this->drupalLogin($account); $this->createContext('Test', 'test'); - $this->addCondition('test', 'is_node'); + $this->addCondition('test', 'content_entity_type'); + $this->getSession()->getPage()->checkField("edit-conditions-content-entity-type-types-node"); + $this->getSession()->getPage()->findById("edit-conditions-content-entity-type-context-mapping-node")->selectOption("@node.node_route_context:node"); + $this->getSession()->getPage()->pressButton(t('Save and continue')); $this->addPresetReaction('test', 'index', 'hello_world'); // Create a new node confirm Hello World! is printed to the screen. diff --git a/tests/src/Functional/EmitNodeEventTest.php b/tests/src/Functional/EmitNodeEventTest.php index 083c1ead..d3bb7449 100644 --- a/tests/src/Functional/EmitNodeEventTest.php +++ b/tests/src/Functional/EmitNodeEventTest.php @@ -18,7 +18,9 @@ class EmitNodeEventTest extends IslandoraFunctionalTestBase { * @covers \Drupal\islandora\EventGenerator\EventGenerator::generateEvent * @covers \Drupal\islandora\IslandoraContextManager::evaluateContexts * @covers \Drupal\islandora\IslandoraContextManager::applyContexts - * @covers \Drupal\islandora\Plugin\Condition\IsNode::evaluate + * @covers \Drupal\islandora\Plugin\Condition\ContentEntityType::buildConfigurationForm + * @covers \Drupal\islandora\Plugin\Condition\ContentEntityType::submitConfigurationForm + * @covers \Drupal\islandora\Plugin\Condition\ContentEntityType::evaluate * @covers \Drupal\islandora\PresetReaction\PresetReaction::buildConfigurationForm * @covers \Drupal\islandora\PresetReaction\PresetReaction::submitConfigurationForm * @covers \Drupal\islandora\PresetReaction\PresetReaction::execute @@ -38,7 +40,12 @@ class EmitNodeEventTest extends IslandoraFunctionalTestBase { // Create a context and add the action as an index reaction. $this->createContext('Test', 'test'); - $this->addCondition('test', 'is_node'); + + $this->addCondition('test', 'content_entity_type'); + $this->getSession()->getPage()->checkField("edit-conditions-content-entity-type-types-node"); + $this->getSession()->getPage()->findById("edit-conditions-content-entity-type-context-mapping-node")->selectOption("@node.node_route_context:node"); + $this->getSession()->getPage()->pressButton(t('Save and continue')); + $this->addPresetReaction('test', 'index', $action_id); $this->assertSession()->statusCodeEquals(200); diff --git a/tests/src/Functional/IsFileTest.php b/tests/src/Functional/IsFileTest.php deleted file mode 100644 index b5e7277c..00000000 --- a/tests/src/Functional/IsFileTest.php +++ /dev/null @@ -1,63 +0,0 @@ -drupalCreateUser([ - 'administer contexts', - 'view media', - 'create media', - 'update media', - ]); - $this->drupalLogin($account); - - // Set it up. - $this->createContext('Test', 'test'); - $this->addCondition('test', 'is_file'); - $this->addPresetReaction('test', 'index', 'hello_world'); - - // Add a new media and confirm Hello World! is printed to the - // screen for the file upload. - $file = current($this->getTestFiles('file')); - $values = [ - 'name[0][value]' => 'Test Media', - 'files[field_media_file_0]' => __DIR__ . '/../../fixtures/test_file.txt', - ]; - $this->drupalPostForm('media/add/' . $this->testMediaType->id(), $values, t('Save')); - $this->assertSession()->pageTextContains("Hello World!"); - - // Stash the media's url. - $url = $this->getUrl(); - - // Edit the media, not touching the file this time. - $values = [ - 'name[0][value]' => 'Test Media Changed', - ]; - $this->postEntityEditForm($url, $values, 'Save'); - - // Confirm Hello World! is not printed to the screen. - $this->assertSession()->pageTextNotContains("Hello World!"); - } - -} diff --git a/tests/src/Functional/IsMediaTest.php b/tests/src/Functional/IsMediaTest.php deleted file mode 100644 index 1e6929e9..00000000 --- a/tests/src/Functional/IsMediaTest.php +++ /dev/null @@ -1,56 +0,0 @@ -drupalCreateUser([ - 'bypass node access', - 'administer contexts', - 'view media', - 'create media', - 'update media', - ]); - $this->drupalLogin($account); - - $this->createContext('Test', 'test'); - $this->addCondition('test', 'is_media'); - $this->addPresetReaction('test', 'index', 'hello_world'); - - // Add a new media and confirm Hello World! is printed to the - // screen. - $values = [ - 'name[0][value]' => 'Test Media', - 'files[field_media_file_0]' => __DIR__ . '/../../fixtures/test_file.txt', - ]; - $this->drupalPostForm('media/add/' . $this->testMediaType->id(), $values, t('Save')); - $this->assertSession()->pageTextContains("Hello World!"); - - // Create a new node. - $this->postNodeAddForm('test_type', ['title[0][value]' => 'Test Node'], 'Save'); - - // Confirm Hello World! is not printed to the screen. - $this->assertSession()->pageTextNotContains("Hello World!"); - } - -} diff --git a/tests/src/Functional/IsTermTest.php b/tests/src/Functional/IsTermTest.php deleted file mode 100644 index bb328ba7..00000000 --- a/tests/src/Functional/IsTermTest.php +++ /dev/null @@ -1,51 +0,0 @@ -drupalCreateUser([ - 'bypass node access', - 'administer contexts', - 'administer taxonomy', - ]); - $this->drupalLogin($account); - - $this->createContext('Test', 'test'); - $this->addCondition('test', 'is_term'); - $this->addPresetReaction('test', 'index', 'hello_world'); - - // Create a new term and confirm Hello World! is printed to the screen. - $this->drupalPostForm( - 'admin/structure/taxonomy/manage/' . $this->testVocabulary->id() . '/add', - ['name[0][value]' => 'Test Term'], - t('Save') - ); - $this->assertSession()->pageTextContains("Hello World!"); - - // Create a new node and confirm Hello World! is not printed to the screen. - $this->postNodeAddForm('test_type', ['title[0][value]' => 'Test Node'], 'Save'); - $this->assertSession()->pageTextNotContains("Hello World!"); - } - -} diff --git a/tests/src/Kernel/FedoraAdapterTest.php b/tests/src/Kernel/FedoraAdapterTest.php new file mode 100644 index 00000000..d16cd798 --- /dev/null +++ b/tests/src/Kernel/FedoraAdapterTest.php @@ -0,0 +1,549 @@ +prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(404); + $response = $prophecy->reveal(); + + $prophecy = $this->prophesize(IFedoraApi::class); + $prophecy->getResourceHeaders('')->willReturn($response); + $prophecy->getResource('')->willReturn($response); + $api = $prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + + /** + * Mocks up an adapter for Fedora LDP-NR response. + */ + protected function createAdapterForFile() { + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(200); + $prophecy->getHeader('Last-Modified')->willReturn(["Wed, 25 Jul 2018 17:42:04 GMT"]); + $prophecy->getHeader('Link')->willReturn([';rel="type"', ';rel="type"']); + $prophecy->getHeader('Content-Type')->willReturn(['text/plain']); + $prophecy->getHeader('Content-Length')->willReturn([strlen("DERP")]); + $prophecy->getBody()->willReturn(PSR7\stream_for("DERP")); + $response = $prophecy->reveal(); + + $prophecy = $this->prophesize(IFedoraApi::class); + $prophecy->getResourceHeaders('')->willReturn($response); + $prophecy->getResource('')->willReturn($response); + $api = $prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + + /** + * Mocks up an adapter for Fedora LDP-RS response. + */ + protected function createAdapterForDirectory() { + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(200); + $prophecy->getHeader('Last-Modified')->willReturn(["Wed, 25 Jul 2018 17:42:04 GMT"]); + $prophecy->getHeader('Link')->willReturn([';rel="type"', ';rel="type"']); + $response = $prophecy->reveal(); + + $prophecy = $this->prophesize(IFedoraApi::class); + $prophecy->getResourceHeaders('')->willReturn($response); + $api = $prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + + /** + * Mocks up an adapter for Fedora write requests. + */ + protected function createAdapterForWrite() { + $fedora_prophecy = $this->prophesize(IFedoraApi::class); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(201); + + $fedora_prophecy->saveResource('', '', Argument::any())->willReturn($prophecy->reveal()); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(200); + $prophecy->getHeader('Last-Modified')->willReturn(["Wed, 25 Jul 2018 17:42:04 GMT"]); + $prophecy->getHeader('Link')->willReturn([';rel="type"', ';rel="type"']); + $prophecy->getHeader('Content-Type')->willReturn(['text/plain']); + $prophecy->getHeader('Content-Length')->willReturn([strlen("DERP")]); + $prophecy->getBody()->willReturn(PSR7\stream_for("DERP")); + + $fedora_prophecy->getResourceHeaders('')->willReturn($prophecy->reveal()); + + $api = $fedora_prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + + /** + * Mocks up an adapter for Fedora write requests that fail. + */ + protected function createAdapterForWriteFail() { + $fedora_prophecy = $this->prophesize(IFedoraApi::class); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(500); + + $fedora_prophecy->saveResource('', '', Argument::any())->willReturn($prophecy->reveal()); + + $api = $fedora_prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + + /** + * Mocks up an adapter for creating directories requests. + */ + protected function createAdapterForCreateDir() { + $fedora_prophecy = $this->prophesize(IFedoraApi::class); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(201); + + $fedora_prophecy->saveResource('')->willReturn($prophecy->reveal()); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(200); + $prophecy->getHeader('Last-Modified')->willReturn(["Wed, 25 Jul 2018 17:42:04 GMT"]); + $prophecy->getHeader('Link')->willReturn([';rel="type"', ';rel="type"']); + + $fedora_prophecy->getResourceHeaders('')->willReturn($prophecy->reveal()); + + $api = $fedora_prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + + /** + * Mocks up an adapter for Delete requests. + */ + protected function createAdapterForDelete() { + $fedora_prophecy = $this->prophesize(IFedoraApi::class); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(204); + + $fedora_prophecy->deleteResource('')->willReturn($prophecy->reveal()); + $fedora_prophecy->getResourceHeaders('')->willReturn($prophecy->reveal()); + $api = $fedora_prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + + /** + * Mocks up an adapter for Delete requests that fail. + */ + protected function createAdapterForDeleteFail() { + $fedora_prophecy = $this->prophesize(IFedoraApi::class); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(500); + + $fedora_prophecy->deleteResource('')->willReturn($prophecy->reveal()); + + $api = $fedora_prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + + /** + * Asserts the stucture/contents of a metadata response for a file. + */ + protected function assertFileMetadata(array $metadata) { + $this->assertTrue($metadata['type'] == 'file', "Expecting 'type' of 'file', received '" . $metadata['type'] . "'"); + $this->assertTrue($metadata['timestamp'] == '1532540524', "Expecting 'timestamp' of '1532540524', received '" . $metadata['timestamp'] . "'"); + $this->assertTrue($metadata['size'] == strlen("DERP"), "Expecting 'size' of '" . strlen("DERP") . "', received '" . $metadata['size'] . "'"); + $this->assertTrue($metadata['mimetype'] == 'text/plain', "Expecting 'mimetype' of 'image/png', received '" . $metadata['mimetype'] . "'"); + } + + /** + * Asserts the stucture/contents of a metadata response for a directory. + */ + protected function assertDirMetadata(array $metadata) { + $this->assertTrue($metadata['type'] == 'dir', "Expecting 'type' of 'dir', received '" . $metadata['type'] . "'"); + $this->assertTrue($metadata['timestamp'] == '1532540524', "Expecting 'timestamp' of '1532540524', received '" . $metadata['timestamp'] . "'"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::getMetadata + */ + public function testGetMetadataFail() { + $adapter = $this->createAdapterForFail(); + + $this->assertTrue($adapter->getMetadata('') == FALSE, "getMetadata() must return FALSE on non-200"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::getMetadata + */ + public function testGetMetadataForFile() { + $adapter = $this->createAdapterForFile(); + + $metadata = $adapter->getMetadata(''); + $this->assertFileMetadata($metadata); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::getMetadata + */ + public function testGetMetadataForDirectory() { + $adapter = $this->createAdapterForDirectory(); + + $metadata = $adapter->getMetadata(''); + $this->assertDirMetadata($metadata); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::readStream + */ + public function testReadStream() { + $adapter = $this->createAdapterForFile(); + + $metadata = $adapter->readStream(''); + $this->assertFileMetadata($metadata); + $this->assertTrue(is_resource($metadata['stream']), "Expecting a 'stream' that is a resource"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::readStream + */ + public function testReadStreamFail() { + $adapter = $this->createAdapterForFail(); + + $this->assertTrue($adapter->readStream('') == FALSE, "readStream() should return FALSE on non-200 from Fedora"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::read + */ + public function testRead() { + $adapter = $this->createAdapterForFile(); + + $metadata = $adapter->read(''); + $this->assertFileMetadata($metadata); + $this->assertTrue($metadata['contents'] == "DERP", "Expecting 'content' of 'DERP', received '${metadata['contents']}'"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::read + */ + public function testReadFail() { + $adapter = $this->createAdapterForFail(); + + $this->assertTrue($adapter->read('') == FALSE, "readStream() should return FALSE on non-200 from Fedora"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::has + */ + public function testHasFail() { + $adapter = $this->createAdapterForFail(); + + $this->assertTrue($adapter->has('') == FALSE, "has() must return FALSE on non-200"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::has + */ + public function testHasSuccess() { + $adapter = $this->createAdapterForFile(); + + $this->assertTrue($adapter->has('') == TRUE, "has() must return TRUE on 200"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::getSize + */ + public function testGetSizeFail() { + $adapter = $this->createAdapterForFail(); + + $this->assertTrue($adapter->getSize('') == FALSE, "getSize() must return FALSE on non-200"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::getSize + */ + public function testGetSizeSuccess() { + $adapter = $this->createAdapterForFile(); + + $metadata = $adapter->getSize(''); + $this->assertTrue($metadata['size'] == strlen("DERP"), "Expecting 'size' of '" . strlen("DERP") . "', received '" . $metadata['size'] . "'"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::getMimetype + */ + public function testGetMimetypeFail() { + $adapter = $this->createAdapterForFail(); + + $this->assertTrue($adapter->getMimetype('') == FALSE, "getMimetype() must return FALSE on non-200"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::getMimetype + */ + public function testGetMimetypeSuccess() { + $adapter = $this->createAdapterForFile(); + + $metadata = $adapter->getMimetype(''); + $this->assertTrue($metadata['mimetype'] == 'text/plain', "Expecting 'mimetype' of 'text/plain', received '" . $metadata['mimetype'] . "'"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::getTimestamp + */ + public function testGetTimestampFail() { + $adapter = $this->createAdapterForFail(); + + $this->assertTrue($adapter->getTimestamp('') == FALSE, "getTimestamp() must return FALSE on non-200"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::getTimestamp + */ + public function testGetTimestampSuccess() { + $adapter = $this->createAdapterForFile(); + + $metadata = $adapter->getTimestamp(''); + $this->assertTrue($metadata['timestamp'] == '1532540524', "Expecting 'timestamp' of '1532540524', received '" . $metadata['timestamp'] . "'"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::write + */ + public function testWriteFail() { + $adapter = $this->createAdapterForWriteFail(); + + $this->assertTrue($adapter->write('', '', $this->prophesize(Config::class)->reveal()) == FALSE, "write() must return FALSE on non-201 or non-204"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::write + */ + public function testWrite() { + $adapter = $this->createAdapterForWrite(); + + $metadata = $adapter->write('', '', $this->prophesize(Config::class)->reveal()); + $this->assertFileMetadata($metadata); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::writeStream + */ + public function testWriteStreamFail() { + $adapter = $this->createAdapterForWriteFail(); + + $this->assertTrue($adapter->writeStream('', '', $this->prophesize(Config::class)->reveal()) == FALSE, "writeStream() must return FALSE on non-201 or non-204"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::writeStream + */ + public function testWriteStream() { + $adapter = $this->createAdapterForWrite(); + + $metadata = $adapter->writeStream('', '', $this->prophesize(Config::class)->reveal()); + $this->assertFileMetadata($metadata); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::update + */ + public function testUpdateFail() { + $adapter = $this->createAdapterForWriteFail(); + + $this->assertTrue($adapter->update('', '', $this->prophesize(Config::class)->reveal()) == FALSE, "write() must return FALSE on non-201 or non-204"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::update + */ + public function testUpdate() { + $adapter = $this->createAdapterForWrite(); + + $metadata = $adapter->update('', '', $this->prophesize(Config::class)->reveal()); + $this->assertFileMetadata($metadata); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::updateStream + */ + public function testUpdateStreamFail() { + $adapter = $this->createAdapterForWriteFail(); + + $this->assertTrue($adapter->updateStream('', '', $this->prophesize(Config::class)->reveal()) == FALSE, "writeStream() must return FALSE on non-201 or non-204"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::updateStream + */ + public function testUpdateStream() { + $adapter = $this->createAdapterForWrite(); + + $metadata = $adapter->updateStream('', '', $this->prophesize(Config::class)->reveal()); + $this->assertFileMetadata($metadata); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::delete + */ + public function testDeleteFail() { + $adapter = $this->createAdapterForDeleteFail(); + + $this->assertTrue($adapter->delete('') == FALSE, "delete() must return FALSE on non-204 or non-404"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::delete + */ + public function testDelete() { + $adapter = $this->createAdapterForDelete(); + + $this->assertTrue($adapter->delete('') == TRUE, "delete() must return TRUE on 204 or 404"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::deleteDir + */ + public function testDeleteDirFail() { + $adapter = $this->createAdapterForDeleteFail(); + + $this->assertTrue($adapter->deleteDir('') == FALSE, "deleteDir() must return FALSE on non-204 or non-404"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::deleteDir + */ + public function testDeleteDir() { + $adapter = $this->createAdapterForDelete(); + + $this->assertTrue($adapter->deleteDir('') == TRUE, "deleteDir() must return TRUE on 204 or 404"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::rename + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::copy + */ + public function testRenameFail() { + $adapter = $this->createAdapterForFail(); + + $this->assertTrue($adapter->rename('', '') == FALSE, "rename() must return FALSE on Fedora error"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::rename + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::copy + */ + public function testRename() { + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(200); + $prophecy->getHeader('Last-Modified')->willReturn(["Wed, 25 Jul 2018 17:42:04 GMT"]); + $prophecy->getHeader('Link')->willReturn([';rel="type"', ';rel="type"']); + $prophecy->getHeader('Content-Type')->willReturn(['text/plain']); + $prophecy->getHeader('Content-Length')->willReturn([strlen("DERP")]); + $prophecy->getBody()->willReturn(PSR7\stream_for("DERP")); + $response = $prophecy->reveal(); + + $fedora_prophecy = $this->prophesize(IFedoraApi::class); + $fedora_prophecy->getResource(Argument::any())->willReturn($response); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(201); + $response = $prophecy->reveal(); + + $fedora_prophecy->saveResource(Argument::any(), Argument::any(), Argument::any())->willReturn($response); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(200); + $prophecy->getHeader('Last-Modified')->willReturn(["Wed, 25 Jul 2018 17:42:04 GMT"]); + $prophecy->getHeader('Link')->willReturn([';rel="type"', ';rel="type"']); + $prophecy->getHeader('Content-Type')->willReturn(['text/plain']); + $prophecy->getHeader('Content-Length')->willReturn([strlen("DERP")]); + $response = $prophecy->reveal(); + + $fedora_prophecy->getResourceHeaders(Argument::any())->willReturn($response); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(204); + $response = $prophecy->reveal(); + + $fedora_prophecy->deleteResource(Argument::any())->willReturn($response); + + $api = $fedora_prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + $adapter = new FedoraAdapter($api, $mime_guesser); + + $this->assertTrue($adapter->rename('', '') == TRUE, "rename() must return TRUE on success"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::createDir + */ + public function testCreateDirFail() { + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(500); + + $fedora_prophecy = $this->prophesize(IFedoraApi::class); + $fedora_prophecy->saveResource('')->willReturn($prophecy->reveal()); + + $api = $fedora_prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + $adapter = new FedoraAdapter($api, $mime_guesser); + + $this->assertTrue($adapter->createDir('', $this->prophesize(Config::class)->reveal()) == FALSE, "createDir() must return FALSE on fail"); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::createDir + */ + public function testCreateDir() { + $adapter = $this->createAdapterForCreateDir(); + + $metadata = $adapter->createDir('', $this->prophesize(Config::class)->reveal()); + $this->assertDirMetadata($metadata); + } + +} diff --git a/tests/src/Kernel/FedoraPluginTest.php b/tests/src/Kernel/FedoraPluginTest.php new file mode 100644 index 00000000..8f85251b --- /dev/null +++ b/tests/src/Kernel/FedoraPluginTest.php @@ -0,0 +1,62 @@ +prophesize(ResponseInterface::class); + $prophecy->getStatusCode()->willReturn($return_code); + $response = $prophecy->reveal(); + + $prophecy = $this->prophesize(IFedoraApi::class); + $prophecy->getResourceHeaders('')->willReturn($response); + $prophecy->getBaseUri()->willReturn(""); + $api = $prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new Fedora($api, $mime_guesser); + } + + /** + * Tests the getAdapter() method. + * + * @covers \Drupal\islandora\Flysystem\Fedora::getAdapter + */ + public function testGetAdapter() { + $plugin = $this->createPlugin(200); + $adapter = $plugin->getAdapter(); + + $this->assertTrue($adapter instanceof AdapterInterface, "getAdapter() must return an AdapterInterface"); + } + + /** + * Tests the ensure() method. + * + * @covers \Drupal\islandora\Flysystem\Fedora::ensure + */ + public function testEnsure() { + $plugin = $this->createPlugin(200); + $this->assertTrue(empty($plugin->ensure()), "ensure() must return an empty array on success"); + + $plugin = $this->createPlugin(404); + $this->assertTrue(!empty($plugin->ensure()), "ensure() must return a non-empty array on fail"); + } + +} diff --git a/tests/src/Kernel/IslandoraKernelTestBase.php b/tests/src/Kernel/IslandoraKernelTestBase.php index a24afa69..5690bc1f 100644 --- a/tests/src/Kernel/IslandoraKernelTestBase.php +++ b/tests/src/Kernel/IslandoraKernelTestBase.php @@ -37,6 +37,7 @@ abstract class IslandoraKernelTestBase extends KernelTestBase { 'image', 'media', 'islandora', + 'flysystem', ]; /**