diff --git a/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php index b049e4a2e0..e1e9993fcc 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php @@ -24,6 +24,7 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\TermIndexer; use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper; use Alchemy\Phrasea\SearchEngine\Elastic\Search\Escaper; use Alchemy\Phrasea\SearchEngine\Elastic\Search\FacetsResponse; +use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContextFactory; use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryCompiler; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus; @@ -62,7 +63,7 @@ class SearchEngineServiceProvider implements ServiceProviderInterface $app['search_engine.structure'], $app['elasticsearch.client'], $options->getIndexName(), - $app['locales.available'], + $app['query_context.factory'], $app['elasticsearch.facets_response.factory'], $options ); @@ -181,6 +182,14 @@ class SearchEngineServiceProvider implements ServiceProviderInterface ); }); + $app['query_context.factory'] = $app->share(function ($app) { + return new QueryContextFactory( + $app['search_engine.structure'], + array_keys($app['locales.available']), + $app['locale'] + ); + }); + $app['query_parser.grammar_path'] = function ($app) { $configPath = ['registry', 'searchengine', 'query-grammar-path']; $grammarPath = $app['conf']->get($configPath, 'grammar/query.pp'); @@ -197,6 +206,7 @@ class SearchEngineServiceProvider implements ServiceProviderInterface $app['query_compiler'] = $app->share(function ($app) { return new QueryCompiler( $app['query_parser'], + $app['search_engine.structure'], $app['thesaurus'] ); }); diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/FieldKey.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/FieldKey.php index 89b6376e31..f4f8553738 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/FieldKey.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/FieldKey.php @@ -20,6 +20,11 @@ class FieldKey implements Key, QueryPostProcessor $this->name = $name; } + public function getName() + { + return $this->name; + } + public function getIndexField(QueryContext $context, $raw = false) { return $this->getField($context)->getIndexField($raw); diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php index 928c27eeb3..3d08ea528a 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php @@ -19,8 +19,8 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Search\AggregationHelper; use Alchemy\Phrasea\SearchEngine\Elastic\Search\FacetsResponse; use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryCompiler; use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext; +use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContextFactory; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Flag; -use Alchemy\Phrasea\SearchEngine\Elastic\Structure\LimitedStructure; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; use Alchemy\Phrasea\SearchEngine\SearchEngineInterface; use Alchemy\Phrasea\SearchEngine\SearchEngineOptions; @@ -48,12 +48,12 @@ class ElasticSearchEngine implements SearchEngineInterface /** @var ElasticsearchOptions */ private $options; - public function __construct(Application $app, Structure $structure, Client $client, $indexName, array $locales, Closure $facetsResponseFactory, ElasticsearchOptions $options) + public function __construct(Application $app, Structure $structure, Client $client, $indexName, QueryContextFactory $context_factory, Closure $facetsResponseFactory, ElasticsearchOptions $options) { $this->app = $app; $this->structure = $structure; $this->client = $client; - $this->locales = array_keys($locales); + $this->context_factory = $context_factory; $this->facetsResponseFactory = $facetsResponseFactory; $this->options = $options; @@ -62,6 +62,7 @@ class ElasticSearchEngine implements SearchEngineInterface } $this->indexName = $indexName; + } public function getIndexName() @@ -261,12 +262,7 @@ class ElasticSearchEngine implements SearchEngineInterface public function query($string, $offset, $perPage, SearchEngineOptions $options = null) { $options = $options ?: new SearchEngineOptions(); - - $narrowToFields = array(); - foreach($options->getFields() as $field) { - $narrowToFields[] = $field->get_name(); - } - $context = $this->createQueryContext($options)->narrowToFields($narrowToFields); + $context = $this->context_factory->createContext($options); /** @var QueryCompiler $query_compiler */ $query_compiler = $this->app['query_compiler']; @@ -352,23 +348,6 @@ class ElasticSearchEngine implements SearchEngineInterface ]; } - /** - * @todo Move in search engine service provider - */ - private function createQueryContext(SearchEngineOptions $options) - { - return new QueryContext( - $this->getLimitedStructure($options), - $this->locales, - $this->app['locale'] - ); - } - - private function getLimitedStructure(SearchEngineOptions $options) - { - return new LimitedStructure($this->structure, $options); - } - /** * {@inheritdoc} */ @@ -455,7 +434,7 @@ class ElasticSearchEngine implements SearchEngineInterface $base_facet_agg['terms']['field'] = 'type'; $aggs['Type'] = $base_facet_agg; - $structure = $this->getLimitedStructure($options); + $structure = $this->context_factory->getLimitedStructure($options); foreach ($structure->getFacetFields() as $name => $field) { // 2015-05-26 (mdarse) Removed databox filtering. // It was already done by the ACL filter in the query scope, so no diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryCompiler.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryCompiler.php index 7fa7fd60d2..29b4c6b64e 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryCompiler.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryCompiler.php @@ -4,6 +4,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Search; use Alchemy\Phrasea\SearchEngine\Elastic\AST; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\QueryException; +use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus; use Hoa\Compiler\Exception\Exception as CompilerException; use Hoa\Compiler\Llk\Parser; @@ -14,6 +15,7 @@ use Hoa\Visitor\Visit; class QueryCompiler { private $parser; + private $structure; private $thesaurus; private static $leftAssociativeOperators = array( @@ -22,9 +24,10 @@ class QueryCompiler NodeTypes::EXCEPT_EXPR ); - public function __construct(Parser $parser, Thesaurus $thesaurus) + public function __construct(Parser $parser, Structure $structure, Thesaurus $thesaurus) { $this->parser = $parser; + $this->structure = $structure; $this->thesaurus = $thesaurus; } @@ -54,7 +57,12 @@ class QueryCompiler */ public function parse($string, $postprocessing = true) { - return $this->visitString($string, new QueryVisitor(), $postprocessing); + return $this->visitString($string, $this->createQueryVisitor(), $postprocessing); + } + + private function createQueryVisitor() + { + return new QueryVisitor($this->structure); } public function dump($string, $postprocessing = true) diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContextFactory.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContextFactory.php new file mode 100644 index 0000000000..82759132c7 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContextFactory.php @@ -0,0 +1,49 @@ +structure = $structure; + $this->locales = $locales; + $this->current_locale = $current_locale; + } + + public function createContext(SearchEngineOptions $options = null) + { + $structure = $options + ? $this->getLimitedStructure($options) + : $this->structure; + + $context = new QueryContext($structure, $this->locales, $this->current_locale); + + if ($options) { + $fields = $this->getSearchedFields($options); + $context = $context->narrowToFields($fields); + } + + return $context; + } + + private function getSearchedFields(SearchEngineOptions $options) + { + $fields = []; + foreach ($options->getFields() as $field) { + $fields[] = $field->get_name(); + } + return $fields; + } + + public function getLimitedStructure(SearchEngineOptions $options) + { + return new LimitedStructure($this->structure, $options); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php index 0c0edaec9b..7ee5d7b793 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php @@ -5,6 +5,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Search; use Alchemy\Phrasea\SearchEngine\Elastic\AST; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\Exception; use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryHelper; +use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; use Hoa\Compiler\Llk\TreeNode; use Hoa\Visitor\Element; use Hoa\Visitor\Visit; @@ -12,6 +13,13 @@ use InvalidArgumentException; class QueryVisitor implements Visit { + private $structure; + + public function __construct(Structure $structure) + { + $this->structure = $structure; + } + public function visit(Element $element, &$handle = null, $eldnah = null) { if (null !== $value = $element->getValue()) { @@ -197,7 +205,12 @@ class QueryVisitor implements Visit private function isDateKey(AST\KeyValue\Key $key) { - return $key instanceof AST\KeyValue\TimestampKey; + if ($key instanceof AST\KeyValue\TimestampKey) { + return true; + } elseif ($key instanceof AST\KeyValue\FieldKey) { + return $this->structure->get($key->getName()) !== null; + } + return false; } private function visitTerm(Element $element)