diff --git a/islandora.module b/islandora.module index 1accbe16..dc580398 100644 --- a/islandora.module +++ b/islandora.module @@ -181,3 +181,13 @@ function islandora_entity_delete(EntityInterface $entity) { } } } + +/** + * Implements hook_jsonld_alter_normalized_array(). + */ +function islandora_jsonld_alter_normalized_array(EntityInterface $entity, array &$normalized, array $context) { + $context_manager = \Drupal::service('context.manager'); + foreach ($context_manager->getActiveReactions('\Drupal\islandora\ContextReaction\NormalizerAlterReaction') as $reaction) { + $reaction->execute($entity, $normalized, $context); + } +} diff --git a/src/ContextReaction/NormalizerAlterReaction.php b/src/ContextReaction/NormalizerAlterReaction.php new file mode 100644 index 00000000..3b7f807b --- /dev/null +++ b/src/ContextReaction/NormalizerAlterReaction.php @@ -0,0 +1,29 @@ +t('Map Drupal URI to configured predicate.'); + } + + /** + * {@inheritdoc} + */ + public function execute(EntityInterface $entity = NULL, array &$normalized = NULL, array $context = NULL) { + $config = $this->getConfiguration(); + $drupal_predicate = $config[self::URI_PREDICATE]; + if (!is_null($drupal_predicate) && !empty($drupal_predicate)) { + $url = $entity + ->toUrl('canonical', ['absolute' => TRUE]) + ->setRouteParameter('_format', 'jsonld') + ->toString(); + if ($context['needs_jsonldcontext'] === FALSE) { + $drupal_predicate = NormalizerBase::escapePrefix($drupal_predicate, $context['namespaces']); + } + if (isset($normalized['@graph']) && is_array($normalized['@graph'])) { + foreach ($normalized['@graph'] as &$graph) { + if (isset($graph['@id']) && $graph['@id'] == $url) { + if (isset($graph[$drupal_predicate])) { + if (!is_array($graph[$drupal_predicate])) { + if ($graph[$drupal_predicate] == $url) { + // Don't add it if it already exists. + return; + } + $tmp = $graph[$drupal_predicate]; + $graph[$drupal_predicate] = [$tmp]; + } + elseif (array_search($url, array_column($graph[$drupal_predicate], '@value'))) { + // Don't add it if it already exists. + return; + } + } + else { + $graph[$drupal_predicate] = []; + } + $graph[$drupal_predicate][] = ["@value" => $url]; + return; + } + } + } + } + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $config = $this->getConfiguration(); + $form[self::URI_PREDICATE] = [ + '#type' => 'textfield', + '#title' => $this->t('Drupal URI predicate'), + '#description' => $this->t("The Drupal object's URI will be added to the resource with this predicate. Must use a defined prefix."), + '#default_value' => isset($config[self::URI_PREDICATE]) ? $config[self::URI_PREDICATE] : '', + '#size' => 35, + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + $drupal_predicate = $form_state->getValue(self::URI_PREDICATE); + if (!is_null($drupal_predicate) and !empty($drupal_predicate)) { + if (preg_match('/^https?:\/\//', $drupal_predicate)) { + // Can't validate all URIs so we have to trust them. + return; + } + elseif (preg_match('/^([^\s:]+):/', $drupal_predicate, $matches)) { + $predicate_prefix = $matches[1]; + $rdf = rdf_get_namespaces(); + $rdf_prefixes = array_keys($rdf); + if (!in_array($predicate_prefix, $rdf_prefixes)) { + $form_state->setErrorByName( + self::URI_PREDICATE, + $this->t('Namespace prefix @prefix is not registered.', + ['@prefix' => $predicate_prefix] + ) + ); + } + } + else { + $form_state->setErrorByName( + self::URI_PREDICATE, + $this->t('Predicate must use a defined prefix or be a full URI') + ); + } + } + parent::validateConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->setConfiguration([self::URI_PREDICATE => $form_state->getValue(self::URI_PREDICATE)]); + } + +} diff --git a/tests/src/Functional/MappingUriPredicateReactionTest.php b/tests/src/Functional/MappingUriPredicateReactionTest.php new file mode 100644 index 00000000..6b95e087 --- /dev/null +++ b/tests/src/Functional/MappingUriPredicateReactionTest.php @@ -0,0 +1,156 @@ + ['schema:dateCreated'], + 'datatype' => 'xsd:dateTime', + 'datatype_callback' => ['callable' => 'Drupal\rdf\CommonDataConverter::dateIso8601Value'], + ]; + + // Save bundle mapping config. + $this->rdfMapping = rdf_get_mapping('node', 'test_type') + ->setBundleMapping(['types' => $types]) + ->setFieldMapping('created', $created_mapping) + ->setFieldMapping('title', [ + 'properties' => ['dc:title'], + 'datatype' => 'xsd:string', + ]) + ->save(); + + $resourceConfigStorage = $this->container + ->get('entity_type.manager') + ->getStorage('rest_resource_config'); + // There is already a setting for entity.node, so delete it. + $resourceConfigStorage + ->delete($resourceConfigStorage + ->loadMultiple(['entity.node'])); + // Create it new. + $resourceConfigStorage->create([ + 'id' => 'entity.node', + 'granularity' => 'resource', + 'configuration' => [ + 'methods' => ['GET'], + 'authentication' => ['basic_auth', 'cookie'], + 'formats' => ['jsonld'], + ], + 'status' => TRUE, + ])->save(TRUE); + + $this->container->get('router.builder')->rebuildIfNeeded(); + } + + /** + * @covers \Drupal\islandora\Plugin\ContextReaction\MappingUriPredicateReaction + */ + public function testMappingReaction() { + $account = $this->drupalCreateUser([ + 'bypass node access', + 'administer contexts', + ]); + $this->drupalLogin($account); + + $context_name = 'test'; + $reaction_id = 'islandora_map_uri_predicate'; + + $this->postNodeAddForm('test_type', + ['title[0][value]' => 'Test Node'], + t('Save')); + $this->assertSession()->pageTextContains("Test Node"); + $url = $this->getUrl(); + + // Make sure the node exists. + $this->drupalGet($url); + $this->assertSession()->statusCodeEquals(200); + + $contents = $this->drupalGet($url . '?_format=jsonld'); + $this->assertSession()->statusCodeEquals(200); + $json = \GuzzleHttp\json_decode($contents, TRUE); + $this->assertArrayHasKey('http://purl.org/dc/terms/title', + $json['@graph'][0], 'Missing dcterms:title key'); + $this->assertEquals( + 'Test Node', + $json['@graph'][0]['http://purl.org/dc/terms/title'][0]['@value'], + 'Missing title value' + ); + $this->assertArrayNotHasKey('http://www.w3.org/2002/07/owl#sameAs', + $json['@graph'][0], 'Has predicate when not configured'); + + $this->createContext('Test', $context_name); + $this->drupalGet("admin/structure/context/$context_name/reaction/add/$reaction_id"); + $this->assertSession()->statusCodeEquals(200); + + $this->drupalGet("admin/structure/context/$context_name"); + // Can't use an undefined prefix. + $this->getSession()->getPage() + ->fillField("Drupal URI predicate", "bob:smith"); + $this->getSession()->getPage()->pressButton("Save and continue"); + $this->assertSession() + ->pageTextContains("Namespace prefix bob is not registered"); + + // Can't use a straight string. + $this->getSession()->getPage() + ->fillField("Drupal URI predicate", "woohoo"); + $this->getSession()->getPage()->pressButton("Save and continue"); + $this->assertSession() + ->pageTextContains("Predicate must use a defined prefix or be a full URI"); + + // Use an existing prefix. + $this->getSession()->getPage() + ->fillField("Drupal URI predicate", "owl:sameAs"); + $this->getSession()->getPage()->pressButton("Save and continue"); + $this->assertSession() + ->pageTextContains("The context $context_name has been saved"); + + $new_contents = $this->drupalGet($url . '?_format=jsonld'); + $json = \GuzzleHttp\json_decode($new_contents, TRUE); + $this->assertEquals( + 'Test Node', + $json['@graph'][0]['http://purl.org/dc/terms/title'][0]['@value'], + 'Missing title value' + ); + $this->assertEquals( + "$url?_format=jsonld", + $json['@graph'][0]['http://www.w3.org/2002/07/owl#sameAs'][0]['@value'], + 'Missing alter added predicate.' + ); + + $this->drupalGet("admin/structure/context/$context_name"); + // Change to a random URL. + $this->getSession()->getPage() + ->fillField("Drupal URI predicate", "http://example.org/first/second"); + $this->getSession()->getPage()->pressButton("Save and continue"); + $this->assertSession() + ->pageTextContains("The context $context_name has been saved"); + $new_contents = $this->drupalGet($url . '?_format=jsonld'); + $json = \GuzzleHttp\json_decode($new_contents, TRUE); + $this->assertEquals( + 'Test Node', + $json['@graph'][0]['http://purl.org/dc/terms/title'][0]['@value'], + 'Missing title value' + ); + $this->assertArrayNotHasKey('http://www.w3.org/2002/07/owl#sameAs', + $json['@graph'][0], 'Still has old predicate'); + $this->assertEquals( + "$url?_format=jsonld", + $json['@graph'][0]['http://example.org/first/second'][0]['@value'], + 'Missing alter added predicate.' + ); + } + +}