private function buildWarmer($search) { // This has a couple of compromises: $searcher = new Searcher($this->maint->getConnection(), 0, 50, null, array(), null); $searcher->setReturnQuery(true); $searcher->setResultsType(new FullTextResultsType(FullTextResultsType::HIGHLIGHT_ALL)); $searcher->limitSearchToLocalWiki(true); $query = $searcher->searchText($search, true)->getValue(); return $query['query']; }
public function execute() { $context = RequestContext::getMain(); $user = $context->getUser(); $conn = $this->getCirrusConnection(); $searcher = new Searcher($conn, 0, $this->getParameter('limit'), null, null, $user); $queryText = $this->getParameter('text'); if (!$queryText) { return; } $contextString = $this->getParameter('context'); if ($contextString) { $context = @json_decode($contextString, true); /* * Validate the context, must be in the form of: * { * name: { foo: bar, baz: qux } * name2: { foo: bar, baz: qux } * } * */ if (!is_array($context)) { $context = null; } else { foreach ($context as $name => $ctx) { if (!is_array($ctx)) { $this->dieUsage("Bad context element {$name}", 'cirrus-badcontext'); } } } } else { $context = null; } // TODO: add passing context here, // see https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html $result = $searcher->suggest($queryText, $context); if ($result->isOK()) { $this->getResult()->addValue(null, 'suggest', $result->getValue()); } else { $this->getResult()->addValue(null, "error", $result->getErrorsArray()); } }
/** * Constructor * @param int[] $namespaces Namespace numbers to search * @param User|null $user * @param string $index Base name for index to search from, defaults to wfWikiId() * @param string $interwiki Interwiki prefix we're searching */ public function __construct(Connection $connection, array $namespaces, User $user = null, $index, $interwiki) { parent::__construct($connection, 0, self::MAX_RESULTS, null, $namespaces, $user, $index); $this->interwiki = $interwiki; // Only allow core namespaces. We can't be sure any others exist if ($this->namespaces !== null) { $this->namespaces = array_filter($namespaces, function ($namespace) { return $namespace <= 15; }); } }
public function onView() { // Disable regular results $this->getOutput()->disable(); $response = $this->getRequest()->response(); $response->header('Content-type: application/json; charset=UTF-8'); $config = ConfigFactory::getDefaultInstance()->makeConfig('CirrusSearch'); $conn = new Connection($config); $searcher = new Searcher($conn, 0, 0, null, array(), $this->getUser()); $id = $this->getTitle()->getArticleID(); $esSources = $searcher->get(array($id), true); if (!$esSources->isOk()) { // Exception has been logged echo '{}'; return null; } $esSources = $esSources->getValue(); $result = array(); foreach ($esSources as $esSource) { $result[] = array('_index' => $esSource->getIndex(), '_type' => $esSource->getType(), '_id' => $esSource->getId(), '_version' => $esSource->getVersion(), '_source' => $esSource->getData()); } echo json_encode($result); return null; }
/** * Transform the search request into a Status object representing the * search result. Varies based on CLI input argument `type`. * * @param string $query * @return Status<ResultSet> */ protected function searchFor($query) { $searchType = $this->getOption('type', 'full_text'); switch ($searchType) { case 'full_text': // @todo pass through $this->getConnection() ? $engine = new CirrusSearch($this->indexBaseName); $engine->setConnection($this->getConnection()); $result = $engine->searchText($query); if ($result instanceof Status) { return $result; } else { return Status::newGood($result); } case 'prefix': $searcher = new Searcher($this->getConnection(), 0, 10, null, null, null, $this->indexBaseName); return $searcher->prefixSearch($query); case 'suggest': $searcher = new Searcher($this->getConnection(), 0, 10, null, null, null, $this->indexBaseName); $result = $searcher->suggest($query); if ($result instanceof Status) { return $result; } else { return Status::newGood($result); } default: $this->error("\nERROR: Unknown search type {$searchType}\n"); exit(1); } }
/** * Search articles with provided term. * @param $term string term to search * @param boolean $showSuggestion should this search suggest alternative searches that might be better? * @return Status(mixed) status containing results defined by resultsType on success */ public function searchText($term, $showSuggestion) { $checkLengthStatus = self::checkTextSearchRequestLength($term); if (!$checkLengthStatus->isOk()) { return $checkLengthStatus; } // Transform Mediawiki specific syntax to filters and extra (pre-escaped) query string $searcher = $this; $originalTerm = $term; $searchContainedSyntax = false; $this->term = $term; $this->boostLinks = $this->config->get('CirrusSearchBoostLinks'); $searchType = 'full_text'; // Handle title prefix notation $prefixPos = strpos($this->term, 'prefix:'); if ($prefixPos !== false) { $value = substr($this->term, 7 + $prefixPos); $value = trim($value, '"'); // Trim quotes in case the user wanted to quote the prefix if (strlen($value) > 0) { $searchContainedSyntax = true; $this->term = substr($this->term, 0, max(0, $prefixPos - 1)); $this->suggestSuffixes[] = ' prefix:' . $value; // Suck namespaces out of $value $cirrusSearchEngine = new CirrusSearch(); $cirrusSearchEngine->setConnection($this->connection); $value = trim($cirrusSearchEngine->replacePrefixes($value)); $this->namespaces = $cirrusSearchEngine->namespaces; // If the namespace prefix wasn't the entire prefix filter then add a filter for the title if (strpos($value, ':') !== strlen($value) - 1) { $value = str_replace('_', ' ', $value); $prefixQuery = new \Elastica\Query\Match(); $prefixQuery->setFieldQuery('title.prefix', $value); $this->filters[] = new \Elastica\Filter\Query($prefixQuery); } } } $preferRecentDecayPortion = $this->config->get('CirrusSearchPreferRecentDefaultDecayPortion'); $preferRecentHalfLife = $this->config->get('CirrusSearchPreferRecentDefaultHalfLife'); $unspecifiedDecayPortion = $this->config->get('CirrusSearchPreferRecentUnspecifiedDecayPortion'); // Matches "prefer-recent:" and then an optional floating point number <= 1 but >= 0 (decay // portion) and then an optional comma followed by another floating point number >= 0 (half life) $this->extractSpecialSyntaxFromTerm('/prefer-recent:(1|0?(?:\\.\\d+)?)?(?:,(\\d*\\.?\\d+))? ?/', function ($matches) use($unspecifiedDecayPortion, &$preferRecentDecayPortion, &$preferRecentHalfLife, &$searchContainedSyntax) { if (isset($matches[1]) && strlen($matches[1])) { $preferRecentDecayPortion = floatval($matches[1]); } else { $preferRecentDecayPortion = $unspecifiedDecayPortion; } if (isset($matches[2])) { $preferRecentHalfLife = floatval($matches[2]); } $searchContainedSyntax = true; return ''; }); $this->preferRecentDecayPortion = $preferRecentDecayPortion; $this->preferRecentHalfLife = $preferRecentHalfLife; $this->extractSpecialSyntaxFromTerm('/^\\s*local:/', function ($matches) use($searcher) { $searcher->limitSearchToLocalWiki(true); return ''; }); // Handle other filters $filters = $this->filters; $notFilters = $this->notFilters; $boostTemplates = self::getDefaultBoostTemplates(); $highlightSource = array(); $this->extractSpecialSyntaxFromTerm('/(?<not>-)?insource:\\/(?<pattern>(?:[^\\\\\\/]|\\\\.)+)\\/(?<insensitive>i)? ?/', function ($matches) use($searcher, &$filters, &$notFilters, &$searchContainedSyntax, &$searchType, &$highlightSource) { if (!$searcher->config->get('CirrusSearchEnableRegex')) { return; } $searchContainedSyntax = true; $searchType = 'regex'; $insensitive = !empty($matches['insensitive']); $filterDestination =& $filters; if (!empty($matches['not'])) { $filterDestination =& $notFilters; } else { $highlightSource[] = array('pattern' => $matches['pattern'], 'locale' => $searcher->config->get('LanguageCode'), 'insensitive' => $insensitive); } $regex = $searcher->config->getElement('CirrusSearchWikimediaExtraPlugin', 'regex'); if ($regex && in_array('use', $regex)) { $filter = new SourceRegex($matches['pattern'], 'source_text', 'source_text.trigram'); if (isset($regex['max_inspect'])) { $filter->setMaxInspect($regex['max_inspect']); } else { $filter->setMaxInspect(10000); } $filter->setMaxDeterminizedStates($searcher->config->get('CirrusSearchRegexMaxDeterminizedStates')); if (isset($regex['max_ngrams_extracted'])) { $filter->setMaxNgramExtracted($regex['max_ngrams_extracted']); } $filter->setCaseSensitive(!$insensitive); $filter->setLocale($this->config->get('LanguageCode')); $filterDestination[] = $filter; } else { // Without the extra plugin we need to use groovy to attempt the regex. // Its less good but its something. $script = <<<GROOVY import org.apache.lucene.util.automaton.*; sourceText = _source.get("source_text"); if (sourceText == null) { \tfalse; } else { \tif (automaton == null) { \t\tif (insensitive) { \t\t\tlocale = new Locale(language); \t\t\tpattern = pattern.toLowerCase(locale); \t\t} \t\tregexp = new RegExp(pattern, RegExp.ALL ^ RegExp.AUTOMATON); \t\tautomaton = new CharacterRunAutomaton(regexp.toAutomaton()); \t} \tif (insensitive) { \t\tsourceText = sourceText.toLowerCase(locale); \t} \tautomaton.run(sourceText); } GROOVY; $filterDestination[] = new \Elastica\Filter\Script(new \Elastica\Script($script, array('pattern' => '.*(' . $matches['pattern'] . ').*', 'insensitive' => $insensitive, 'language' => $searcher->config->get('LanguageCode'), 'automaton' => null, 'locale' => null), 'groovy')); } }); // Match filters that look like foobar:thing or foobar:"thing thing" // The {7,15} keeps this from having horrible performance on big strings $escaper = $this->escaper; $fuzzyQuery = $this->fuzzyQuery; $isEmptyQuery = false; $this->extractSpecialSyntaxFromTerm('/(?<key>[a-z\\-]{7,15}):\\s*(?<value>"(?<quoted>(?:[^"]|(?<=\\\\)")+)"|(?<unquoted>\\S+)) ?/', function ($matches) use($searcher, $escaper, &$filters, &$notFilters, &$boostTemplates, &$searchContainedSyntax, &$fuzzyQuery, &$highlightSource, &$isEmptyQuery) { $key = $matches['key']; $quotedValue = $matches['value']; $value = $matches['quoted'] !== '' ? str_replace('\\"', '"', $matches['quoted']) : $matches['unquoted']; $filterDestination =& $filters; $keepText = true; if ($key[0] === '-') { $key = substr($key, 1); $filterDestination =& $notFilters; $keepText = false; } switch ($key) { case 'boost-templates': $boostTemplates = Searcher::parseBoostTemplates($value); if ($boostTemplates === null) { $boostTemplates = Searcher::getDefaultBoostTemplates(); } $searchContainedSyntax = true; return ''; case 'hastemplate': // We emulate template syntax here as best as possible, // so things in NS_MAIN are prefixed with ":" and things // in NS_TEMPLATE don't have a prefix at all. Since we // don't actually index templates like that, munge the // query here if (strpos($value, ':') === 0) { $value = substr($value, 1); } else { $title = Title::newFromText($value); if ($title && $title->getNamespace() == NS_MAIN) { $value = Title::makeTitle(NS_TEMPLATE, $title->getDBkey())->getPrefixedText(); } } $filterDestination[] = $searcher->matchPage('template', $value); $searchContainedSyntax = true; return ''; case 'linksto': $filterDestination[] = $searcher->matchPage('outgoing_link', $value, true); $searchContainedSyntax = true; return ''; case 'incategory': $categories = array_slice(explode('|', $value), 0, $searcher->config->get('CirrusSearchMaxIncategoryOptions')); $categoryFilters = $searcher->matchPageCategories($categories); if ($categoryFilters === null) { $isEmptyQuery = true; } else { $filterDestination[] = $categoryFilters; } $searchContainedSyntax = true; return ''; case 'insource': $updateReferences = Filters::insource($escaper, $searcher->getSearchContext(), $quotedValue); $updateReferences($fuzzyQuery, $filterDestination, $highlightSource, $searchContainedSyntax); return ''; case 'intitle': $updateReferences = Filters::intitle($escaper, $searcher->getSearchContext(), $quotedValue); $updateReferences($fuzzyQuery, $filterDestination, $highlightSource, $searchContainedSyntax); return $keepText ? "{$quotedValue} " : ''; default: return $matches[0]; } }); if ($isEmptyQuery) { return Status::newGood(new SearchResultSet(true)); } $this->filters = $filters; $this->notFilters = $notFilters; $this->boostTemplates = $boostTemplates; $this->searchContext->setSearchContainedSyntax($searchContainedSyntax); $this->fuzzyQuery = $fuzzyQuery; $this->highlightSource = $highlightSource; $this->term = $this->escaper->escapeQuotes($this->term); $this->term = trim($this->term); // Match quoted phrases including those containing escaped quotes // Those phrases can optionally be followed by ~ then a number (this is the phrase slop) // That can optionally be followed by a ~ (this matches stemmed words in phrases) // The following all match: "a", "a boat", "a\"boat", "a boat"~, "a boat"~9, "a boat"~9~, -"a boat", -"a boat"~9~ $slop = $this->config->get('CirrusSearchPhraseSlop'); $query = self::replacePartsOfQuery($this->term, '/(?<![\\]])(?<negate>-|!)?(?<main>"((?:[^"]|(?<=\\\\)")+)"(?<slop>~\\d+)?)(?<fuzzy>~)?/', function ($matches) use($searcher, $escaper, $slop) { $negate = $matches['negate'][0] ? 'NOT ' : ''; $main = $escaper->fixupQueryStringPart($matches['main'][0]); if (!$negate && !isset($matches['fuzzy']) && !isset($matches['slop']) && preg_match('/^"([^"*]+)[*]"/', $main, $matches)) { $phraseMatch = new Elastica\Query\Match(); $phraseMatch->setFieldQuery("all.plain", $matches[1]); $phraseMatch->setFieldType("all.plain", "phrase_prefix"); $this->nonTextQueries[] = $phraseMatch; $phraseHighlightMatch = new Elastica\Query\QueryString(); $phraseHighlightMatch->setQuery($matches[1] . '*'); $phraseHighlightMatch->setFields(array('all.plain')); $this->nonTextHighlightQueries[] = $phraseHighlightMatch; return array(); } if (!isset($matches['fuzzy'])) { if (!isset($matches['slop'])) { $main = $main . '~' . $slop['precise']; } // Got to collect phrases that don't use the all field so we can highlight them. // The highlighter locks phrases to the fields that specify them. It doesn't do // that with terms. return array('escaped' => $negate . $searcher->switchSearchToExact($main, true), 'nonAll' => $negate . $searcher->switchSearchToExact($main, false)); } return array('escaped' => $negate . $main); }); // Find prefix matches and force them to only match against the plain analyzed fields. This // prevents prefix matches from getting confused by stemming. Users really don't expect stemming // in prefix queries. $query = self::replaceAllPartsOfQuery($query, '/\\w+\\*(?:\\w*\\*?)*/u', function ($matches) use($searcher, $escaper) { $term = $escaper->fixupQueryStringPart($matches[0][0]); return array('escaped' => $searcher->switchSearchToExactForWildcards($term), 'nonAll' => $searcher->switchSearchToExactForWildcards($term)); }); $escapedQuery = array(); $nonAllQuery = array(); $nearMatchQuery = array(); foreach ($query as $queryPart) { if (isset($queryPart['escaped'])) { $escapedQuery[] = $queryPart['escaped']; if (isset($queryPart['nonAll'])) { $nonAllQuery[] = $queryPart['nonAll']; } else { $nonAllQuery[] = $queryPart['escaped']; } continue; } if (isset($queryPart['raw'])) { $fixed = $this->escaper->fixupQueryStringPart($queryPart['raw']); $escapedQuery[] = $fixed; $nonAllQuery[] = $fixed; $nearMatchQuery[] = $queryPart['raw']; continue; } LoggerFactory::getInstance('CirrusSearch')->warning('Unknown query part: {queryPart}', array('queryPart' => serialize($queryPart))); } // Actual text query list($queryStringQueryString, $this->fuzzyQuery) = $escaper->fixupWholeQueryString(implode(' ', $escapedQuery)); // Note that no escaping is required for near_match's match query. $nearMatchQuery = implode(' ', $nearMatchQuery); if ($queryStringQueryString !== '') { if (preg_match('/(?<!\\\\)[?*+~"!|-]|AND|OR|NOT/', $queryStringQueryString)) { $this->searchContext->setSearchContainedSyntax(true); // We're unlikey to make good suggestions for query string with special syntax in them.... $showSuggestion = false; } $fields = array_merge($this->buildFullTextSearchFields(1, '.plain', true), $this->buildFullTextSearchFields($this->config->get('CirrusSearchStemmedWeight'), '', true)); $nearMatchFields = $this->buildFullTextSearchFields($this->config->get('CirrusSearchNearMatchWeight'), '.near_match', true); $this->query = $this->buildSearchTextQuery($fields, $nearMatchFields, $queryStringQueryString, $nearMatchQuery); // The highlighter doesn't know about the weightinging from the all fields so we have to send // it a query without the all fields. This swaps one in. if ($this->config->getElement('CirrusSearchAllFields', 'use')) { $nonAllFields = array_merge($this->buildFullTextSearchFields(1, '.plain', false), $this->buildFullTextSearchFields($this->config->get('CirrusSearchStemmedWeight'), '', false)); list($nonAllQueryString, ) = $escaper->fixupWholeQueryString(implode(' ', $nonAllQuery)); $this->highlightQuery = $this->buildSearchTextQueryForFields($nonAllFields, $nonAllQueryString, 1, false, true); } else { $nonAllFields = $fields; } // Only do a phrase match rescore if the query doesn't include any quotes and has a space. // Queries without spaces are either single term or have a phrase query generated. // Queries with the quote already contain a phrase query and we can't build phrase queries // out of phrase queries at this point. if ($this->config->get('CirrusSearchPhraseRescoreBoost') > 1.0 && $this->config->get('CirrusSearchPhraseRescoreWindowSize') && !$this->searchContext->isSearchContainedSyntax() && strpos($queryStringQueryString, '"') === false && strpos($queryStringQueryString, ' ') !== false) { $rescoreFields = $fields; if (!$this->config->get('CirrusSearchAllFieldsForRescore')) { $rescoreFields = $nonAllFields; } $this->rescore[] = array('window_size' => $this->config->get('CirrusSearchPhraseRescoreWindowSize'), 'query' => array('rescore_query' => $this->buildSearchTextQueryForFields($rescoreFields, '"' . $queryStringQueryString . '"', $this->config->getElement('CirrusSearchPhraseSlop', 'boost'), true), 'query_weight' => 1.0, 'rescore_query_weight' => $this->config->get('CirrusSearchPhraseRescoreBoost'))); } $showSuggestion = $showSuggestion && $this->offset == 0; if ($showSuggestion) { $this->suggest = array('text' => $this->term, 'suggest' => $this->buildSuggestConfig('suggest')); } $result = $this->search($searchType, $originalTerm); if (!$result->isOK() && $this->isParseError($result)) { // Elasticsearch has reported a parse error and we've already logged it when we built the status // so at this point all we can do is retry the query as a simple query string query. $this->query = new \Elastica\Query\Simple(array('simple_query_string' => array('fields' => $fields, 'query' => $queryStringQueryString, 'default_operator' => 'AND'))); $this->rescore = array(); // Not worth trying in this state. $result = $this->search('degraded_full_text', $originalTerm); // If that doesn't work we're out of luck but it should. There no guarantee it'll work properly // with the syntax we've built above but it'll do _something_ and we'll still work on fixing all // the parse errors that come in. } } else { $result = $this->search($searchType, $originalTerm); // No need to check for a parse error here because we don't actually create a query for // Elasticsearch to parse } return $result; }
/** * Let Elasticsearch take a crack at getting near matches once mediawiki has tried all kinds of variants. * @param string $term the original search term and all language variants * @param null|Title $titleResult resulting match. A Title if we found something, unchanged otherwise. * @return bool return false if we find something, true otherwise so mediawiki can try its default behavior */ public static function onSearchGetNearMatch($term, &$titleResult) { global $wgContLang; $title = Title::newFromText($term); if ($title === null) { return false; } $user = RequestContext::getMain()->getUser(); // Ask for the first 50 results we see. If there are more than that too bad. $searcher = new Searcher(self::getConnection(), 0, 50, null, array($title->getNamespace()), $user); if ($title->getNamespace() === NS_MAIN) { $searcher->updateNamespacesFromQuery($term); } else { $term = $title->getText(); } $searcher->setResultsType(new FancyTitleResultsType('near_match')); try { $status = $searcher->nearMatchTitleSearch($term); } catch (UsageException $e) { if (defined('MW_API')) { throw $e; } return true; } // 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 // constrution site to log errors. if (!$status->isOK()) { return true; } $picker = new NearMatchPicker($wgContLang, $term, $status->getValue()); $best = $picker->pickBest(); if ($best) { $titleResult = $best; return false; } // Didn't find a result so let Mediawiki have a crack at it. return true; }
/** * @param integer $maxDocs the number of docs in the index * @param array of key values, key is the template name, value the boost factor. * Defaults to Searcher::getDefaultBoostTemplates() */ public function __construct($maxDocs, $boostTemplates = null) { $this->maxDocs = $maxDocs; $this->boostTemplates = $boostTemplates ?: Searcher::getDefaultBoostTemplates(); // We normalize incoming links according to the size of the index $this->incomingLinksNorm = (int) ($maxDocs * self::INCOMING_LINKS_MAX_DOCS_FACTOR); if ($this->incomingLinksNorm < 1) { // it's a very small wiki let's force the norm to 1 $this->incomingLinksNorm = 1; } }
private function searchTextReal($term, SearchConfig $config = null) { global $wgCirrusSearchInterwikiSources; // Convert the unicode character 'idiographic whitespace' into standard // whitespace. Cirrussearch treats them both as normal whitespace, but // the preceding isn't appropriatly trimmed. $term = trim(str_replace(" ", " ", $term)); // No searching for nothing! That takes forever! if (!$term) { return null; } $context = RequestContext::getMain(); $request = $context->getRequest(); $user = $context->getUser(); if ($config) { $this->indexBaseName = $config->getWikiId(); } $searcher = new Searcher($this->connection, $this->offset, $this->limit, $config, $this->namespaces, $user, $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()); } $dumpQuery = $request && $request->getVal('cirrusDumpQuery') !== null; $searcher->setReturnQuery($dumpQuery); $dumpResult = $request && $request->getVal('cirrusDumpResult') !== null; $searcher->setDumpResult($dumpResult); $returnExplain = $request && $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 ($request) { if ($request->getVal('cirrusSuppressSuggest') !== null) { $this->showSuggestion = false; } if ($request->getVal('cirrusSuppressTitleHighlight') !== null) { $highlightingConfig ^= FullTextResultsType::HIGHLIGHT_TITLE; } if ($request->getVal('cirrusSuppressAltTitle') !== null) { $highlightingConfig ^= FullTextResultsType::HIGHLIGHT_ALT_TITLE; } if ($request->getVal('cirrusSuppressSnippet') !== null) { $highlightingConfig ^= FullTextResultsType::HIGHLIGHT_SNIPPET; } if ($request->getVal('cirrusHighlightDefaultSimilarity') === 'no') { $highlightingConfig ^= FullTextResultsType::HIGHLIGHT_WITH_DEFAULT_SIMILARITY; } if ($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. $context->getOutput()->disable(); $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, $user, $index, $interwiki); $interwikiResult = $iwSearch->getInterwikiResults($term); if ($interwikiResult) { $status->getValue()->addInterwikiResults($interwikiResult); } } } // 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; }