diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AbstractTermNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AbstractTermNode.php index 5a1892cce3..0722f31bd5 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AbstractTermNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AbstractTermNode.php @@ -25,6 +25,28 @@ abstract class AbstractTermNode extends Node implements TermInterface $this->concepts = $concepts; } + protected function buildConceptQueries(QueryContext $context) + { + $concept_query = $this->buildConceptQuery($context); + if ($concept_query === null) { + return []; + } + + // Extract all should clauses + if ( + isset($concept_query['bool']) && + isset($concept_query['bool']['should']) && + count($concept_query) === 1 /* no options or must(_not) clauses */ + ) { + return isset($concept_query['bool']['should'][0]) ? + $concept_query['bool']['should'] : + [$concept_query['bool']['should']]; + } + + // Fallback to returning full query + return [$concept_query]; + } + protected function buildConceptQuery(QueryContext $context) { $concepts = Concept::pruneNarrowConcepts($this->concepts); diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TermNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TermNode.php index 25378811c9..8e0d8be531 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TermNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TermNode.php @@ -9,18 +9,13 @@ class TermNode extends AbstractTermNode { public function buildQuery(QueryContext $context) { - $query = $this->buildConceptQuery($context); - // Should not match anything if no concept is defined - if ($query === null) { - $query = [ - 'bool' => [ - 'should' => [] - ] - ]; - } - - return $query; + // TODO Ensure no match when no concept queries are provided + return [ + 'bool' => [ + 'should' => $this->buildConceptQueries($context) + ] + ]; } public function __toString() diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php index 0811fb549e..042f313a18 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php @@ -68,8 +68,8 @@ class TextNode extends AbstractTermNode implements ContextAbleInterface $query = QueryHelper::applyBooleanClause($query, 'should', $private_field_query); } - $concept_query = $this->buildConceptQuery($context); - if ($concept_query !== null) { + $concept_queries = $this->buildConceptQueries($context); + foreach ($concept_queries as $concept_query) { $query = QueryHelper::applyBooleanClause($query, 'should', $concept_query); } diff --git a/tests/Alchemy/Tests/Phrasea/SearchEngine/AST/TextNodeTest.php b/tests/Alchemy/Tests/Phrasea/SearchEngine/AST/TextNodeTest.php index 396770ae1c..1514fa8637 100644 --- a/tests/Alchemy/Tests/Phrasea/SearchEngine/AST/TextNodeTest.php +++ b/tests/Alchemy/Tests/Phrasea/SearchEngine/AST/TextNodeTest.php @@ -154,4 +154,85 @@ class TextNodeTest extends \PHPUnit_Framework_TestCase $this->assertEquals(json_decode($expected, true), $query); } + + public function testQueryBuildWithPrivateFieldAndConcept() + { + $public_field = new Field('foo', Mapping::TYPE_STRING, ['private' => false]); + $private_field = new Field('bar', Mapping::TYPE_STRING, [ + 'private' => true, + 'used_by_collections' => [1, 2, 3] + ]); + + $query_context = $this->prophesize(QueryContext::class); + $query_context + ->getUnrestrictedFields() + ->willReturn([$public_field]); + $query_context + ->localizeField($public_field) + ->willReturn(['foo.fr', 'foo.en']); + $query_context + ->getPrivateFields() + ->willReturn([$private_field]); + $query_context + ->localizeField($private_field) + ->willReturn(['private_caption.bar.fr', 'private_caption.bar.en']); + + $node = new TextNode('baz'); + $node->setConcepts([ + new Concept('/qux'), + ]); + $query = $node->buildQuery($query_context->reveal()); + + $expected = '{ + "bool": { + "should": [{ + "multi_match": { + "fields": ["foo.fr", "foo.en"], + "query": "baz", + "operator": "and", + "lenient": true + } + }, { + "filtered": { + "filter": { + "terms": { + "base_id": [1, 2, 3] + } + }, + "query": { + "multi_match": { + "fields": ["private_caption.bar.fr", "private_caption.bar.en"], + "query": "baz", + "operator": "and", + "lenient": true + } + } + } + }, { + "multi_match": { + "fields": [ + "concept_path.foo" + ], + "query": "\/qux" + } + }, { + "filtered": { + "filter": { + "terms": { + "base_id": [1, 2, 3] + } + }, + "query": { + "multi_match": { + "fields": ["concept_path.bar"], + "query": "/qux" + } + } + } + }] + } + }'; + + $this->assertEquals(json_decode($expected, true), $query); + } }