%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/Search/ |
Current File : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/Search/SearchContext.php |
<?php namespace CirrusSearch\Search; use CirrusSearch\SearchConfig; use GeoData\Coord; use Elastica\Query\AbstractQuery; /** * The search context, maintains the state of the current search query. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html */ /** * The SearchContext stores the various states maintained * during the query building process. */ class SearchContext { /** * @var SearchConfig */ private $config; /** * @var int[]|null list of namespaces */ private $namespaces; /** * @var array|null list of boost templates extracted from the query string */ private $boostTemplatesFromQuery; /** * @deprecated use rescore profiles instead * @var bool do we need to boost links */ private $boostLinks = false; /** * @var float portion of article's score which decays with time. Defaults to 0 meaning don't decay the score * with time since the last update. */ private $preferRecentDecayPortion = 0; /** * @var float number of days it takes an the portion of an article score that will decay with time * since last update to decay half way. Defaults to 0 meaning don't decay the score with time. */ private $preferRecentHalfLife = 0; /** * @var string rescore profile to use */ private $rescoreProfile; /** * @var array[] nested array of arrays. Each child array contains three keys: * coord, radius and weight. Used for geographic radius boosting. */ private $geoBoosts = []; /** * @var bool Could this query possibly return results? */ private $resultsPossible = true; /** * @var string[] List of features in the user suplied query string. Features are * held in the array key, value is always true. */ private $syntaxUsed = []; /** * @var string The type of search being performed. ex: full_text, near_match, prefix, etc. */ private $searchType = 'unknown'; /** * @var AbstractQuery[] List of filters that query results must match */ private $filters = []; /** * @var AbstractQuery[] List of filters that query results must not match */ private $notFilters = []; /** * @var array[] $config List of configurations for highlighting the article * source. Passed to ResultType::getHighlightingConfiguration to generate * final highlighting configuration. Empty if source is ignored. */ private $highlightSource = []; /** * @var boolean is this a fuzzy query? */ private $fuzzyQuery = false; /** * @var AbstractQuery|null Query that should be used for highlighting if different * from the query used for selecting. */ private $highlightQuery; /** * @var AbstractQuery[] queries that don't use Elastic's "query string" query, * for more advanced highlighting (e.g. match_phrase_prefix for regular * quoted strings). */ private $nonTextHighlightQueries = []; /** * @var array Set of rescore configurations as used by elasticsearch. The query needs * to be an Elastica query. */ private $rescore = []; /** * @var string[] array of prefixes that should be prepended to suggestions. Can be added * to externally and is added to during search syntax parsing. */ private $suggestPrefixes = []; /** * @var string[] array of suffixes that should be prepended to suggestions. Can be added * to externally and is added to during search syntax parsing. */ private $suggestSuffixes = []; /** * @var AbstractQuery|null main query. null defaults to MatchAll */ private $mainQuery; /** * @var \Elastica\Query\Match[] Queries that don't use Elastic's "query string" query, for * more advanced searching (e.g. match_phrase_prefix for regular quoted strings). */ private $nonTextQueries = []; /** * @var array|null Configuration for suggest query */ private $suggest; /** * @var bool Should this search limit results to the local wiki? */ private $limitSearchToLocalWiki = false; /** * @param SearchConfig $config * @param int[]|null $namespaces */ public function __construct( SearchConfig $config, array $namespaces = null ) { $this->config = $config; $this->boostLinks = $this->config->get( 'CirrusSearchBoostLinks' ); $this->namespaces = $namespaces; $this->rescoreProfile = $this->config->get( 'CirrusSearchRescoreProfile' ); $decay = $this->config->get( 'CirrusSearchPreferRecentDefaultDecayPortion' ); if ( $decay > 0 ) { $this->preferRecentDecayPortion = $decay; $this->preferRecentHalfLife = $this->config->get( 'CirrusSearchPreferRecentDefaultHalfLife' ); } } /** * @return SearchConfig the Cirrus config object */ public function getConfig() { return $this->config; } /** * mediawiki namespace id's being requested. * NOTE: this value may change during the Searcher process. * * @return int[]|null */ public function getNamespaces() { return $this->namespaces; } /** * set the mediawiki namespace id's * * @param int[]|null $namespaces array of integer */ public function setNamespaces( $namespaces ) { $this->namespaces = $namespaces; } /** * Return the list of boosted templates specified in the user query (special syntax) * null if not used in the query or an empty array if there was a syntax error. * Initialized after special syntax extraction. * * @return array|null of boosted templates, key is the template value is the weight */ public function getBoostTemplatesFromQuery() { return $this->boostTemplatesFromQuery; } /** * @param array $boostTemplatesFromQuery boosted templates extracted from query */ public function setBoostTemplatesFromQuery( $boostTemplatesFromQuery ) { $this->boostTemplatesFromQuery = $boostTemplatesFromQuery; } /** * @deprecated use rescore profiles * @param bool $boostLinks Deactivate IncomingLinksFunctionScoreBuilder if present in the rescore profile */ public function setBoostLinks( $boostLinks ) { $this->boostLinks = $boostLinks; } /** * @deprecated use custom rescore profile * @return bool * @suppress PhanDeprecatedProperty */ public function isBoostLinks() { return $this->boostLinks; } /** * Set prefer recent options * * @param float $preferRecentDecayPortion * @param float $preferRecentHalfLife */ public function setPreferRecentOptions( $preferRecentDecayPortion, $preferRecentHalfLife ) { $this->preferRecentDecayPortion = $preferRecentDecayPortion; $this->preferRecentHalfLife = $preferRecentHalfLife; } /** * @return bool true if preferRecent options have been set. */ public function hasPreferRecentOptions() { return $this->preferRecentHalfLife > 0 && $this->preferRecentDecayPortion > 0; } /** * Parameter used by Search\PreferRecentFunctionScoreBuilder * * @return float the decay portion for prefer recent */ public function getPreferRecentDecayPortion() { return $this->preferRecentDecayPortion; } /** * Parameter used by Search\PreferRecentFunctionScoreBuilder * * @return float the half life for prefer recent */ public function getPreferRecentHalfLife() { return $this->preferRecentHalfLife; } /** * @return string the rescore profile to use */ public function getRescoreProfile() { return $this->rescoreProfile; } /** * @param string the rescore profile to use */ public function setRescoreProfile( $rescoreProfile ) { $this->rescoreProfile = $rescoreProfile; } /** * @return array[] nested array of arrays. Each child array contains three keys: * coord, radius and weight */ public function getGeoBoosts() { return $this->geoBoosts; } /** * @param Coord $coord Coordinates to boost near * @param int $radius radius to boost within, in meters * @param float $weight Number to multiply score by when within radius */ public function addGeoBoost( Coord $coord, $radius, $weight ) { $this->geoBoosts[] = [ 'coord' => $coord, 'radius' => $radius, 'weight' => $weight, ]; } /** * @return bool Could this query possibly return results? */ public function areResultsPossible() { return $this->resultsPossible; } /** * @param bool $possible Could this query possible return results? Defaults to true * if not called. */ public function setResultsPossible( $possible ) { $this->resultsPossible = $possible; } /** * @var string|null $type type of syntax to check, null for any type * @return bool True when the query uses $type kind of special syntax */ public function isSyntaxUsed( $type = null ) { if ( $type === null ) { return count( $this->syntaxUsed ) > 0; } return isset( $this->syntaxUsed[$type] ); } /** * @return string[] List of special syntax used in the query */ public function getSyntaxUsed() { return array_keys( $this->syntaxUsed ); } /** * @param string $feature Name of a syntax feature used in the query string */ public function addSyntaxUsed( $feature ) { $this->syntaxUsed[$feature] = true; } /** * @return string The type of search being performed, ex: full_text, near_match, prefix, etc. * @todo It might be possible to determine this based on the features used. */ public function getSearchType() { return $this->searchType; } /** * @param string $type The type of search being performed. ex: full_text, near_match, prefix, etc. */ public function setSearchType( $type ) { $this->searchType = $type; } /** * @param AbstractQuery $filter Query results must match this filter */ public function addFilter( AbstractQuery $filter ) { $this->filters[] = $filter; } /** * @param AbstractQuery $filter Query results must not match this filter */ public function addNotFilter( AbstractQuery $filter ) { $this->notFilters[] = $filter; } /** * @param bool $isFuzzy is this a fuzzy query? */ public function setFuzzyQuery( $isFuzzy ) { $this->fuzzyQuery = $isFuzzy; } /** * @return bool is this a fuzzy query? */ public function isFuzzyQuery() { return $this->fuzzyQuery; } /** * @param array $config Configuration for highlighting the article source. Passed * to ResultType::getHighlightingConfiguration to generate final highlighting * configuration. */ public function addHighlightSource( array $config ) { $this->highlightSource[] = $config; } /** * @param AbstractQuery Query that should be used for highlighting if different * from the query used for selecting. */ public function setHighlightQuery( AbstractQuery $query ) { $this->highlightQuery = $query; } /** * @param AbstractQuery $query queries that don't use Elastic's "query * string" query, for more advanced highlighting (e.g. match_phrase_prefix * for regular quoted strings). */ public function addNonTextHighlightQuery( AbstractQuery $query ) { $this->nonTextHighlightQueries[] = $query; } /** * @return array|null Highlight portion of query to be sent to elasticsearch */ public function getHighlight( ResultsType $resultsType ) { $highlight = $resultsType->getHighlightingConfiguration( $this->highlightSource ); if ( !$highlight ) { return null; } if ( $this->fuzzyQuery ) { $highlight['fields'] = array_filter( $highlight['fields'], function ( $field ) { return $field['type'] !== 'plain'; } ); } $query = $this->getHighlightQuery(); if ( $query ) { $highlight['highlight_query'] = $query->toArray(); } return $highlight; } /** * @return AbstractQuery|null Query that should be used for highlighting if different * from the query used for selecting. */ private function getHighlightQuery() { if ( empty( $this->nonTextHighlightQueries ) ) { return $this->highlightQuery; } $bool = new \Elastica\Query\BoolQuery(); if ( $this->highlightQuery) { $bool->addShould( $this->highlightQuery ); } foreach ( $this->nonTextHighlightQueries as $nonTextHighlightQuery ) { $bool->addShould( $nonTextHighlightQuery ); } return $bool; } /** * @return bool True if rescore queries are attached */ public function hasRescore() { return count( $this->rescore ) > 0; } /** * rescore_query has to be in array form before we send it to Elasticsearch but it is way * easier to work with if we leave it in query form until now * * @return array[] Rescore configurations as used by elasticsearch. */ public function getRescore() { $result = []; foreach ( $this->rescore as $rescore ) { $rescore['query']['rescore_query'] = $rescore['query']['rescore_query']->toArray(); $result[] = $rescore; } return $result; } /** * @param array[] $rescore Rescore configuration as used by elasticsearch. The query needs * to be an Elastica query. */ public function addRescore( array $rescore ) { $this->rescore[] = $rescore; } /** * Remove all rescores from the query. Used when it is known that extra work scoring * results will not be useful or necessary. Only effective if done *after* all rescores * have been added. */ public function clearRescore() { $this->rescore = []; } /** * @param array[] $rescores A set of rescore configurations as used by elasticsearch. The * query needs to be an Elastica query. */ public function mergeRescore( $rescores ) { $this->rescore = array_merge( $this->rescore, $rescores ); } /** * @return string[] List of prefixes to be prepended to suggestions */ public function getSuggestPrefixes() { return $this->suggestPrefixes; } /** * @param string $prefix Prefix to be prepended to suggestions */ public function addSuggestPrefix( $prefix ) { $this->suggestPrefixes[] = $prefix; } /** * @return string[] List of suffixes to be appended to suggestions */ public function getSuggestSuffixes() { return $this->suggestSuffixes; } /** * @param string $suffix Suffix to be appended to suggestions */ public function addSuggestSuffix( $suffix ) { $this->suggestSuffixes[] = $suffix; } /** * @return AbstractQuery The primary query to be sent to elasticsearch. Includes * the main query, non text queries, and any additional filters. */ public function getQuery() { if ( empty( $this->nonTextQueries ) ) { $mainQuery = $this->mainQuery ?: new \Elastica\Query\MatchAll(); } else { $mainQuery = new \Elastica\Query\BoolQuery(); if ( $this->mainQuery ) { $mainQuery ->addMust( $this->mainQuery ); } foreach ( $this->nonTextQueries as $nonTextQuery ) { $mainQuery->addMust( $nonTextQuery ); } } // Wrap $mainQuery in a filtered query if there are any filters $unifiedFilter = Filters::unify( $this->filters, $this->notFilters ); if ( $unifiedFilter !== null ) { if ( ! ( $mainQuery instanceof \Elastica\Query\BoolQuery ) ) { $bool = new \Elastica\Query\BoolQuery(); $bool->addMust( $mainQuery ); $mainQuery = $bool; } $mainQuery->addFilter( $unifiedFilter ); } return $mainQuery; } /** * @param AbstractQuery $query The primary query to be passed to * elasticsearch. */ public function setMainQuery( AbstractQuery $query ) { $this->mainQuery = $query; } /** * @param \Elastica\Query\Match $match Queries that don't use Elastic's * "query string" query, for more advanced searching (e.g. * match_phrase_prefix for regular quoted strings). */ public function addNonTextQuery( \Elastica\Query\Match $match ) { $this->nonTextQueries[] = $match; } /** * @return array|null Configuration for suggest query */ public function getSuggest() { return $this->suggest; } /** * @param array $suggest Configuration for suggest query */ public function setSuggest( array $suggest ) { $this->suggest = $suggest; } /** * @return boolean Should this search limit results to the local wiki? If * not called the default is false. */ public function getLimitSearchToLocalWiki() { return $this->limitSearchToLocalWiki; } /** * @param boolean $localWikiOnly Should this search limit results to the local wiki? If * not called the default is false. */ public function setLimitSearchToLocalWiki( $localWikiOnly ) { $this->limitSearchToLocalWiki = $localWikiOnly; } }