diff --git a/config/schema/islandora.schema.yml b/config/schema/islandora.schema.yml index 8fd719ce..f63e4341 100644 --- a/config/schema/islandora.schema.yml +++ b/config/schema/islandora.schema.yml @@ -78,6 +78,9 @@ condition.plugin.node_has_term: uri: type: text label: 'Taxonomy Term URI' + logic: + type: string + label: 'Logic (AND or OR)' condition.plugin.node_has_parent: type: condition.plugin @@ -95,6 +98,9 @@ condition.plugin.media_has_term: uri: type: text label: 'Taxonomy Term URI' + logic: + type: string + label: 'Logic (AND or OR)' condition.plugin.parent_node_has_term: type: condition.plugin @@ -102,6 +108,9 @@ condition.plugin.parent_node_has_term: uri: type: text label: 'Taxonomy Term URI' + logic: + type: string + label: 'Logic (AND or OR)' condition.plugin.file_uses_filesystem: type: condition.plugin diff --git a/src/IslandoraUtils.php b/src/IslandoraUtils.php index e98766c7..931e20d3 100644 --- a/src/IslandoraUtils.php +++ b/src/IslandoraUtils.php @@ -235,12 +235,32 @@ class IslandoraUtils { * Calling getStorage() throws if the storage handler couldn't be loaded. */ public function getTermForUri($uri) { - $results = $this->entityQuery->get('taxonomy_term') - ->condition(self::EXTERNAL_URI_FIELD . '.uri', $uri) + // Get authority link fields to search. + $field_map = $this->entityFieldManager->getFieldMap(); + $fields = []; + foreach ($field_map['taxonomy_term'] as $field_name => $field_data) { + if ($field_data['type'] == 'authority_link') { + $fields[] = $field_name; + } + } + // Add field_external_uri. + $fields[] = self::EXTERNAL_URI_FIELD; + + $query = $this->entityQuery->get('taxonomy_term'); + + $orGroup = $query->orConditionGroup(); + foreach ($fields as $field) { + $orGroup->condition("$field.uri", $uri); + } + + $results = $query + ->condition($orGroup) ->execute(); + if (empty($results)) { return NULL; } + return $this->entityTypeManager->getStorage('taxonomy_term')->load(reset($results)); } @@ -258,16 +278,39 @@ class IslandoraUtils { * be created. */ public function getUriForTerm(TermInterface $term) { - if ($term && $term->hasField(self::EXTERNAL_URI_FIELD)) { - $field = $term->get(self::EXTERNAL_URI_FIELD); - if (!$field->isEmpty()) { - $link = $field->first()->getValue(); - return $link['uri']; + $fields = $this->getUriFieldNamesForTerms(); + foreach ($fields as $field_name) { + if ($term && $term->hasField($field_name)) { + $field = $term->get($field_name); + if (!$field->isEmpty()) { + $link = $field->first()->getValue(); + return $link['uri']; + } } } return NULL; } + /** + * Gets every field name that might contain an external uri for a term. + * + * @return string[] + * Field names for fields that a term may have as an external uri. + */ + public function getUriFieldNamesForTerms() { + // Get authority link fields to search. + $field_map = $this->entityFieldManager->getFieldMap(); + $fields = []; + foreach ($field_map['taxonomy_term'] as $field_name => $field_data) { + if ($field_data['type'] == 'authority_link') { + $fields[] = $field_name; + } + } + // Add field_external_uri. + $fields[] = self::EXTERNAL_URI_FIELD; + return $fields; + } + /** * Executes context reactions for a Node. * diff --git a/src/Plugin/Condition/MediaHasTerm.php b/src/Plugin/Condition/MediaHasTerm.php index 67d4db3c..b170c7a6 100644 --- a/src/Plugin/Condition/MediaHasTerm.php +++ b/src/Plugin/Condition/MediaHasTerm.php @@ -7,7 +7,7 @@ namespace Drupal\islandora\Plugin\Condition; * * @Condition( * id = "media_has_term", - * label = @Translation("Media has term"), + * label = @Translation("Media has term with URI"), * context = { * "media" = @ContextDefinition("entity:media", required = TRUE , label = @Translation("media")) * } diff --git a/src/Plugin/Condition/NodeHasTerm.php b/src/Plugin/Condition/NodeHasTerm.php index dae52ad0..bfa8e9e8 100644 --- a/src/Plugin/Condition/NodeHasTerm.php +++ b/src/Plugin/Condition/NodeHasTerm.php @@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * * @Condition( * id = "node_has_term", - * label = @Translation("Node has term"), + * label = @Translation("Node has term with URI"), * context = { * "node" = @ContextDefinition("entity:node", required = TRUE , label = @Translation("node")) * } @@ -79,6 +79,16 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI ); } + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array_merge( + ['logic' => 'and'], + parent::defaultConfiguration() + ); + } + /** * {@inheritdoc} */ @@ -94,12 +104,24 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI $form['term'] = [ '#type' => 'entity_autocomplete', '#title' => $this->t('Term'), + '#description' => $this->t('Only terms that have external URIs/URLs will appear here.'), '#tags' => TRUE, '#default_value' => $default, '#target_type' => 'taxonomy_term', '#required' => TRUE, + '#selection_handler' => 'islandora:external_uri', ]; + $form['logic'] = [ + '#type' => 'radios', + '#title' => $this->t('Logic'), + '#description' => $this->t('Whether to use AND or OR logic to evaluate multiple terms'), + '#options' => [ + 'and' => 'And', + 'or' => 'Or', + ], + '#default_value' => $this->configuration['logic'], + ]; return parent::buildConfigurationForm($form, $form_state); } @@ -124,6 +146,9 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI $this->configuration['uri'] = implode(',', $uris); } } + + $this->configuration['logic'] = $form_state->getValue('logic'); + parent::submitConfigurationForm($form, $form_state); } @@ -153,22 +178,30 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI */ protected function evaluateEntity(EntityInterface $entity) { // Find the terms on the node. - $terms = array_filter($entity->referencedEntities(), function ($entity) { - return $entity->getEntityTypeId() == 'taxonomy_term' && - $entity->hasField(IslandoraUtils::EXTERNAL_URI_FIELD) && - !$entity->get(IslandoraUtils::EXTERNAL_URI_FIELD)->isEmpty(); + $field_names = $this->utils->getUriFieldNamesForTerms(); + $terms = array_filter($entity->referencedEntities(), function ($entity) use ($field_names) { + if ($entity->getEntityTypeId() != 'taxonomy_term') { + return FALSE; + } + + foreach ($field_names as $field_name) { + if ($entity->hasField($field_name) && !$entity->get($field_name)->isEmpty()) { + return TRUE; + } + } + return FALSE; }); // Get their URIs. $haystack = array_map(function ($term) { - return $term->get(IslandoraUtils::EXTERNAL_URI_FIELD)->first()->getValue()['uri']; + return $this->utils->getUriForTerm($term); }, $terms ); // FALSE if there's no URIs on the node. if (empty($haystack)) { - return $this->isNegated() ? TRUE : FALSE; + return FALSE; } // Get the URIs to look for. It's a required field, so there @@ -176,12 +209,19 @@ class NodeHasTerm extends ConditionPluginBase implements ContainerFactoryPluginI $needles = explode(',', $this->configuration['uri']); // TRUE if every needle is in the haystack. - if (count(array_intersect($needles, $haystack)) == count($needles)) { - return $this->isNegated() ? FALSE : TRUE; + if ($this->configuration['logic'] == 'and') { + if (count(array_intersect($needles, $haystack)) == count($needles)) { + return TRUE; + } + return FALSE; + } + // TRUE if any needle is in the haystack. + else { + if (count(array_intersect($needles, $haystack)) > 0) { + return TRUE; + } + return FALSE; } - - // Otherwise, FALSE. - return $this->isNegated() ? TRUE : FALSE; } /** diff --git a/src/Plugin/Condition/ParentNodeHasTerm.php b/src/Plugin/Condition/ParentNodeHasTerm.php index 098a80c9..3bc08641 100644 --- a/src/Plugin/Condition/ParentNodeHasTerm.php +++ b/src/Plugin/Condition/ParentNodeHasTerm.php @@ -7,7 +7,7 @@ namespace Drupal\islandora\Plugin\Condition; * * @Condition( * id = "parent_node_has_term", - * label = @Translation("Parent node for media has term"), + * label = @Translation("Parent node for media has term with URI"), * context = { * "media" = @ContextDefinition("entity:media", required = TRUE , label = @Translation("media")) * } diff --git a/src/Plugin/EntityReferenceSelection/ExternalUriSelection.php b/src/Plugin/EntityReferenceSelection/ExternalUriSelection.php new file mode 100644 index 00000000..6715fb23 --- /dev/null +++ b/src/Plugin/EntityReferenceSelection/ExternalUriSelection.php @@ -0,0 +1,125 @@ +utils = $utils; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager'), + $container->get('module_handler'), + $container->get('current_user'), + $container->get('entity_field.manager'), + $container->get('entity_type.bundle.info'), + $container->get('entity.repository'), + $container->get('islandora.utils') + ); + } + + /** + * {@inheritdoc} + */ + public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { + $options = parent::getReferenceableEntities($match, $match_operator, $limit); + + foreach (array_keys($options) as $vid) { + foreach (array_keys($options[$vid]) as $tid) { + $term = $this->entityTypeManager->getStorage('taxonomy_term')->load($tid); + $uri = $this->utils->getUriForTerm($term); + if (empty($uri)) { + unset($options[$vid][$tid]); + } + } + if (empty($options[$vid])) { + unset($options[$vid]); + } + } + + return $options; + } + +} diff --git a/tests/src/Functional/NodeHasTermTest.php b/tests/src/Functional/NodeHasTermTest.php index a27cf474..7d336de0 100644 --- a/tests/src/Functional/NodeHasTermTest.php +++ b/tests/src/Functional/NodeHasTermTest.php @@ -77,14 +77,36 @@ class NodeHasTermTest extends IslandoraFunctionalTestBase { $condition->setContextValue('node', $node); $this->assertFalse($condition->execute(), "Condition should fail if node does not have both terms"); - // Create a node with both tags. + // Check for two tags this time. + // Node still only has one. + $condition = $condition_manager->createInstance( + 'node_has_term', + [ + 'uri' => 'http://purl.org/coar/resource_type/c_c513,http://pcdm.org/use#PreservationMasterFile', + 'logic' => 'or', + ] + ); + $condition->setContextValue('node', $node); + $this->assertTrue($condition->execute(), "Condition should pass if has one of two terms using OR logic."); + + // Create a node with both tags and try it with OR. $node = $this->container->get('entity_type.manager')->getStorage('node')->create([ 'type' => 'test_type', 'title' => 'Test Node', 'field_tags' => [$this->imageTerm->id(), $this->preservationMasterTerm->id()], ]); $condition->setContextValue('node', $node); - $this->assertTrue($condition->execute(), "Condition should pass if node has both terms"); + $this->assertTrue($condition->execute(), "Condition should pass if node has both terms using OR logic"); + + // Try it with AND. + $condition = $condition_manager->createInstance( + 'node_has_term', + [ + 'uri' => 'http://purl.org/coar/resource_type/c_c513,http://pcdm.org/use#PreservationMasterFile', + ] + ); + $condition->setContextValue('node', $node); + $this->assertTrue($condition->execute(), "Condition should pass if node has both terms using AND logic"); } }