%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/ |
| Current File : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/CirrusSearch.php |
<?php
use CirrusSearch\Connection;
use CirrusSearch\ElasticsearchIntermediary;
use CirrusSearch\InterwikiSearcher;
use CirrusSearch\Search\FullTextResultsType;
use CirrusSearch\Searcher;
use CirrusSearch\CompletionSuggester;
use CirrusSearch\Search\ResultSet;
use CirrusSearch\SearchConfig;
use CirrusSearch\Search\FancyTitleResultsType;
use CirrusSearch\Search\TitleResultsType;
use CirrusSearch\Search\TextIndexField;
use CirrusSearch\UserTesting;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
/**
* SearchEngine implementation for CirrusSearch. Delegates to
* CirrusSearchSearcher for searches and CirrusSearchUpdater for updates. Note
* that lots of search behavior is hooked in CirrusSearchHooks rather than
* overridden here.
*
* 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
*/
class CirrusSearch extends SearchEngine {
const MORE_LIKE_THIS_PREFIX = 'morelike:';
const MORE_LIKE_THIS_JUST_WIKIBASE_PREFIX = 'morelikewithwikibase:';
const COMPLETION_SUGGESTER_FEATURE = 'completionSuggester';
/** @const string name of the prefixsearch fallback profile */
const COMPLETION_PREFIX_FALLBACK_PROFILE = 'classic';
/**
* @var string The last prefix substituted by replacePrefixes.
*/
private $lastNamespacePrefix;
/**
* @var array metrics about the last thing we searched sourced from the
* Searcher instance
*/
private $lastSearchMetrics = [];
/**
* @var array additional metrics about the search sourced within this class
*/
private $extraSearchMetrics = [];
/**
* @var string
*/
private $indexBaseName;
/**
* @var Connection
*/
private $connection;
/**
* Search configuration.
* @var SearchConfig
*/
private $config;
/**
* Current request.
* @var WebRequest
*/
private $request;
public function __construct( $baseName = null ) {
// Initialize UserTesting before we create a Connection
// This is useful to do tests accross multiple clusters
UserTesting::getInstance();
$this->config = MediaWikiServices::getInstance()
->getConfigFactory()
->makeConfig( 'CirrusSearch' );
$this->indexBaseName = $baseName === null
? $this->config->get( SearchConfig::INDEX_BASE_NAME )
: $baseName;
$this->connection = new Connection( $this->config );
$this->request = RequestContext::getMain()->getRequest();
}
public function setConnection( Connection $connection ) {
$this->connection = $connection;
}
/**
* @return Connection
*/
public function getConnection() {
return $this->connection;
}
/**
* Set search config
* @param SearchConfig $config
*/
public function setConfig( SearchConfig $config ) {
$this->config = $config;
}
/**
* Get search config
* @return SearchConfig
*/
public function getConfig() {
return $this->config;
}
/**
* Override supports to shut off updates to Cirrus via the SearchEngine infrastructure. Page
* updates and additions are chained on the end of the links update job. Deletes are noticed
* via the ArticleDeleteComplete hook.
* @param string $feature feature name
* @return bool is this feature supported?
*/
public function supports( $feature ) {
switch ( $feature ) {
case 'search-update':
case 'list-redirects':
return false;
default:
return parent::supports( $feature );
}
}
/**
* Overridden to delegate prefix searching to Searcher.
* @param string $term text to search
* @return ResultSet|null|Status results, no results, or error respectively
*/
public function searchText( $term ) {
$config = null;
if ( $this->request && $this->request->getVal( 'cirrusLang' ) ) {
$config = new SearchConfig( $this->request->getVal( 'cirrusLang' ) );
}
$matches = $this->searchTextReal( $term, $config );
if (!$matches instanceof ResultSet) {
return $matches;
}
if ( $this->isFeatureEnabled( 'rewrite' ) &&
$matches->isQueryRewriteAllowed( $GLOBALS['wgCirrusSearchInterwikiThreshold'] ) ) {
$matches = $this->searchTextSecondTry( $term, $matches );
}
ElasticsearchIntermediary::setResultPages( [ $matches ] );
return $matches;
}
/**
* Check whether we want to try another language.
* @param string $term Search term
* @return string[]|null Array of (interwiki, dbname) for another wiki to try, or null
*/
private function hasSecondaryLanguage( $term ) {
if ( empty( $GLOBALS['wgCirrusSearchLanguageToWikiMap'] ) ||
empty( $GLOBALS['wgCirrusSearchWikiToNameMap'] ) ) {
// map's empty - no need to bother with detection
return null;
}
$detected = null;
foreach ( $GLOBALS['wgCirrusSearchLanguageDetectors'] as $name => $klass ) {
if ( !class_exists( $klass ) ) {
LoggerFactory::getInstance( 'CirrusSearch' )->info(
"Unknown detector class for {name}: {class}",
[
"name" => $name,
"class" => $klass,
]
);
continue;
}
$detector = new $klass();
if( !( $detector instanceof \CirrusSearch\LanguageDetector\Detector ) ) {
LoggerFactory::getInstance( 'CirrusSearch' )->info(
"Bad detector class for {name}: {class}",
[
"name" => $name,
"class" => $klass,
]
);
continue;
}
$lang = $detector->detect( $this, $term );
$wiki = self::wikiForLanguage( $lang );
if ( $wiki !== null ) {
// it might be more accurate to attach these to the 'next'
// log context? It would be inconsistent with the
// langdetect => false condition which does not have a next
// request though.
Searcher::appendLastLogContext( [
'langdetect' => $name,
] );
$detected = $wiki;
break;
}
}
if ( $detected === null ) {
Searcher::appendLastLogContext( [
'langdetect' => 'failed',
] );
} else {
// Report language detection with search metrics
$this->extraSearchMetrics['wgCirrusSearchAltLanguage'] = $detected;
}
return $detected;
}
/**
* @param string $lang Language code to find wiki for
* @return string[]|null Array of (interwiki, dbname) for wiki related to specified language code
*/
private static function wikiForLanguage( $lang ) {
if ( empty( $GLOBALS['wgCirrusSearchLanguageToWikiMap'][$lang] ) ) {
return null;
}
$interwiki = $GLOBALS['wgCirrusSearchLanguageToWikiMap'][$lang];
if ( empty( $GLOBALS['wgCirrusSearchWikiToNameMap'][$interwiki] ) ) {
return null;
}
$interWikiId = $GLOBALS['wgCirrusSearchWikiToNameMap'][$interwiki];
if ( $interWikiId == wfWikiID() ) {
// we're back to the same wiki, no use to try again
return null;
}
return [ $interwiki, $interWikiId ];
}
/**
* @param string $feature
* @return bool
*/
private function isFeatureEnabled( $feature ) {
return isset( $this->features[$feature] ) && $this->features[$feature];
}
/**
* @param string $term
* @param ResultSet $oldResult
* @return ResultSet
*/
private function searchTextSecondTry( $term, ResultSet $oldResult ) {
// TODO: figure out who goes first - language or suggestion?
if ( $oldResult->numRows() == 0 && $oldResult->hasSuggestion() ) {
$rewritten = $oldResult->getSuggestionQuery();
$rewrittenSnippet = $oldResult->getSuggestionSnippet();
$this->showSuggestion = false;
$rewrittenResult = $this->searchTextReal( $rewritten );
if (
$rewrittenResult instanceof ResultSet
&& $rewrittenResult->numRows() > 0
) {
$rewrittenResult->setRewrittenQuery( $rewritten, $rewrittenSnippet );
if ( $rewrittenResult->numRows() < $GLOBALS['wgCirrusSearchInterwikiThreshold'] ) {
// replace the result but still try the alt language
$oldResult = $rewrittenResult;
} else {
return $rewrittenResult;
}
}
}
$altWiki = $this->hasSecondaryLanguage( $term );
if ( $altWiki ) {
try {
$config = new SearchConfig( $altWiki[0], $altWiki[1] );
} catch ( MWException $e ) {
LoggerFactory::getInstance( 'CirrusSearch' )->info(
"Failed to get config for {interwiki}:{dbwiki}",
[
"interwiki" => $altWiki[0],
"dbwiki" => $altWiki[1],
"exception" => $e
]
);
$config = null;
}
if ( $config ) {
$matches = $this->searchTextReal( $term, $config );
if ( $matches instanceof ResultSet ) {
$numRows = $matches->numRows();
$this->extraSearchMetrics['wgCirrusSearchAltLanguageNumResults'] = $numRows;
// check whether we have second language functionality enabled.
// This comes after the actual query is run so we can collect metrics about
// users in the control buckets, and provide them the same latency as users
// in the test bucket.
if ( $GLOBALS['wgCirrusSearchEnableAltLanguage'] && $numRows > 0) {
$oldResult->addInterwikiResults( $matches, SearchResultSet::INLINE_RESULTS, $altWiki[1] );
}
}
}
}
// Don't have any other options yet.
return $oldResult;
}
/**
* Do the hard part of the searching - actual Searcher invocation
* @param string $term
* @param SearchConfig $config
* @return null|Status|ResultSet
*/
private function searchTextReal( $term, SearchConfig $config = null ) {
global $wgCirrusSearchInterwikiSources;
// Convert the unicode character 'ideographic whitespace' into standard
// whitespace. Cirrussearch treats them both as normal whitespace, but
// the preceding isn't appropriately trimmed.
$term = trim( str_replace( "\xE3\x80\x80", " ", $term) );
// No searching for nothing! That takes forever!
if ( $term === '' ) {
return null;
}
if ( $config ) {
$this->indexBaseName = $config->get( SearchConfig::INDEX_BASE_NAME );
}
$searcher = new Searcher( $this->connection, $this->offset, $this->limit, $config, $this->namespaces, null, $this->indexBaseName );
// Ignore leading ~ because it is used to force displaying search results but not to effect them
if ( substr( $term, 0, 1 ) === '~' ) {
$term = substr( $term, 1 );
$searcher->addSuggestPrefix( '~' );
}
// TODO remove this when we no longer have to support core versions without
// Ie946150c6796139201221dfa6f7750c210e97166
if ( method_exists( $this, 'getSort' ) ) {
$searcher->setSort( $this->getSort() );
}
if ( isset( $this->features[SearchEngine::FT_QUERY_INDEP_PROFILE_TYPE] ) ) {
$profile = $this->features[SearchEngine::FT_QUERY_INDEP_PROFILE_TYPE];
if ( $this->config->getElement( 'CirrusSearchRescoreProfiles', $profile ) !== null ) {
$searcher->getSearchContext()->setRescoreProfile( $profile );
}
}
$dumpQuery = $this->request && $this->request->getVal( 'cirrusDumpQuery' ) !== null;
$searcher->setReturnQuery( $dumpQuery );
$dumpResult = $this->request && $this->request->getVal( 'cirrusDumpResult' ) !== null;
$searcher->setDumpResult( $dumpResult );
$returnExplain = $this->request && $this->request->getVal( 'cirrusExplain' ) !== null;
$searcher->setReturnExplain( $returnExplain );
// Delegate to either searchText or moreLikeThisArticle and dump the result into $status
if ( substr( $term, 0, strlen( self::MORE_LIKE_THIS_PREFIX ) ) === self::MORE_LIKE_THIS_PREFIX ) {
$term = substr( $term, strlen( self::MORE_LIKE_THIS_PREFIX ) );
$status = $this->moreLikeThis( $term, $searcher, Searcher::MORE_LIKE_THESE_NONE );
} else if ( substr( $term, 0, strlen( self::MORE_LIKE_THIS_JUST_WIKIBASE_PREFIX ) ) === self::MORE_LIKE_THIS_JUST_WIKIBASE_PREFIX ) {
$term = substr( $term, strlen( self::MORE_LIKE_THIS_JUST_WIKIBASE_PREFIX ) );
$status = $this->moreLikeThis( $term, $searcher, Searcher::MORE_LIKE_THESE_ONLY_WIKIBASE );
} else {
# Namespace lookup should not be done for morelike special syntax (T111244)
if ( $this->lastNamespacePrefix ) {
$searcher->addSuggestPrefix( $this->lastNamespacePrefix );
} else {
$searcher->updateNamespacesFromQuery( $term );
}
$highlightingConfig = FullTextResultsType::HIGHLIGHT_ALL;
if ( $this->request ) {
if ( $this->request->getVal( 'cirrusSuppressSuggest' ) !== null ) {
$this->showSuggestion = false;
}
if ( $this->request->getVal( 'cirrusSuppressTitleHighlight' ) !== null ) {
$highlightingConfig ^= FullTextResultsType::HIGHLIGHT_TITLE;
}
if ( $this->request->getVal( 'cirrusSuppressAltTitle' ) !== null ) {
$highlightingConfig ^= FullTextResultsType::HIGHLIGHT_ALT_TITLE;
}
if ( $this->request->getVal( 'cirrusSuppressSnippet' ) !== null ) {
$highlightingConfig ^= FullTextResultsType::HIGHLIGHT_SNIPPET;
}
if ( $this->request->getVal( 'cirrusHighlightDefaultSimilarity' ) === 'no' ) {
$highlightingConfig ^= FullTextResultsType::HIGHLIGHT_WITH_DEFAULT_SIMILARITY;
}
if ( $this->request->getVal( 'cirrusHighlightAltTitleWithPostings' ) === 'no' ) {
$highlightingConfig ^= FullTextResultsType::HIGHLIGHT_ALT_TITLES_WITH_POSTINGS;
}
}
if ( $this->namespaces && !in_array( NS_FILE, $this->namespaces ) ) {
$highlightingConfig ^= FullTextResultsType::HIGHLIGHT_FILE_TEXT;
}
$searcher->setResultsType( new FullTextResultsType( $highlightingConfig, $config ? $config->getWikiCode() : '') );
$status = $searcher->searchText( $term, $this->showSuggestion );
}
if ( $dumpQuery || $dumpResult ) {
// When dumping the query we skip _everything_ but echoing the query.
RequestContext::getMain()->getOutput()->disable();
if ( $this->request && $this->request->getVal( 'cirrusExplain' ) === 'pretty' ) {
$printer = new CirrusSearch\ExplainPrinter();
echo $printer->format( $status->getValue() );
} else {
$this->request->response()->header( 'Content-type: application/json; charset=UTF-8' );
if ( $status->getValue() === null ) {
echo '{}';
} else {
echo json_encode( $status->getValue() );
}
}
exit();
}
$this->lastSearchMetrics = $searcher->getSearchMetrics();
// Add interwiki results, if we have a sane result
// Note that we have no way of sending warning back to the user. In this case all warnings
// are logged when they are added to the status object so we just ignore them here....
if ( $status->isOK() && $wgCirrusSearchInterwikiSources && $status->getValue() &&
method_exists( $status->getValue(), 'addInterwikiResults' ) ) {
// @todo @fixme: This should absolutely be a multisearch. I knew this when I
// wrote the code but Searcher needs some refactoring first.
foreach ( $wgCirrusSearchInterwikiSources as $interwiki => $index ) {
$iwSearch = new InterwikiSearcher( $this->connection, $this->namespaces, null, $index, $interwiki );
$interwikiResult = $iwSearch->getInterwikiResults( $term );
if ( $interwikiResult ) {
$status->getValue()->addInterwikiResults( $interwikiResult, SearchResultSet::SECONDARY_RESULTS, $interwiki );
}
}
}
// For historical reasons all callers of searchText interpret any Status return as an error
// so we must unwrap all OK statuses. Note that $status can be "good" and still contain null
// since that is interpreted as no results.
return $status->isOK() ? $status->getValue() : $status;
}
/**
* Look for suggestions using ES completion suggester.
* @param string $search Search string
* @param string[]|null $variants Search term variants
* @param SearchConfig $config search configuration
* @return SearchSuggestionSet Set of suggested names
*/
protected function getSuggestions( $search, $variants, SearchConfig $config ) {
// Inspect features to check if the user selected a specific profile
$profile = null;
if ( isset( $this->features[SearchEngine::COMPLETION_PROFILE_TYPE] ) ) {
$profile = $this->features[SearchEngine::COMPLETION_PROFILE_TYPE];
}
// offset is omitted, searchSuggestion does not support
// scrolling results
$suggester = new CompletionSuggester( $this->connection, $this->limit,
$this->offset, $config, $this->namespaces, null,
$this->indexBaseName, $profile );
$response = $suggester->suggest( $search, $variants );
if ( $response->isOK() ) {
// Errors will be logged, let's try the exact db match
return $response->getValue();
} else {
return SearchSuggestionSet::emptySuggestionSet();
}
}
/**
* @param string $term
* @param Searcher $searcher
* @param int $options A bitset of Searcher::MORE_LIKE_THESE_*
* @return Status<SearchResultSet>
*/
private function moreLikeThis( $term, $searcher, $options ) {
// Expand titles chasing through redirects
$titles = [];
$found = [];
foreach ( explode( '|', $term ) as $title ) {
$title = Title::newFromText( trim( $title ) );
while ( true ) {
if ( !$title ) {
continue 2;
}
$titleText = $title->getFullText();
if ( in_array( $titleText, $found ) ) {
continue 2;
}
$found[] = $titleText;
if ( !$title->exists() ) {
continue 2;
}
if ( $title->isRedirect() ) {
$page = WikiPage::factory( $title );
if ( !$page->exists() ) {
continue 2;
}
$title = $page->getRedirectTarget();
} else {
break;
}
}
$titles[] = $title;
}
if ( count( $titles ) ) {
return $searcher->moreLikeTheseArticles( $titles, $options );
}
return Status::newGood( new SearchResultSet( true ) /* empty */ );
}
/**
* Merge the prefix into the query (if any).
* @param string $term search term
* @return string possibly with a prefix appended
*/
public function transformSearchTerm( $term ) {
if ( $this->prefix != '' ) {
// Slap the standard prefix notation onto the query
$term = $term . ' prefix:' . $this->prefix;
}
return $term;
}
/**
* @param string $query
* @return string
*/
public function replacePrefixes( $query ) {
$parsed = parent::replacePrefixes( $query );
if ( $parsed !== $query ) {
$this->lastNamespacePrefix = substr( $query, 0, strlen( $query ) - strlen( $parsed ) );
} else {
$this->lastNamespacePrefix = '';
}
return $parsed;
}
/**
* Get the sort of sorts we allow
* @return string[]
*/
public function getValidSorts() {
return [ 'relevance', 'title_asc', 'title_desc' ];
}
/**
* Get the metrics for the last search we performed. Null if we haven't done any.
* @return array
*/
public function getLastSearchMetrics() {
/** @suppress PhanTypeMismatchReturn Phan doesn't handle array addition correctly */
return $this->lastSearchMetrics + $this->extraSearchMetrics;
}
protected function completionSuggesterEnabled( SearchConfig $config ) {
$useCompletion = $config->getElement( 'CirrusSearchUseCompletionSuggester' );
if( $useCompletion !== 'yes' && $useCompletion !== 'beta' ) {
return false;
}
// This way API can force-enable completion suggester
if ( $this->isFeatureEnabled( self::COMPLETION_SUGGESTER_FEATURE ) ) {
return true;
}
// Allow falling back to prefix search with query param
if ( $this->request && $this->request->getVal( 'cirrusUseCompletionSuggester' ) === 'no' ) {
return false;
}
// Allow experimentation with query parameters
if ( $this->request && $this->request->getVal( 'cirrusUseCompletionSuggester' ) === 'yes' ) {
return true;
}
if ( $useCompletion === 'beta' ) {
return class_exists( '\BetaFeatures' ) &&
\BetaFeatures::isFeatureEnabled( $GLOBALS['wgUser'], 'cirrussearch-completionsuggester' );
}
return true;
}
/**
* Perform a completion search.
* Does not resolve namespaces and does not check variants.
* We use parent search for:
* - Special: namespace
* We use old prefix search for:
* - Suggester not enabled
* -
* @param string $search
* @return SearchSuggestionSet
*/
protected function completionSearchBackend( $search ) {
if ( in_array( NS_SPECIAL, $this->namespaces ) ) {
// delegate special search to parent
return parent::completionSearchBackend( $search );
}
if ( !$this->completionSuggesterEnabled( $this->config ) ) {
// Completion suggester is not enabled, fallback to
// default implementation
return $this->prefixSearch( $search );
}
if ( count( $this->namespaces ) != 1 ||
reset( $this->namespaces ) != NS_MAIN ) {
// for now, suggester only works for main namespace
return $this->prefixSearch( $search );
}
if ( isset( $this->features[SearchEngine::COMPLETION_PROFILE_TYPE] ) ) {
// Fallback to prefixsearch if the classic profile was selected.
if ( $this->features[SearchEngine::COMPLETION_PROFILE_TYPE] == self::COMPLETION_PREFIX_FALLBACK_PROFILE ) {
return $this->prefixSearch( $search );
}
}
// Not really useful, mostly for testing purpose
$variants = $this->request->getArray( 'cirrusCompletionSuggesterVariant' );
if ( empty( $variants ) ) {
global $wgContLang;
$variants = $wgContLang->autoConvertToAllVariants( $search );
} else if ( count( $variants ) > 3 ) {
// We should not allow too many variants
$variants = array_slice( $variants, 0, 3 );
}
return $this->getSuggestions( $search, $variants, $this->config );
}
/**
* Override variants function because we always do variants
* in the backend.
* @see SearchEngine::completionSearchWithVariants()
* @param string $search
* @return SearchSuggestionSet
*/
public function completionSearchWithVariants( $search ) {
return $this->completionSearch( $search );
}
/**
* Older prefix search.
* @param string $search search text
* @return SearchSuggestionSet
*/
protected function prefixSearch( $search ) {
$searcher = new Searcher( $this->connection, $this->offset, $this->limit, null, $this->namespaces );
if ( $search ) {
$searcher->setResultsType( new FancyTitleResultsType( 'prefix' ) );
} else {
// Empty searches always find the title.
$searcher->setResultsType( new TitleResultsType() );
}
try {
$status = $searcher->prefixSearch( $search );
} catch ( UsageException $e ) {
if ( defined( 'MW_API' ) ) {
throw $e;
}
return SearchSuggestionSet::emptySuggestionSet();
}
// There is no way to send errors or warnings back to the caller here so we have to make do with
// only sending results back if there are results and relying on the logging done at the status
// construction site to log errors.
if ( $status->isOK() ) {
if ( !$search ) {
// No need to unpack the simple title matches from non-fancy TitleResultsType
return SearchSuggestionSet::fromTitles( $status->getValue() );
}
$results = array_filter( array_map( function( $match ) {
if ( isset( $match[ 'titleMatch' ] ) ) {
return $match[ 'titleMatch' ];
} else {
if ( isset( $match[ 'redirectMatches' ][ 0 ] ) ) {
// TODO maybe dig around in the redirect matches and find the best one?
return $match[ 'redirectMatches' ][0];
}
}
return false;
}, $status->getValue() ) );
return SearchSuggestionSet::fromTitles( $results );
}
return SearchSuggestionSet::emptySuggestionSet();
}
/**
* {@inheritDoc}
*/
public function getProfiles( $profileType ) {
switch( $profileType ) {
case SearchEngine::COMPLETION_PROFILE_TYPE:
if ( $this->config->get( 'CirrusSearchUseCompletionSuggester' ) == 'no' ) {
// No profile selection if completion suggester is disabled.
return [];
}
$profiles = [];
foreach( array_keys( $this->config->get( 'CirrusSearchCompletionProfiles' ) ) as $name ) {
$profiles[] = [
'name' => $name,
'desc-message' => 'cirrussearch-completion-profile-' . $name,
'default' => $this->config->get( 'CirrusSearchCompletionSettings' ) == $name,
];
}
// Add fallback to prefixsearch
$profiles[] = [
'name' => self::COMPLETION_PREFIX_FALLBACK_PROFILE,
'desc-message' => 'cirrussearch-completion-profile-' . self::COMPLETION_PREFIX_FALLBACK_PROFILE,
'default' => false,
];
return $profiles;
case SearchEngine::FT_QUERY_INDEP_PROFILE_TYPE:
$profiles = [];
// The Hook should run profiles/RescoreProfiles.php before
// any consumer call to getProfiles, so that the default
// will be properly set when the curstom request param
// cirrusRescoreProfile=profile is set.
$cirrusDefault = $this->config->get( 'CirrusSearchRescoreProfile' );
$defaultFound = false;
foreach( $this->config->get( 'CirrusSearchRescoreProfiles' ) as $name => $profile ) {
$default = $cirrusDefault === $name;
$defaultFound |= $default;
$profiles[] = [
'name' => $name,
// @todo: decide what to with profiles we declare
// in wmf-config with no i18n messages.
// Do we want to expose them anyway, or simply
// hide them but still allow Api to pass them to us.
// It may require a change in core since ApiBase is
// strict and won't allow unknown values to be set
// here.
'desc-message' => isset ( $profile['i18n_msg'] ) ? $profile['i18n_msg'] : null,
'default' => $default,
];
}
return $profiles;
}
return null;
}
/**
* Create a search field definition
* @param string $name
* @param int $type
* @return SearchIndexField
*/
public function makeSearchFieldMapping( $name, $type ) {
$overrides = $this->config->get( 'CirrusSearchFieldTypeOverrides' );
$mappings = $this->config->get( 'CirrusSearchFieldTypes' );
if ( !isset( $mappings[$type] ) ) {
return new NullIndexField();
}
$klass = $mappings[$type];
// Check if a specific class is provided for this field
if ( isset( $overrides[$name] ) ) {
if ( $klass !== $overrides[$name] && !is_subclass_of( $overrides[$name], $klass ) ) {
throw new \Exception( "Specialized class " . $overrides[$name] .
" for field $name is not compatible with type class $klass" );
}
$klass = $overrides[$name];
}
return new $klass( $name, $type, $this->config );
}
}