diff --git a/.travis.yml b/.travis.yml index 80dbcb06..82b526f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,10 +26,9 @@ install: - cd web; drush --uri=127.0.0.1:8282 en -y islandora script: - - $SCRIPT_DIR/line_endings.sh $TRAVIS_BUILD_DIR - - phpcs --standard=Drupal --ignore=*.md --extensions=php,module,inc,install,test,profile,theme,css,info $TRAVIS_BUILD_DIR - - phpcpd --names *.module,*.inc,*.test,*.php $TRAVIS_BUILD_DIR - - php core/scripts/run-tests.sh --suppress-deprecations --url http://127.0.0.1:8282 --verbose --php `which php` --module "islandora" + - $SCRIPT_DIR/travis_scripts.sh + - $SCRIPT_DIR/run-tests.sh "islandora" + - $SCRIPT_DIR/run-tests.sh "islandora_breadcrumbs" notifications: irc: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c5dd0d4..916d6794 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,10 @@ # Welcome! -If you are reading this document then you are interested in contributing to the Islandora CLAW. All contributions are welcome: use-cases, documentation, code, patches, bug reports, feature requests, etc. You do not need to be a programmer to speak up! +If you are reading this document then you are interested in contributing to Islandora 8. All contributions are welcome: use-cases, documentation, code, patches, bug reports, feature requests, etc. You do not need to be a programmer to speak up! + +We also have an irc channel -- #islandora -- on freenode.net. Feel free to hang out there, ask questions, and help others out if you can. + +Please note that this project operates under the [Islandora Community Code of Conduct](http://islandora.ca/codeofconduct). By participating in this project you agree to abide by its terms. ## Workflows @@ -8,7 +12,7 @@ The group meets each Wednesday at 1:00 PM Eastern. Meeting notes and announcemen ### Use cases -If you would like to submit a use case to the Islandora CLAW project, please submit an issue [here](https://github.com/Islandora-CLAW/CLAW/issues/new) using the [Use Case template](https://github.com/Islandora-CLAW/CLAW/wiki/Use-Case-template), prepending "Use Case:" to the title of the issue. +If you would like to submit a use case to the Islandora 8 project, please submit an issue [here](https://github.com/Islandora-CLAW/CLAW/issues/new) using the [Use Case template](https://github.com/Islandora-CLAW/CLAW/wiki/Use-Case-template), prepending "Use Case:" to the title of the issue. ### Documentation @@ -16,11 +20,11 @@ You can contribute documentation in two different ways. One way is to create an ### Request a new feature -To request a new feature you should [open an issue in the CLAW repository](https://github.com/Islandora-CLAW/CLAW/issues/new) or create a use case (see the _Use cases_ section above), and summarize the desired functionality. Prepend "Enhancement:" if creating an issue on the project repo, and "Use Case:" if creating a use case. +To request a new feature you should [open an issue in the Islandora 8 repository](https://github.com/Islandora-CLAW/CLAW/issues/new) or create a use case (see the _Use cases_ section above), and summarize the desired functionality. Prepend "Enhancement:" if creating an issue on the project repo, and "Use Case:" if creating a use case. ### Report a bug -To report a bug you should [open an issue in the CLAW repository](https://github.com/Islandora-CLAW/CLAW/issues/new) that summarizes the bug. Prepend the label "Bug:" to the title of the issue. +To report a bug you should [open an issue in the Islandora 8 repository](https://github.com/Islandora-CLAW/CLAW/issues/new) that summarizes the bug. Prepend the label "Bug:" to the title of the issue. In order to help us understand and fix the bug it would be great if you could provide us with: @@ -36,7 +40,7 @@ That is great! In this case please send us a pull request as described in the se ### Contribute code -Before you set out to contribute code you will need to have completed a [Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_cla.pdf) or be covered by a [Corporate Contributor Licencse Agreement](http://islandora.ca/sites/default/files/islandora_ccla.pdf). The signed copy of the license agreement should be sent to +Before you set out to contribute code you will need to have completed a [Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_cla.pdf) or be covered by a [Corporate Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_ccla.pdf). The signed copy of the license agreement should be sent to _If you are interested in contributing code to Islandora but do not know where to begin:_ @@ -66,4 +70,4 @@ You may want to read [Syncing a fork](https://help.github.com/articles/syncing-a ## License Agreements -The Islandora Foundation requires that contributors complete a [Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_cla.pdf) or be covered by a [Corporate Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_ccla.pdf). The signed copy of the license agreement should be sent to community@islandora.ca. This license is for your protection as a contributor as well as the protection of the Foundation and its users; it does not change your rights to use your own contributions for any other purpose. +The Islandora Foundation requires that contributors complete a [Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_cla.pdf) or be covered by a [Corporate Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_ccla.pdf). The signed copy of the license agreement should be sent to community@islandora.ca. This license is for your protection as a contributor as well as the protection of the Foundation and its users; it does not change your rights to use your own contributions for any other purpose. A list of current CLAs is kept [here](https://github.com/Islandora/islandora/wiki/Contributor-License-Agreements). diff --git a/config/install/system.action.delete_media.yml b/config/install/system.action.delete_media.yml deleted file mode 100644 index c95e8423..00000000 --- a/config/install/system.action.delete_media.yml +++ /dev/null @@ -1,13 +0,0 @@ -langcode: en -status: true -dependencies: - enforced: - module: - - islandora - module: - - islandora -id: delete_media -label: 'Delete media' -type: media -plugin: delete_media -configuration: { } diff --git a/config/schema/islandora.schema.yml b/config/schema/islandora.schema.yml index 1df5b7e8..8fd719ce 100644 --- a/config/schema/islandora.schema.yml +++ b/config/schema/islandora.schema.yml @@ -68,10 +68,6 @@ action.configuration.emit_term_event: type: text label: 'Event Type' -action.configuration.delete_media: - type: action_configuration_default - label: 'Delete media' - action.configuration.delete_media_and_file: type: action_configuration_default label: 'Delete media and file' @@ -83,6 +79,16 @@ condition.plugin.node_has_term: type: text label: 'Taxonomy Term URI' +condition.plugin.node_has_parent: + type: condition.plugin + mapping: + parent_nid: + type: integer + label: 'Parent node' + parent_reference_field: + type: string + label: 'Parent reference field' + condition.plugin.media_has_term: type: condition.plugin mapping: @@ -113,6 +119,13 @@ condition.plugin.media_uses_filesystem: sequence: type: string +condition.plugin.media_has_mimetype: + type: condition.plugin + mapping: + mimetypes: + type: text + label: 'Mime types' + condition.plugin.content_entity_type: type: condition.plugin mapping: @@ -121,6 +134,16 @@ condition.plugin.content_entity_type: sequence: type: string +condition.plugin.node_had_namespace: + type: condition.plugin + mapping: + namespace: + type: text + label: 'Namespace' + pid_field: + type: ignore + label: 'PID field' + field.formatter.settings.islandora_image: type: mapping label: 'Image field display format settings' diff --git a/islandora.install b/islandora.install index e900f2f6..526583d4 100644 --- a/islandora.install +++ b/islandora.install @@ -40,3 +40,15 @@ function islandora_schema() { ]; return $schema; } + +/** + * Delete the 'delete_media' action we used to provide, if it exists. + * + * Use the core 'media_delete_action' instead. + */ +function islandora_update_8001(&$sandbox) { + $action = \Drupal::service('entity_type.manager')->getStorage('action')->load('delete_media'); + if ($action) { + $action->delete(); + } +} diff --git a/islandora.module b/islandora.module index 8b0d52e1..183adabc 100644 --- a/islandora.module +++ b/islandora.module @@ -343,10 +343,13 @@ function islandora_form_block_form_alter(&$form, FormStateInterface $form_state, // to alter block layout. unset($form['visibility']['content_entity_type']); unset($form['visibility']['parent_node_has_term']); + unset($form['visibility']['node_had_namespace']); unset($form['visibility']['media_has_term']); unset($form['visibility']['file_uses_filesystem']); unset($form['visibility']['node_has_term']); + unset($form['visibility']['node_has_parent']); unset($form['visibility']['media_uses_filesystem']); + unset($form['visibility']['media_has_mimetype']); } /** diff --git a/islandora.services.yml b/islandora.services.yml index b88d186b..b08e0699 100644 --- a/islandora.services.yml +++ b/islandora.services.yml @@ -3,7 +3,7 @@ services: islandora.eventgenerator: class: Drupal\islandora\EventGenerator\EventGenerator - arguments: ['@language_manager', '@islandora.media_source_service'] + arguments: ['@islandora.utils', '@islandora.media_source_service'] islandora.stomp: class: Stomp\StatefulStomp factory: ['Drupal\islandora\StompFactory', create] @@ -16,12 +16,12 @@ services: - { name: event_subscriber } islandora.media_link_header_subscriber: class: Drupal\islandora\EventSubscriber\MediaLinkHeaderSubscriber - arguments: ['@entity_type.manager', '@entity_field.manager', '@access_manager', '@current_user', '@current_route_match', '@request_stack', '@language_manager'] + arguments: ['@entity_type.manager', '@entity_field.manager', '@access_manager', '@current_user', '@current_route_match', '@request_stack', '@islandora.utils'] tags: - { name: event_subscriber } islandora.node_link_header_subscriber: class: Drupal\islandora\EventSubscriber\NodeLinkHeaderSubscriber - arguments: ['@entity_type.manager', '@entity_field.manager', '@access_manager', '@current_user', '@current_route_match', '@request_stack', '@language_manager', '@islandora.utils'] + arguments: ['@entity_type.manager', '@entity_field.manager', '@access_manager', '@current_user', '@current_route_match', '@request_stack', '@islandora.utils'] tags: - { name: event_subscriber } islandora.admin_view_route_subscriber: @@ -51,7 +51,7 @@ services: arguments: ['@entity_type.manager', '@current_user', '@language_manager', '@entity.query', '@file_system', '@islandora.utils'] islandora.utils: class: Drupal\islandora\IslandoraUtils - arguments: ['@entity_type.manager', '@entity_field.manager', '@entity.query', '@context.manager', '@flysystem_factory'] + arguments: ['@entity_type.manager', '@entity_field.manager', '@entity.query', '@context.manager', '@flysystem_factory', '@language_manager'] islandora.gemini.client: class: Islandora\Crayfish\Commons\Client\GeminiClient factory: ['Drupal\islandora\GeminiClientFactory', create] diff --git a/modules/islandora_audio/tests/src/Functional/GenerateAudioDerivativeTest.php b/modules/islandora_audio/tests/src/Functional/GenerateAudioDerivativeTest.php index 9c7f4e87..bcd78f70 100644 --- a/modules/islandora_audio/tests/src/Functional/GenerateAudioDerivativeTest.php +++ b/modules/islandora_audio/tests/src/Functional/GenerateAudioDerivativeTest.php @@ -11,6 +11,9 @@ use Drupal\Tests\islandora\Functional\GenerateDerivativeTestBase; */ class GenerateAudioDerivativeTest extends GenerateDerivativeTestBase { + /** + * {@inheritdoc} + */ protected static $modules = ['context_ui', 'islandora_audio']; /** diff --git a/modules/islandora_breadcrumbs/config/install/islandora.breadcrumbs.yml b/modules/islandora_breadcrumbs/config/install/islandora.breadcrumbs.yml new file mode 100644 index 00000000..38f8be38 --- /dev/null +++ b/modules/islandora_breadcrumbs/config/install/islandora.breadcrumbs.yml @@ -0,0 +1,3 @@ +maxDepth: -1 +includeSelf: FALSE +referenceField: field_member_of diff --git a/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml b/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml new file mode 100644 index 00000000..7a26de0c --- /dev/null +++ b/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml @@ -0,0 +1,12 @@ +islandora.breadcrumbs: + type: config_object + mapping: + maxDepth: + type: integer + label: 'Max Depth' + includeSelf: + type: boolean + label: 'Include Self' + referenceField: + type: string + label: 'Reference Field' diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml b/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml new file mode 100644 index 00000000..085b38bf --- /dev/null +++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml @@ -0,0 +1,7 @@ +name: 'Islandora Breadcrumbs' +type: module +description: 'Builds breadcrumbs based on field_member_of relationships.' +core: 8.x +package: Islandora +dependencies: + - islandora diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml b/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml new file mode 100644 index 00000000..af9efa06 --- /dev/null +++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml @@ -0,0 +1,6 @@ +services: + islandora_breadcrumbs.breadcrumb: + class: Drupal\islandora_breadcrumbs\IslandoraBreadcrumbBuilder + arguments: ['@config.factory'] + tags: + - { name: breadcrumb_builder, priority: 100 } diff --git a/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php b/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php new file mode 100644 index 00000000..763c523a --- /dev/null +++ b/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php @@ -0,0 +1,96 @@ +config = $config_factory->get('islandora.breadcrumbs'); + } + + /** + * {@inheritdoc} + */ + public function applies(RouteMatchInterface $attributes) { + $parameters = $attributes->getParameters()->all(); + if (!empty($parameters['node'])) { + return ($parameters['node']->hasField($this->config->get('referenceField')) && + !$parameters['node']->get($this->config->get('referenceField'))->isEmpty()); + } + } + + /** + * {@inheritdoc} + */ + public function build(RouteMatchInterface $route_match) { + + $node = $route_match->getParameter('node'); + $breadcrumb = new Breadcrumb(); + + $chain = []; + $this->walkMembership($node, $chain); + + if (!$this->config->get('includeSelf')) { + array_pop($chain); + } + $breadcrumb->addCacheableDependency($node); + + // Add membership chain to the breadcrumb. + foreach ($chain as $chainlink) { + $breadcrumb->addCacheableDependency($chainlink); + $breadcrumb->addLink($chainlink->toLink()); + } + $breadcrumb->addCacheContexts(['route']); + return $breadcrumb; + } + + /** + * Follows chain of field_member_of links. + * + * We pass crumbs by reference to enable checking for looped chains. + */ + protected function walkMembership(EntityInterface $entity, &$crumbs) { + // Avoid infinate loops, return if we've seen this before. + foreach ($crumbs as $crumb) { + if ($crumb->uuid == $entity->uuid) { + return; + } + } + + // Add this item onto the pile. + array_unshift($crumbs, $entity); + + if ($this->config->get('maxDepth') > 0 && count($crumbs) >= $this->config->get('maxDepth')) { + return; + } + + // Find the next in the chain, if there are any. + if ($entity->hasField($this->config->get('referenceField')) && + !$entity->get($this->config->get('referenceField'))->isEmpty()) { + $this->walkMembership($entity->get($this->config->get('referenceField'))->entity, $crumbs); + } + } + +} diff --git a/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php b/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php new file mode 100644 index 00000000..c95d362c --- /dev/null +++ b/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php @@ -0,0 +1,109 @@ +nodeA = $this->container->get('entity_type.manager')->getStorage('node')->create([ + 'type' => $this->testType->id(), + 'title' => 'Node A', + ]); + $this->nodeA->save(); + + $this->nodeB = $this->container->get('entity_type.manager')->getStorage('node')->create([ + 'type' => $this->testType->id(), + 'title' => 'Node B', + ]); + $this->nodeB->set('field_member_of', [$this->nodeA->id()]); + $this->nodeB->save(); + + $this->nodeC = $this->container->get('entity_type.manager')->getStorage('node')->create([ + 'type' => $this->testType->id(), + 'title' => 'Node C', + ]); + $this->nodeC->set('field_member_of', [$this->nodeB->id()]); + $this->nodeC->save(); + + $this->nodeD = $this->container->get('entity_type.manager')->getStorage('node')->create([ + 'type' => $this->testType->id(), + 'title' => 'Node D', + ]); + $this->nodeD->set('field_member_of', [$this->nodeC->id()]); + $this->nodeD->save(); + } + + /** + * @covers \Drupal\islandora_breadcrumbs\IslandoraBreadcrumbBuilder::applies + */ + public function testDefaults() { + $breadcrumbs = [ + $this->nodeC->toUrl()->toString() => $this->nodeC->label(), + $this->nodeB->toUrl()->toString() => $this->nodeB->label(), + $this->nodeA->toUrl()->toString() => $this->nodeA->label(), + ]; + $this->assertBreadcrumb($this->nodeD->toUrl()->toString(), $breadcrumbs); + + // Create a reference loop. + $this->nodeA->set('field_member_of', [$this->nodeD->id()]); + $this->nodeA->save(); + + // We should still escape it and have the same trail as before. + $this->assertBreadcrumb($this->nodeD->toUrl()->toString(), $breadcrumbs); + } + +} diff --git a/modules/islandora_core_feature/config/install/field.field.media.audio.field_media_use.yml b/modules/islandora_core_feature/config/install/field.field.media.audio.field_media_use.yml index 88f5be9c..2f1bdda8 100644 --- a/modules/islandora_core_feature/config/install/field.field.media.audio.field_media_use.yml +++ b/modules/islandora_core_feature/config/install/field.field.media.audio.field_media_use.yml @@ -10,7 +10,7 @@ field_name: field_media_use entity_type: media bundle: audio label: 'Media Use' -description: '' +description: 'Defined by Portland Common Data Model: Use Extension https://pcdm.org/2015/05/12/use. ''Original File'' will trigger creation of derivatives.' required: false translatable: false default_value: { } diff --git a/modules/islandora_core_feature/config/install/field.field.media.file.field_media_use.yml b/modules/islandora_core_feature/config/install/field.field.media.file.field_media_use.yml index 85eab457..ae1b6c31 100644 --- a/modules/islandora_core_feature/config/install/field.field.media.file.field_media_use.yml +++ b/modules/islandora_core_feature/config/install/field.field.media.file.field_media_use.yml @@ -10,7 +10,7 @@ field_name: field_media_use entity_type: media bundle: file label: 'Media Use' -description: '' +description: 'Defined by Portland Common Data Model: Use Extension https://pcdm.org/2015/05/12/use. ''Original File'' will trigger creation of derivatives.' required: false translatable: true default_value: { } diff --git a/modules/islandora_core_feature/config/install/field.field.media.image.field_media_use.yml b/modules/islandora_core_feature/config/install/field.field.media.image.field_media_use.yml index 5b93f475..f9f2d259 100644 --- a/modules/islandora_core_feature/config/install/field.field.media.image.field_media_use.yml +++ b/modules/islandora_core_feature/config/install/field.field.media.image.field_media_use.yml @@ -10,7 +10,7 @@ field_name: field_media_use entity_type: media bundle: image label: 'Media Use' -description: '' +description: 'Defined by Portland Common Data Model: Use Extension https://pcdm.org/2015/05/12/use. ''Original File'' will trigger creation of derivatives.' required: false translatable: true default_value: { } diff --git a/modules/islandora_core_feature/config/install/field.field.media.video.field_media_use.yml b/modules/islandora_core_feature/config/install/field.field.media.video.field_media_use.yml index e8cc3098..1fea8534 100644 --- a/modules/islandora_core_feature/config/install/field.field.media.video.field_media_use.yml +++ b/modules/islandora_core_feature/config/install/field.field.media.video.field_media_use.yml @@ -10,7 +10,7 @@ field_name: field_media_use entity_type: media bundle: video label: 'Media Use' -description: '' +description: 'Defined by Portland Common Data Model: Use Extension https://pcdm.org/2015/05/12/use. ''Original File'' will trigger creation of derivatives.' required: false translatable: true default_value: { } diff --git a/modules/islandora_core_feature/config/install/field.storage.media.field_media_use.yml b/modules/islandora_core_feature/config/install/field.storage.media.field_media_use.yml index 90a0cb1b..30b9d730 100644 --- a/modules/islandora_core_feature/config/install/field.storage.media.field_media_use.yml +++ b/modules/islandora_core_feature/config/install/field.storage.media.field_media_use.yml @@ -12,7 +12,7 @@ settings: target_type: taxonomy_term module: core locked: false -cardinality: 1 +cardinality: -1 translatable: true indexes: { } persist_with_no_fields: false diff --git a/modules/islandora_core_feature/config/install/field.storage.node.field_display_hints.yml b/modules/islandora_core_feature/config/install/field.storage.node.field_display_hints.yml index 7bbfacad..08590174 100644 --- a/modules/islandora_core_feature/config/install/field.storage.node.field_display_hints.yml +++ b/modules/islandora_core_feature/config/install/field.storage.node.field_display_hints.yml @@ -12,7 +12,7 @@ settings: target_type: taxonomy_term module: core locked: false -cardinality: -1 +cardinality: 1 translatable: true indexes: { } persist_with_no_fields: false diff --git a/modules/islandora_image/tests/src/Functional/GenerateImageDerivativeTest.php b/modules/islandora_image/tests/src/Functional/GenerateImageDerivativeTest.php index 69e18ba7..c917ba6c 100644 --- a/modules/islandora_image/tests/src/Functional/GenerateImageDerivativeTest.php +++ b/modules/islandora_image/tests/src/Functional/GenerateImageDerivativeTest.php @@ -11,6 +11,9 @@ use Drupal\Tests\islandora\Functional\GenerateDerivativeTestBase; */ class GenerateImageDerivativeTest extends GenerateDerivativeTestBase { + /** + * {@inheritdoc} + */ protected static $modules = ['context_ui', 'islandora_image']; /** diff --git a/modules/islandora_video/tests/src/Functional/GenerateVideoDerivativeTest.php b/modules/islandora_video/tests/src/Functional/GenerateVideoDerivativeTest.php index 64bd6d4a..02beca06 100644 --- a/modules/islandora_video/tests/src/Functional/GenerateVideoDerivativeTest.php +++ b/modules/islandora_video/tests/src/Functional/GenerateVideoDerivativeTest.php @@ -11,6 +11,9 @@ use Drupal\Tests\islandora\Functional\GenerateDerivativeTestBase; */ class GenerateVideoDerivativeTest extends GenerateDerivativeTestBase { + /** + * {@inheritdoc} + */ protected static $modules = ['context_ui', 'islandora_video']; /** diff --git a/src/ContextReaction/NormalizerAlterReaction.php b/src/ContextReaction/NormalizerAlterReaction.php index 503c7248..1dcd5c68 100644 --- a/src/ContextReaction/NormalizerAlterReaction.php +++ b/src/ContextReaction/NormalizerAlterReaction.php @@ -7,6 +7,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\jsonld\Form\JsonLdSettingsForm; +use Drupal\islandora\IslandoraUtils; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -25,16 +26,25 @@ abstract class NormalizerAlterReaction extends ContextReactionPluginBase impleme */ protected $jsonldConfig; + /** + * Islandora utils. + * + * @var \Drupal\islandora\IslandoraUtils + */ + protected $utils; + /** * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition, - ConfigFactoryInterface $config_factory) { + ConfigFactoryInterface $config_factory, + IslandoraUtils $utils) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->jsonldConfig = $config_factory->get(JsonLdSettingsForm::CONFIG_NAME); + $this->utils = $utils; } /** @@ -45,7 +55,8 @@ abstract class NormalizerAlterReaction extends ContextReactionPluginBase impleme $configuration, $plugin_id, $plugin_definition, - $container->get('config.factory') + $container->get('config.factory'), + $container->get('islandora.utils') ); } @@ -71,11 +82,11 @@ abstract class NormalizerAlterReaction extends ContextReactionPluginBase impleme * The url. */ protected function getSubjectUrl(EntityInterface $entity) { - $url = $entity->toUrl('canonical', ['absolute' => TRUE]); + $format = ''; if (!$this->jsonldConfig->get(JsonLdSettingsForm::REMOVE_JSONLD_FORMAT)) { - $url->setRouteParameter('_format', 'jsonld'); + $format = 'jsonld'; } - return $url->toString(); + return $this->utils->getRestUrl($entity, $format); } } diff --git a/src/Controller/MediaSourceController.php b/src/Controller/MediaSourceController.php index 28377ddc..2e5b4b26 100644 --- a/src/Controller/MediaSourceController.php +++ b/src/Controller/MediaSourceController.php @@ -11,6 +11,7 @@ use Drupal\media\MediaInterface; use Drupal\media\MediaTypeInterface; use Drupal\node\NodeInterface; use Drupal\taxonomy\TermInterface; +use Drupal\islandora\IslandoraUtils; use Drupal\islandora\MediaSource\MediaSourceService; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -39,6 +40,13 @@ class MediaSourceController extends ControllerBase { */ protected $database; + /** + * Islandora utils. + * + * @var \Drupal\islandora\IslandoraUtils + */ + protected $utils; + /** * MediaSourceController constructor. * @@ -46,13 +54,17 @@ class MediaSourceController extends ControllerBase { * Service for business logic. * @param \Drupal\Core\Database\Connection $database * Database connection. + * @param \Drupal\islandora\IslandoraUtils $utils + * Islandora utils. */ public function __construct( MediaSourceService $service, - Connection $database + Connection $database, + IslandoraUtils $utils ) { $this->service = $service; $this->database = $database; + $this->utils = $utils; } /** @@ -67,7 +79,8 @@ class MediaSourceController extends ControllerBase { public static function create(ContainerInterface $container) { return new static( $container->get('islandora.media_source_service'), - $container->get('database') + $container->get('database'), + $container->get('islandora.utils') ); } @@ -162,7 +175,7 @@ class MediaSourceController extends ControllerBase { // We return the media if it was newly created. if ($media) { $response = new Response("", 201); - $response->headers->set("Location", $media->url('canonical', ['absolute' => TRUE])); + $response->headers->set("Location", $this->utils->getEntityUrl($media)); } else { $response = new Response("", 204); diff --git a/src/EventGenerator/EventGenerator.php b/src/EventGenerator/EventGenerator.php index 4e73a145..4debec35 100644 --- a/src/EventGenerator/EventGenerator.php +++ b/src/EventGenerator/EventGenerator.php @@ -3,8 +3,7 @@ namespace Drupal\islandora\EventGenerator; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Language\LanguageManagerInterface; -use Drupal\Core\Url; +use Drupal\islandora\IslandoraUtils; use Drupal\islandora\MediaSource\MediaSourceService; use Drupal\user\UserInterface; @@ -16,11 +15,11 @@ use Drupal\user\UserInterface; class EventGenerator implements EventGeneratorInterface { /** - * Language manager. + * Islandora utils. * - * @var \Drupal\Core\Language\LanguageManagerInterface + * @var \Drupal\islandora\IslandoraUtils */ - protected $languageManager; + protected $utils; /** * Media source service. @@ -32,13 +31,13 @@ class EventGenerator implements EventGeneratorInterface { /** * Constructor. * - * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager - * Language manager. + * @param \Drupal\islandora\IslandoraUtils $utils + * Islandora utils. * @param \Drupal\islandora\MediaSource\MediaSourceService $media_source * Media source service. */ - public function __construct(LanguageManagerInterface $language_manager, MediaSourceService $media_source) { - $this->languageManager = $language_manager; + public function __construct(IslandoraUtils $utils, MediaSourceService $media_source) { + $this->utils = $utils; $this->mediaSource = $media_source; } @@ -47,21 +46,16 @@ class EventGenerator implements EventGeneratorInterface { */ public function generateEvent(EntityInterface $entity, UserInterface $user, array $data) { - $user_url = $user->toUrl()->setAbsolute()->toString(); - $entity_type = $entity->getEntityTypeId(); + $user_url = $this->utils->getEntityUrl($user); - $undefined = $this->languageManager->getLanguage('und'); + $entity_type = $entity->getEntityTypeId(); if ($entity_type == 'file') { - $entity_url = $entity->url('canonical', ['absolute' => TRUE, 'language' => $undefined]); + $entity_url = $this->utils->getDownloadUrl($entity); $mimetype = $entity->getMimeType(); } else { - $entity_url = Url::fromRoute( - "rest.entity.$entity_type.GET", - [$entity_type => $entity->id()], - ['absolute' => TRUE, 'language' => $undefined] - )->toString(); + $entity_url = $this->utils->getEntityUrl($entity); $mimetype = 'text/html'; } @@ -112,14 +106,14 @@ class EventGenerator implements EventGeneratorInterface { $event['object']['url'][] = [ "name" => "JSON", "type" => "Link", - "href" => "$entity_url?_format=json", + "href" => $this->utils->getRestUrl($entity, 'json'), "mediaType" => "application/json", "rel" => "alternate", ]; $event['object']['url'][] = [ "name" => "JSONLD", "type" => "Link", - "href" => "$entity_url?_format=jsonld", + "href" => $this->utils->getRestUrl($entity, 'jsonld'), "mediaType" => "application/ld+json", "rel" => "alternate", ]; @@ -132,7 +126,7 @@ class EventGenerator implements EventGeneratorInterface { $event['object']['url'][] = [ "name" => "Describes", "type" => "Link", - "href" => $file->url('canonical', ['absolute' => TRUE, 'language' => $undefined]), + "href" => $this->utils->getDownloadUrl($file), "mediaType" => $file->getMimeType(), "rel" => "describes", ]; diff --git a/src/EventSubscriber/LinkHeaderSubscriber.php b/src/EventSubscriber/LinkHeaderSubscriber.php index 6bb184fd..4edf7da4 100644 --- a/src/EventSubscriber/LinkHeaderSubscriber.php +++ b/src/EventSubscriber/LinkHeaderSubscriber.php @@ -6,10 +6,9 @@ use Drupal\Core\Access\AccessManagerInterface; use Drupal\Core\Entity\EntityFieldManager; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManager; -use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\Core\Url; +use Drupal\islandora\IslandoraUtils; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; @@ -66,11 +65,11 @@ abstract class LinkHeaderSubscriber implements EventSubscriberInterface { protected $requestStack; /** - * Language manager. + * Islandora utils. * - * @var \Drupal\Core\Language\LanguageManagerInterface + * @var \Drupal\islandora\IslandoraUtils */ - protected $languageManager; + protected $utils; /** * Constructor. @@ -87,8 +86,8 @@ abstract class LinkHeaderSubscriber implements EventSubscriberInterface { * The route match object. * @param Symfony\Component\HttpFoundation\RequestStack $request_stack * Request stack (for current request). - * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager - * Language manager. + * @param \Drupal\islandora\IslandoraUtils $utils + * Islandora utils. */ public function __construct( EntityTypeManager $entity_type_manager, @@ -97,7 +96,7 @@ abstract class LinkHeaderSubscriber implements EventSubscriberInterface { AccountInterface $account, RouteMatchInterface $route_match, RequestStack $request_stack, - LanguageManagerInterface $language_manager + IslandoraUtils $utils ) { $this->entityTypeManager = $entity_type_manager; $this->entityFieldManager = $entity_field_manager; @@ -107,7 +106,7 @@ abstract class LinkHeaderSubscriber implements EventSubscriberInterface { $this->accessManager = $access_manager; $this->account = $account; $this->requestStack = $request_stack; - $this->languageManager = $language_manager; + $this->utils = $utils; } /** @@ -206,6 +205,8 @@ abstract class LinkHeaderSubscriber implements EventSubscriberInterface { // Headers are subject to an access check. if ($referencedEntity->access('view')) { + $entity_url = $this->utils->getEntityUrl($referencedEntity); + // Taxonomy terms are written out as // ; rel="tag"; title="Tag Name" // where url is defined in field_same_as. @@ -213,7 +214,6 @@ abstract class LinkHeaderSubscriber implements EventSubscriberInterface { // it becomes the taxonomy term's local uri. if ($referencedEntity->getEntityTypeId() == 'taxonomy_term') { $rel = "tag"; - $entity_url = $referencedEntity->url('canonical', ['absolute' => TRUE]); if ($referencedEntity->hasField('field_external_uri')) { $external_uri = $referencedEntity->get('field_external_uri')->getValue(); if (!empty($external_uri) && isset($external_uri[0]['uri'])) { @@ -228,7 +228,6 @@ abstract class LinkHeaderSubscriber implements EventSubscriberInterface { // ; rel="related"; title="Field Label" // and the url is the local uri. $rel = "related"; - $entity_url = $referencedEntity->url('canonical', ['absolute' => TRUE]); $title = $field_definition->label(); } $links[] = "<$entity_url>; rel=\"$rel\"; title=\"$title\""; @@ -287,19 +286,16 @@ abstract class LinkHeaderSubscriber implements EventSubscriberInterface { continue; } + // Skip route if the user doesn't have access. $meta_route_name = "rest.entity.$entity_type.GET"; - $route_params = [$entity_type => $entity->id()]; - if (!$this->accessManager->checkNamedRoute($meta_route_name, $route_params, $this->account)) { continue; } - $meta_url = Url::fromRoute($meta_route_name, $route_params) - ->setAbsolute() - ->toString(); + $meta_url = $this->utils->getRestUrl($entity, $format); - $links[] = "<$meta_url?_format=$format>; rel=\"alternate\"; type=\"$mime\""; + $links[] = "<$meta_url>; rel=\"alternate\"; type=\"$mime\""; } } diff --git a/src/EventSubscriber/MediaLinkHeaderSubscriber.php b/src/EventSubscriber/MediaLinkHeaderSubscriber.php index 6e344f0d..644db872 100644 --- a/src/EventSubscriber/MediaLinkHeaderSubscriber.php +++ b/src/EventSubscriber/MediaLinkHeaderSubscriber.php @@ -77,10 +77,9 @@ class MediaLinkHeaderSubscriber extends LinkHeaderSubscriber implements EventSub } // Collect file links for the media. - $undefined = $this->languageManager->getLanguage('und'); foreach ($media->get($source_field)->referencedEntities() as $referencedEntity) { if ($referencedEntity->access('view')) { - $file_url = $referencedEntity->url('canonical', ['absolute' => TRUE, 'language' => $undefined]); + $file_url = $this->utils->getDownloadUrl($referencedEntity); $links[] = "<$file_url>; rel=\"describes\"; type=\"{$referencedEntity->getMimeType()}\""; } } diff --git a/src/EventSubscriber/NodeLinkHeaderSubscriber.php b/src/EventSubscriber/NodeLinkHeaderSubscriber.php index 4d898f8f..443ce86f 100644 --- a/src/EventSubscriber/NodeLinkHeaderSubscriber.php +++ b/src/EventSubscriber/NodeLinkHeaderSubscriber.php @@ -2,17 +2,8 @@ namespace Drupal\islandora\EventSubscriber; -use Drupal\Core\Access\AccessManagerInterface; -use Drupal\Core\Entity\EntityFieldManager; -use Drupal\Core\Entity\EntityTypeManager; -use Drupal\Core\Language\LanguageManagerInterface; -use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\Core\Session\AccountInterface; -use Drupal\Core\Url; -use Drupal\islandora\IslandoraUtils; use Drupal\node\NodeInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -22,55 +13,6 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; */ class NodeLinkHeaderSubscriber extends LinkHeaderSubscriber implements EventSubscriberInterface { - /** - * Derivative utils. - * - * @var \Drupal\islandora\IslandoraUtils - */ - protected $utils; - - /** - * Constructor. - * - * @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager - * The entity type manager. - * @param \Drupal\Core\Entity\EntityFieldManager $entity_field_manager - * The entity field manager. - * @param \Drupal\Core\Access\AccessManagerInterface $access_manager - * The access manager. - * @param \Drupal\Core\Session\AccountInterface $account - * The current user. - * @param \Drupal\Core\Routing\RouteMatchInterface $route_match - * The route match object. - * @param Symfony\Component\HttpFoundation\RequestStack $request_stack - * Request stack (for current request). - * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager - * Language manager. - * @param \Drupal\islandora\IslandoraUtils $utils - * Derivative utils. - */ - public function __construct( - EntityTypeManager $entity_type_manager, - EntityFieldManager $entity_field_manager, - AccessManagerInterface $access_manager, - AccountInterface $account, - RouteMatchInterface $route_match, - RequestStack $request_stack, - LanguageManagerInterface $language_manager, - IslandoraUtils $utils - ) { - parent::__construct( - $entity_type_manager, - $entity_field_manager, - $access_manager, - $account, - $route_match, - $request_stack, - $language_manager - ); - $this->utils = $utils; - } - /** * Adds node-specific link headers to appropriate responses. * @@ -105,13 +47,8 @@ class NodeLinkHeaderSubscriber extends LinkHeaderSubscriber implements EventSubs */ protected function generateRelatedMediaLinks(NodeInterface $node) { $links = []; - $undefined = $this->languageManager->getLanguage('und'); foreach ($this->utils->getMedia($node) as $media) { - $url = Url::fromRoute( - "rest.entity.media.GET", - ['media' => $media->id()], - ['absolute' => TRUE, 'language' => $undefined] - )->toString(); + $url = $this->utils->getEntityUrl($media); foreach ($media->referencedEntities() as $term) { if ($term->getEntityTypeId() == 'taxonomy_term' && $term->hasField('field_external_uri')) { $field = $term->get('field_external_uri'); diff --git a/src/Flysystem/Adapter/FedoraAdapter.php b/src/Flysystem/Adapter/FedoraAdapter.php index 03900753..4aebde6e 100644 --- a/src/Flysystem/Adapter/FedoraAdapter.php +++ b/src/Flysystem/Adapter/FedoraAdapter.php @@ -20,7 +20,18 @@ class FedoraAdapter implements AdapterInterface { use StreamedCopyTrait; use NotSupportingVisibilityTrait; + /** + * Fedora client. + * + * @var \Islandora\Chullo\IFedoraApi + */ protected $fedora; + + /** + * Mimetype guesser. + * + * @var \Symfony\Component\HttpFoundation\File\Mimetype\MimeTypeGuesserInterface + */ protected $mimeTypeGuesser; /** @@ -75,7 +86,6 @@ class FedoraAdapter implements AdapterInterface { $meta = $this->getMetadataFromHeaders($response); $meta['path'] = $path; - if ($meta['type'] == 'file') { $meta['stream'] = StreamWrapper::getResource($response->getBody()); } diff --git a/src/Flysystem/Fedora.php b/src/Flysystem/Fedora.php index 85f42b1a..b90fc529 100644 --- a/src/Flysystem/Fedora.php +++ b/src/Flysystem/Fedora.php @@ -27,8 +27,18 @@ class Fedora implements FlysystemPluginInterface, ContainerFactoryPluginInterfac use FlysystemUrlTrait; + /** + * Fedora client. + * + * @var \Islandora\Chullo\IFedoraApi + */ protected $fedora; + /** + * Mimetype guesser. + * + * @var \Symfony\Component\HttpFoundation\File\Mimetype\MimeTypeGuesserInterface + */ protected $mimeTypeGuesser; /** diff --git a/src/IslandoraUtils.php b/src/IslandoraUtils.php index e6ba1b75..e98766c7 100644 --- a/src/IslandoraUtils.php +++ b/src/IslandoraUtils.php @@ -4,12 +4,15 @@ namespace Drupal\islandora; use Drupal\context\ContextManager; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityFieldManager; use Drupal\Core\Entity\EntityTypeManager; use Drupal\Core\Entity\Query\QueryException; use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\Entity\Query\QueryInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Site\Settings; +use Drupal\Core\Url; use Drupal\file\FileInterface; use Drupal\flysystem\FlysystemFactory; use Drupal\islandora\ContextProvider\NodeContextProvider; @@ -64,6 +67,13 @@ class IslandoraUtils { */ protected $flysystemFactory; + /** + * Language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + /** * Constructor. * @@ -77,19 +87,23 @@ class IslandoraUtils { * Context manager. * @param \Drupal\flysystem\FlysystemFactory $flysystem_factory * Flysystem factory. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * Language manager. */ public function __construct( EntityTypeManager $entity_type_manager, EntityFieldManager $entity_field_manager, QueryFactory $entity_query, ContextManager $context_manager, - FlysystemFactory $flysystem_factory + FlysystemFactory $flysystem_factory, + LanguageManagerInterface $language_manager ) { $this->entityTypeManager = $entity_type_manager; $this->entityFieldManager = $entity_field_manager; $this->entityQuery = $entity_query; $this->contextManager = $context_manager; $this->flysystemFactory = $flysystem_factory; + $this->languageManager = $language_manager; } /** @@ -498,4 +512,62 @@ class IslandoraUtils { return $condition; } + /** + * Gets the id URL of an entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity whose URL you want. + * + * @return string + * The entity URL. + */ + public function getEntityUrl(EntityInterface $entity) { + $undefined = $this->languageManager->getLanguage('und'); + $entity_type = $entity->getEntityTypeId(); + return Url::fromRoute( + "entity.$entity_type.canonical", + [$entity_type => $entity->id()], + ['absolute' => TRUE, 'language' => $undefined] + )->toString(); + } + + /** + * Gets the downloadable URL for a file. + * + * @param \Drupal\file\FileInterface $file + * The file whose URL you want. + * + * @return string + * The file URL. + */ + public function getDownloadUrl(FileInterface $file) { + $undefined = $this->languageManager->getLanguage('und'); + return $file->url('canonical', ['absolute' => TRUE, 'language' => $undefined]); + } + + /** + * Gets the URL for an entity's REST endpoint. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity whose REST endpoint you want. + * @param string $format + * REST serialization format. + * + * @return string + * The REST URL. + */ + public function getRestUrl(EntityInterface $entity, $format = '') { + $undefined = $this->languageManager->getLanguage('und'); + $entity_type = $entity->getEntityTypeId(); + $rest_url = Url::fromRoute( + "rest.entity.$entity_type.GET", + [$entity_type => $entity->id()], + ['absolute' => TRUE, 'language' => $undefined] + )->toString(); + if (!empty($format)) { + $rest_url .= "?_format=$format"; + } + return $rest_url; + } + } diff --git a/src/Plugin/Action/AbstractGenerateDerivative.php b/src/Plugin/Action/AbstractGenerateDerivative.php index 4635a95c..233ce774 100644 --- a/src/Plugin/Action/AbstractGenerateDerivative.php +++ b/src/Plugin/Action/AbstractGenerateDerivative.php @@ -155,7 +155,7 @@ class AbstractGenerateDerivative extends EmitEvent { throw new \RuntimeException("Could not locate source file for media {$source_media->id()}", 500); } - $data['source_uri'] = $source_file->url('canonical', ['absolute' => TRUE]); + $data['source_uri'] = $this->utils->getDownloadUrl($source_file); // Find the term for the derivative and use it to set the destination url // in the data array. diff --git a/src/Plugin/Action/DeleteMedia.php b/src/Plugin/Action/DeleteMedia.php deleted file mode 100644 index 82c4d842..00000000 --- a/src/Plugin/Action/DeleteMedia.php +++ /dev/null @@ -1,35 +0,0 @@ -delete(); - } - } - - /** - * {@inheritdoc} - */ - public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { - return $object->access('delete', $account, $return_as_object); - } - -} diff --git a/src/Plugin/Condition/MediaHasMimetype.php b/src/Plugin/Condition/MediaHasMimetype.php new file mode 100644 index 00000000..fcf085ed --- /dev/null +++ b/src/Plugin/Condition/MediaHasMimetype.php @@ -0,0 +1,174 @@ +utils = $utils; + $this->entityTypeManager = $entity_type_manager; + $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.utils'), + $container->get('entity_type.manager'), + $container->get('islandora.media_source_service') + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form['mimetypes'] = [ + '#type' => 'textfield', + '#title' => t('Mime types'), + '#default_value' => $this->configuration['mimetypes'], + '#required' => TRUE, + '#maxlength' => 256, + '#description' => t('Comma-delimited list of Mime types (e.g. image/jpeg, video/mp4, etc...) that trigger the condition.'), + ]; + return parent::buildConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['mimetypes'] = $form_state->getValue('mimetypes'); + parent::submitConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function summary() { + $mimetypes = $this->configuration['mimetypes']; + return $this->t( + 'The media has one of the Mime types @mimetypes', + [ + '@mimetypes' => $mimetypes, + ] + ); + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + if (empty($this->configuration['mimetypes']) && !$this->isNegated()) { + return TRUE; + } + + $node = \Drupal::routeMatch()->getParameter('node'); + + if (is_null($node) || is_string($node)) { + return FALSE; + } + + $media = $this->utils->getMedia($node); + + if (count($media) > 0) { + $mimetypes = explode(',', str_replace(' ', '', $this->configuration['mimetypes'])); + foreach ($media as $medium) { + $file = $this->mediaSource->getSourceFile($medium); + if (in_array($file->getMimeType(), $mimetypes)) { + return $this->isNegated() ? FALSE : TRUE; + } + } + } + else { + return FALSE; + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array_merge( + ['mimetypes' => ''], + parent::defaultConfiguration() + ); + } + +} diff --git a/src/Plugin/Condition/NodeHadNamespace.php b/src/Plugin/Condition/NodeHadNamespace.php new file mode 100644 index 00000000..a033913a --- /dev/null +++ b/src/Plugin/Condition/NodeHadNamespace.php @@ -0,0 +1,187 @@ +utils = $utils; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@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('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form['namespace'] = [ + '#type' => 'textfield', + '#title' => $this->t('Islandora 7.x Namespaces'), + '#description' => $this->t('Comma-delimited list of 7.x PID namespaces, including the trailing colon (e.g., "islandora:,ir:").'), + '#default_value' => $this->configuration['namespace'], + '#maxlength' => 256, + ]; + $field_map = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('string'); + $node_fields = array_keys($field_map['node']); + $options = array_combine($node_fields, $node_fields); + $form['pid_field'] = [ + '#type' => 'select', + '#title' => t('Field that contains the PID'), + '#options' => $options, + '#default_value' => $this->configuration['pid_field'], + '#required' => TRUE, + '#description' => t("Machine name of the field that contains the PID."), + ]; + + return parent::buildConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['namespace'] = NULL; + $namespace = $form_state->getValue('namespace'); + if (!empty($namespace)) { + if ($namespace) { + $this->configuration['namespace'] = $namespace; + } + } + $this->configuration['pid_field'] = $form_state->getValue('pid_field'); + parent::submitConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + if (empty($this->configuration['namespace']) && !$this->isNegated()) { + return TRUE; + } + + $node = $this->getContextValue('node'); + if (!$node) { + return FALSE; + } + return $this->evaluateEntity($node); + } + + /** + * Evaluates if the value of field_pid with a registered 7.x namespace. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to evalute. + * + * @return bool + * TRUE if entity has the specified namespace, otherwise FALSE. + */ + protected function evaluateEntity(EntityInterface $entity) { + $pid_field = $this->configuration['pid_field']; + if ($entity->hasField($pid_field)) { + $pid_value = $entity->get($pid_field)->getValue(); + $pid = $pid_value[0]['value']; + $namespace = strtok($pid, ':') . ':'; + $registered_namespaces = explode(',', $this->configuration['namespace']); + foreach ($registered_namespaces as &$registered_namespace) { + $registered_namespace = trim($registered_namespace); + if (in_array($namespace, $registered_namespaces)) { + return $this->isNegated() ? FALSE : TRUE; + } + } + } + + return $this->isNegated() ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function summary() { + if (!empty($this->configuration['negate'])) { + return $this->t('The node does not have a value in its PID field with the namespace @namespace.', ['@namespace' => $this->configuration['namespace']]); + } + else { + return $this->t('The node has a value in its PID field with the namespace @namespace.', ['@namespace' => $this->configuration['namespace']]); + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array_merge( + ['namespace' => '', 'pid_field' => 'field_pid'], + parent::defaultConfiguration() + ); + } + +} diff --git a/src/Plugin/Condition/NodeHasParent.php b/src/Plugin/Condition/NodeHasParent.php new file mode 100644 index 00000000..df95c6ad --- /dev/null +++ b/src/Plugin/Condition/NodeHasParent.php @@ -0,0 +1,168 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return parent::defaultConfiguration() + [ + 'parent_reference_field' => 'field_member_of', + 'parent_nid' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form['parent_nid'] = [ + '#type' => 'entity_autocomplete', + '#title' => t('Parent node'), + '#default_value' => $this->entityTypeManager->getStorage('node')->load($this->configuration['parent_nid']), + '#required' => TRUE, + '#description' => t("Can be a collection node or a compound object."), + '#target_type' => 'node', + ]; + $field_map = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('entity_reference'); + $node_fields = array_keys($field_map['node']); + $options = array_combine($node_fields, $node_fields); + $form['parent_reference_field'] = [ + '#type' => 'select', + '#title' => t('Field that contains reference to parents'), + '#options' => $options, + '#default_value' => $this->configuration['parent_reference_field'], + '#required' => TRUE, + '#description' => t("Machine name of field that contains references to parent node."), + ]; + + return parent::buildConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['parent_nid'] = $form_state->getValue('parent_nid'); + $this->configuration['parent_reference_field'] = $form_state->getValue('parent_reference_field'); + parent::submitConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + if (empty($this->configuration['parent_nid']) && !$this->isNegated()) { + return TRUE; + } + + $node = $this->getContextValue('node'); + if (!$node) { + return FALSE; + } + return $this->evaluateEntity($node); + } + + /** + * Evaluates if an entity has the specified node as its parent. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to evalute. + * + * @return bool + * TRUE if entity references the specified parent. + */ + protected function evaluateEntity(EntityInterface $entity) { + foreach ($entity->referencedEntities() as $referenced_entity) { + if ($entity->getEntityTypeID() == 'node' && $referenced_entity->getEntityTypeId() == 'node') { + $parent_reference_field = $this->configuration['parent_reference_field']; + $field = $entity->get($parent_reference_field); + if (!$field->isEmpty()) { + $nids = $field->getValue(); + foreach ($nids as $nid) { + if ($nid['target_id'] == $this->configuration['parent_nid']) { + return $this->isNegated() ? FALSE : TRUE; + } + } + } + } + } + } + + /** + * {@inheritdoc} + */ + public function summary() { + if (!empty($this->configuration['negate'])) { + return $this->t('The node does not have node @nid as its parent.', ['@nid' => $this->configuration['parent_nid']]); + } + else { + return $this->t('The node has node @nid as its parent.', ['@nid' => $this->configuration['parent_nid']]); + } + } + +} diff --git a/src/Plugin/Condition/NodeIsPublished.php b/src/Plugin/Condition/NodeIsPublished.php new file mode 100644 index 00000000..a378aaa4 --- /dev/null +++ b/src/Plugin/Condition/NodeIsPublished.php @@ -0,0 +1,94 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + $node = $this->getContextValue('node'); + if (!$node && !$this->isNegated()) { + return FALSE; + } + if ($node->isPublished() && !$this->isNegated()) { + return TRUE; + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function summary() { + if (!empty($this->configuration['negate'])) { + return $this->t('The node is not published.'); + } + else { + return $this->t('The node is published.'); + } + } + +} diff --git a/src/Plugin/ContextReaction/MappingUriPredicateReaction.php b/src/Plugin/ContextReaction/MappingUriPredicateReaction.php index c59e48c8..05f2f8c0 100644 --- a/src/Plugin/ContextReaction/MappingUriPredicateReaction.php +++ b/src/Plugin/ContextReaction/MappingUriPredicateReaction.php @@ -9,6 +9,7 @@ use Drupal\islandora\ContextReaction\NormalizerAlterReaction; use Drupal\islandora\MediaSource\MediaSourceService; use Drupal\jsonld\Normalizer\NormalizerBase; use Drupal\media\MediaInterface; +use Drupal\islandora\IslandoraUtils; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -23,6 +24,11 @@ class MappingUriPredicateReaction extends NormalizerAlterReaction { const URI_PREDICATE = 'drupal_uri_predicate'; + /** + * Media source service. + * + * @var \Drupal\islandora\MediaSource\MediaSourceService + */ protected $mediaSource; /** @@ -32,13 +38,15 @@ class MappingUriPredicateReaction extends NormalizerAlterReaction { $plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory, + IslandoraUtils $utils, MediaSourceService $media_source) { parent::__construct( $configuration, $plugin_id, $plugin_definition, - $config_factory + $config_factory, + $utils ); $this->mediaSource = $media_source; } @@ -52,6 +60,7 @@ class MappingUriPredicateReaction extends NormalizerAlterReaction { $plugin_id, $plugin_definition, $container->get('config.factory'), + $container->get('islandora.utils'), $container->get('islandora.media_source_service') ); } @@ -80,7 +89,7 @@ class MappingUriPredicateReaction extends NormalizerAlterReaction { // Swap media and file urls. if ($entity instanceof MediaInterface) { $file = $this->mediaSource->getSourceFile($entity); - $graph['@id'] = $file->url('canonical', ['absolute' => TRUE]); + $graph['@id'] = $this->utils->getDownloadUrl($file); } if (isset($graph[$drupal_predicate])) { if (!is_array($graph[$drupal_predicate])) { diff --git a/src/PresetReaction/PresetReaction.php b/src/PresetReaction/PresetReaction.php index 0584b997..98aa6946 100644 --- a/src/PresetReaction/PresetReaction.php +++ b/src/PresetReaction/PresetReaction.php @@ -14,6 +14,11 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class PresetReaction extends ContextReactionPluginBase implements ContainerFactoryPluginInterface { + /** + * Action storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ protected $actionStorage; /** diff --git a/tests/src/Functional/DeleteMediaTest.php b/tests/src/Functional/DeleteMediaTest.php index 1a3673aa..03192194 100644 --- a/tests/src/Functional/DeleteMediaTest.php +++ b/tests/src/Functional/DeleteMediaTest.php @@ -35,31 +35,6 @@ class DeleteMediaTest extends IslandoraFunctionalTestBase { list($this->file, $this->media) = $this->makeMediaAndFile($account); } - /** - * Tests the delete_media action. - * - * @covers \Drupal\islandora\Plugin\Action\DeleteMedia::execute - */ - public function testDeleteMedia() { - $action = $this->container->get('entity_type.manager')->getStorage('action')->load('delete_media'); - - $mid = $this->media->id(); - $fid = $this->file->id(); - - $action->execute([$this->media]); - - // Attempt to reload the entities. - // Media should be gone but file should remain. - $this->assertTrue( - !$this->container->get('entity_type.manager')->getStorage('media')->load($mid), - "Media must be deleted after running action" - ); - $this->assertTrue( - $this->container->get('entity_type.manager')->getStorage('file')->load($fid), - "File must remain after running action" - ); - } - /** * Tests the delete_media_and_file action. * diff --git a/tests/src/Functional/GenerateDerivativeTestBase.php b/tests/src/Functional/GenerateDerivativeTestBase.php index b650ca3b..c74ff255 100644 --- a/tests/src/Functional/GenerateDerivativeTestBase.php +++ b/tests/src/Functional/GenerateDerivativeTestBase.php @@ -7,6 +7,9 @@ namespace Drupal\Tests\islandora\Functional; */ abstract class GenerateDerivativeTestBase extends IslandoraFunctionalTestBase { + /** + * {@inheritdoc} + */ protected static $modules = ['context_ui']; /** diff --git a/tests/src/Functional/IslandoraFunctionalTestBase.php b/tests/src/Functional/IslandoraFunctionalTestBase.php index e1d6e91f..9a50fb05 100644 --- a/tests/src/Functional/IslandoraFunctionalTestBase.php +++ b/tests/src/Functional/IslandoraFunctionalTestBase.php @@ -20,8 +20,14 @@ class IslandoraFunctionalTestBase extends BrowserTestBase { use TestFileCreationTrait; use MediaTypeCreationTrait; + /** + * {@inheritdoc} + */ protected static $modules = ['context_ui', 'field_ui', 'islandora']; + /** + * {@inheritdoc} + */ protected static $configSchemaCheckerExclusions = [ 'jwt.config', 'context.context.test', @@ -29,12 +35,28 @@ class IslandoraFunctionalTestBase extends BrowserTestBase { 'context.context.media', 'context.context.file', 'key.key.test', + 'media.settings', ]; + /** + * Test node type. + * + * @var \Drupal\node\Entity\NodeType + */ protected $testType; + /** + * Test media type. + * + * @var \Drupal\media\Entity\MediaType + */ protected $testMediaType; + /** + * Test vocabulary. + * + * @var \Drupal\taxonomy\Entity\Vocabulary + */ protected $testVocabulary; /** @@ -170,6 +192,10 @@ EOD; $destination->write($name, $source->read($name)); } + $media_settings = $this->container->get('config.factory')->getEditable('media.settings'); + $media_settings->set('standalone_url', TRUE); + $media_settings->save(TRUE); + // Cache clear / rebuild. drupal_flush_all_caches(); $this->container->get('router.builder')->rebuild(); diff --git a/tests/src/Kernel/EventGeneratorTest.php b/tests/src/Kernel/EventGeneratorTest.php index 21491be0..c423cda3 100644 --- a/tests/src/Kernel/EventGeneratorTest.php +++ b/tests/src/Kernel/EventGeneratorTest.php @@ -65,7 +65,7 @@ class EventGeneratorTest extends IslandoraKernelTestBase { // Create the event generator so we can test it. $this->eventGenerator = new EventGenerator( - $this->container->get('language_manager'), + $this->container->get('islandora.utils'), $this->container->get('islandora.media_source_service') ); } diff --git a/tests/src/Kernel/GeminiClientFactoryTest.php b/tests/src/Kernel/GeminiClientFactoryTest.php index 3259c221..1bbd8e75 100644 --- a/tests/src/Kernel/GeminiClientFactoryTest.php +++ b/tests/src/Kernel/GeminiClientFactoryTest.php @@ -18,6 +18,11 @@ use Psr\Log\LoggerInterface; */ class GeminiClientFactoryTest extends IslandoraKernelTestBase { + /** + * Logger. + * + * @var \Psr\Log\LoggerInterface + */ private $logger; /** diff --git a/tests/src/Kernel/GeminiLookupTest.php b/tests/src/Kernel/GeminiLookupTest.php index 600fea73..15edc2e8 100644 --- a/tests/src/Kernel/GeminiLookupTest.php +++ b/tests/src/Kernel/GeminiLookupTest.php @@ -23,18 +23,53 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; */ class GeminiLookupTest extends IslandoraKernelTestBase { + /** + * JWT Auth. + * + * @var \Drupal\jwt\Authentication\Provider\JwtAuth + */ private $jwtAuth; + /** + * Logger. + * + * @var \Psr\Log\LoggerInterface + */ private $logger; + /** + * Guzzle. + * + * @var \GuzzleHttp\Client + */ private $guzzle; + /** + * Gemini client. + * + * @var \Islandora\Crayfish\Commons\Client\GeminiClient + */ private $geminiClient; + /** + * Media source service. + * + * @var \Drupal\islandora\MediaSource\MediaSourceService + */ private $mediaSource; + /** + * An entity. + * + * @var \Drupal\Core\Entity\EntityInterface + */ private $entity; + /** + * A media. + * + * @var \Drupal\media\MediaInterface + */ private $media; /**