%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/ |
Current File : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/Hooks.php |
<?php namespace CirrusSearch; use ApiBase; use ApiMain; use ApiOpenSearch; use CirrusSearch; use CirrusSearch\Search\FancyTitleResultsType; use DeferredUpdates; use JobQueueGroup; use LinksUpdate; use OutputPage; use MediaWiki\MediaWikiServices; use SearchResultSet; use SpecialSearch; use Title; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RequestContext; use UsageException; use User; use WebRequest; use WikiPage; use Xml; use Html; /** * All CirrusSearch's external hooks. * * 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 Hooks { /** * @var string[] Destination of titles being moved (the ->getPrefixedDBkey() form). */ private static $movingTitles = []; /** * Hooked to call initialize after the user is set up. * * @param Title $title * @param \Article $unused * @param OutputPage $outputPage * @param User $user * @param \WebRequest $request * @param \MediaWiki $mediaWiki * @return bool */ public static function onBeforeInitialize( $title, $unused, $outputPage, $user, $request, $mediaWiki ) { self::initializeForRequest( $request ); return true; } /** * Hooked to call initialize after the user is set up. * @param ApiMain $apiMain The ApiMain instance being used * @return bool */ public static function onApiBeforeMain( $apiMain ) { self::initializeForRequest( $apiMain->getRequest() ); return true; } /** * Initializes the portions of Cirrus that require the $request to be fully initialized * * @param WebRequest $request */ private static function initializeForRequest( WebRequest $request ) { global $wgSearchType, $wgHooks, $wgCirrusSearchUseExperimentalHighlighter, $wgCirrusSearchPhraseRescoreWindowSize, $wgCirrusSearchFunctionRescoreWindowSize, $wgCirrusSearchFragmentSize, $wgCirrusSearchBoostLinks, $wgCirrusSearchAllFields, $wgCirrusSearchAllFieldsForRescore, $wgCirrusSearchPhraseRescoreBoost, $wgCirrusSearchPhraseSlop, $wgCirrusSearchLogElasticRequests, $wgCirrusSearchLogElasticRequestsSecret, $wgCirrusSearchEnableAltLanguage; // Install our prefix search hook only if we're enabled. if ( $wgSearchType === 'CirrusSearch' ) { $wgHooks[ 'PrefixSearchExtractNamespace' ][] = 'CirrusSearch\Hooks::prefixSearchExtractNamespace'; $wgHooks[ 'SearchGetNearMatch' ][] = 'CirrusSearch\Hooks::onSearchGetNearMatch'; } self::overrideMoreLikeThisOptionsFromMessage(); PhraseSuggesterProfiles::overrideOptionsFromMessage(); if ( $request ) { // Engage the experimental highlighter if a url parameter requests it if ( !$wgCirrusSearchUseExperimentalHighlighter && $request->getVal( 'cirrusHighlighter' ) === 'experimental' ) { $wgCirrusSearchUseExperimentalHighlighter = true; } self::overrideNumeric( $wgCirrusSearchPhraseRescoreWindowSize, $request, 'cirrusPhraseWindow', 10000 ); self::overrideNumeric( $wgCirrusSearchPhraseRescoreBoost, $request, 'cirrusPhraseBoost' ); self::overrideNumeric( $wgCirrusSearchPhraseSlop[ 'boost' ], $request, 'cirrusPhraseSlop', 10 ); self::overrideNumeric( $wgCirrusSearchFunctionRescoreWindowSize, $request, 'cirrusFunctionWindow', 10000 ); self::overrideNumeric( $wgCirrusSearchFragmentSize, $request, 'cirrusFragmentSize', 1000 ); self::overrideYesNo( $wgCirrusSearchBoostLinks, $request, 'cirrusBoostLinks' ); self::overrideYesNo( $wgCirrusSearchAllFields[ 'use' ], $request, 'cirrusUseAllFields' ); self::overrideYesNo( $wgCirrusSearchAllFieldsForRescore, $request, 'cirrusUseAllFieldsForRescore' ); self::overrideUseExtraPluginForRegex( $request ); self::overrideMoreLikeThisOptions( $request ); PhraseSuggesterProfiles::overrideOptions( $request ); RescoreProfiles::overrideOptions( $request ); FullTextQueryBuilderProfiles::overrideOptions( $request ); self::overrideSecret( $wgCirrusSearchLogElasticRequests, $wgCirrusSearchLogElasticRequestsSecret, $request, 'cirrusLogElasticRequests', false ); self::overrideYesNo( $wgCirrusSearchEnableAltLanguage, $request, 'cirrusAltLanguage' ); } } /** * Set $dest to the numeric value from $request->getVal( $name ) if it is <= $limit * or => $limit if upperLimit is false. * * @param mixed &$dest * @param WebRequest $request * @param string $name * @param int|null $limit * @param bool $upperLimit */ private static function overrideNumeric( &$dest, WebRequest $request, $name, $limit = null, $upperLimit = true ) { Util::overrideNumeric( $dest, $request, $name, $limit, $upperLimit ); } /** * @param mixed &$dest * @param WebRequest $request * @param string $name */ private static function overrideMinimumShouldMatch( &$dest, WebRequest $request, $name ) { $val = $request->getVal( $name ); if ( self::isMinimumShouldMatch( $val ) ) { $dest = $val; } } /** * Set $dest to $value when $request->getVal( $name ) contains $secret * * @param mixed &$dest * @param string $secret * @param WebRequest $request * @param string $name * @param mixed $value */ private static function overrideSecret( &$dest, $secret, WebRequest $request, $name, $value = true ) { if ( $secret && $secret === $request->getVal( $name ) ) { $dest = $value; } } /** * Set $dest to the true/false from $request->getVal( $name ) if yes/no. * * @param mixed &$dest * @param WebRequest $request * @param string $name */ private static function overrideYesNo( &$dest, WebRequest $request, $name ) { Util::overrideYesNo( $dest, $request, $name ); } /** * @param WebRequest $request */ private static function overrideUseExtraPluginForRegex( WebRequest $request ) { global $wgCirrusSearchWikimediaExtraPlugin; if ( $request->getCheck( 'cirrusAccelerateRegex' ) ) { if ( $request->getFuzzyBool( 'cirrusAccelerateRegex' ) ) { $wgCirrusSearchWikimediaExtraPlugin[ 'regex' ][] = 'use'; } elseif ( isset( $wgCirrusSearchWikimediaExtraPlugin[ 'regex' ] ) ) { $useLocation = array_search( 'use', $wgCirrusSearchWikimediaExtraPlugin[ 'regex' ] ); if ( $useLocation !== false ) { unset( $wgCirrusSearchWikimediaExtraPlugin[ 'regex' ][ $useLocation ] ); } } } } /** * Extract more like this settings from the i18n message cirrussearch-morelikethis-settings */ private static function overrideMoreLikeThisOptionsFromMessage() { global $wgCirrusSearchMoreLikeThisConfig, $wgCirrusSearchMoreLikeThisUseFields, $wgCirrusSearchMoreLikeThisAllowedFields, $wgCirrusSearchMoreLikeThisMaxQueryTermsLimit, $wgCirrusSearchMoreLikeThisFields; $cache = \ObjectCache::getLocalServerInstance(); $lines = $cache->getWithSetCallback( $cache->makeKey( 'cirrussearch-morelikethis-settings' ), 600, function () { $source = wfMessage( 'cirrussearch-morelikethis-settings' )->inContentLanguage(); if ( $source && $source->isDisabled() ) { return []; } return Util::parseSettingsInMessage( $source->plain() ); } ); foreach ( $lines as $line ) { if ( false === strpos( $line, ':' ) ) { continue; } list( $k, $v ) = explode( ':', $line, 2 ); switch( $k ) { case 'min_doc_freq': case 'max_doc_freq': case 'max_query_terms': case 'min_term_freq': case 'min_word_len': case 'max_word_len': if( is_numeric( $v ) && $v >= 0 ) { $wgCirrusSearchMoreLikeThisConfig[$k] = intval( $v ); } elseif ( $v === 'null' ) { unset( $wgCirrusSearchMoreLikeThisConfig[$k] ); } break; case 'percent_terms_to_match': // @deprecated Use minimum_should_match now $k = 'minimum_should_match'; if ( is_numeric( $v ) && $v > 0 && $v <= 1 ) { $v = ((int) ($v * 100)) . '%'; } else { break; } // intentional fall-through case 'minimum_should_match': if ( self::isMinimumShouldMatch( $v ) ) { $wgCirrusSearchMoreLikeThisConfig[$k] = $v; } elseif ($v === 'null' ) { unset( $wgCirrusSearchMoreLikeThisConfig[$k] ); } break; case 'fields': $wgCirrusSearchMoreLikeThisFields = array_intersect( array_map( 'trim', explode( ',', $v ) ), $wgCirrusSearchMoreLikeThisAllowedFields ); break; case 'use_fields': if ( $v === 'true' ) { $wgCirrusSearchMoreLikeThisUseFields = true; } elseif ( $v === 'false' ) { $wgCirrusSearchMoreLikeThisUseFields = false; } break; } if ( $wgCirrusSearchMoreLikeThisConfig['max_query_terms'] > $wgCirrusSearchMoreLikeThisMaxQueryTermsLimit ) { $wgCirrusSearchMoreLikeThisConfig['max_query_terms'] = $wgCirrusSearchMoreLikeThisMaxQueryTermsLimit; } } } /** * @param string $v The value to check * @return bool True if $v is an integer percentage in the domain -100 <= $v <= 100, $v != 0 * @todo minimum_should_match also supports combinations (3<90%) and multiple combinations */ private static function isMinimumShouldMatch( $v ) { // specific integer count > 0 if ( ctype_digit( $v ) && $v != 0 ) { return true; } // percentage 0 < x <= 100 if ( substr( $v, -1 ) !== '%' ) { return false; } $v = substr( $v, 0, -1 ); if ( substr( $v, 0, 1 ) === '-' ) { $v = substr( $v, 1 ); } return ctype_digit( $v ) && $v > 0 && $v <= 100; } /** * Override more like this settings from request URI parameters * * @param WebRequest $request */ private static function overrideMoreLikeThisOptions( WebRequest $request ) { global $wgCirrusSearchMoreLikeThisConfig, $wgCirrusSearchMoreLikeThisUseFields, $wgCirrusSearchMoreLikeThisAllowedFields, $wgCirrusSearchMoreLikeThisMaxQueryTermsLimit, $wgCirrusSearchMoreLikeThisFields; self::overrideNumeric( $wgCirrusSearchMoreLikeThisConfig['min_doc_freq'], $request, 'cirrusMltMinDocFreq' ); self::overrideNumeric( $wgCirrusSearchMoreLikeThisConfig['max_doc_freq'], $request, 'cirrusMltMaxDocFreq' ); self::overrideNumeric( $wgCirrusSearchMoreLikeThisConfig['max_query_terms'], $request, 'cirrusMltMaxQueryTerms', $wgCirrusSearchMoreLikeThisMaxQueryTermsLimit ); self::overrideNumeric( $wgCirrusSearchMoreLikeThisConfig['min_term_freq'], $request, 'cirrusMltMinTermFreq' ); self::overrideMinimumShouldMatch( $wgCirrusSearchMoreLikeThisConfig['minimum_should_match'], $request, 'cirrusMltMinimumShouldMatch' ); self::overrideNumeric( $wgCirrusSearchMoreLikeThisConfig['min_word_len'], $request, 'cirrusMltMinWordLength' ); self::overrideNumeric( $wgCirrusSearchMoreLikeThisConfig['max_word_len'], $request, 'cirrusMltMaxWordLength' ); self::overrideYesNo( $wgCirrusSearchMoreLikeThisUseFields, $request, 'cirrusMltUseFields' ); $fields = $request->getVal( 'cirrusMltFields' ); if( isset( $fields ) ) { $wgCirrusSearchMoreLikeThisFields = array_intersect( array_map( 'trim', explode( ',', $fields ) ), $wgCirrusSearchMoreLikeThisAllowedFields ); } } /** * Hook to call before an article is deleted * @param WikiPage $page The page we're deleting * @return bool */ public static function onArticleDelete( $page ) { // We use this to pick up redirects so we can update their targets. // Can't re-use ArticleDeleteComplete because the page info's // already gone // // If we abort or fail deletion it's no big deal because this will // end up being a no-op when it executes. $target = $page->getRedirectTarget(); if ( $target ) { // DeferredUpdate so we don't end up racing our own page deletion DeferredUpdates::addCallableUpdate( function() use ( $target ) { JobQueueGroup::singleton()->push( new Job\LinksUpdate( $target, [ 'addedLinks' => [], 'removedLinks' => [], ] ) ); } ); } return true; } /** * Hook to call after an article is deleted * @param WikiPage $page The page we're deleting * @param User $user The user deleting the page * @param string $reason Reason the page is being deleted * @param int $pageId Page id being deleted * @return bool */ public static function onArticleDeleteComplete( $page, $user, $reason, $pageId ) { // Note that we must use the article id provided or it'll be lost in the ether. The job can't // load it from the title because the page row has already been deleted. JobQueueGroup::singleton()->push( new Job\DeletePages( $page->getTitle(), [ 'docId' => self::getConfig()->makeId( $pageId ) ] ) ); return true; } /** * Called when a page is imported. Force a full index of the page. Use the MassIndex * job since there's likely to be a bunch and we'll prioritize them well but use * INDEX_EVERYTHING since we won't get a chance at a second pass. * * @param Title $title The page title we've just imported * @return bool */ public static function onAfterImportPage( $title ) { // The title can be null if the import failed. Nothing to do in that case. if ( $title === null ) { return false; } JobQueueGroup::singleton()->push( Job\MassIndex::build( [ WikiPage::factory( $title ) ], Updater::INDEX_EVERYTHING ) ); return true; } /** * Called when a revision is deleted. In theory, we shouldn't need to to this since * you can't delete the current text of a page (so we should've already updated when * the page was updated last). But we're paranoid, because deleted revisions absolutely * should not be in the index. * * @param Title $title The page title we've had a revision deleted on * @return bool */ public static function onRevisionDelete( $title ) { JobQueueGroup::singleton()->push( new Job\LinksUpdate( $title, [ 'addedLinks' => [], 'removedLinks' => [], 'prioritize' => true ] ) ); return true; } /** * Hook called to include Elasticsearch version info on Special:Version * @param array $software Array of wikitext and version numbers * @return bool */ public static function onSoftwareInfo( &$software ) { $version = new Version( self::getConnection() ); $status = $version->get(); if ( $status->isOK() ) { // We've already logged if this isn't ok and there is no need to warn the user on this page. $software[ '[https://www.elastic.co/products/elasticsearch Elasticsearch]' ] = $status->getValue(); } return true; } /** * Called to prepend text before search results and inject metrics * @param SpecialSearch $specialSearch The SpecialPage object for Special:Search * @param OutputPage $out The output page object * @param string $term The term being searched for * @return bool */ public static function onSpecialSearchResultsPrepend( $specialSearch, $out, $term ) { global $wgCirrusSearchShowNowUsing; // Prepend our message if needed if ( $wgCirrusSearchShowNowUsing ) { $out->addHTML( Xml::openElement( 'div', [ 'class' => 'cirrussearch-now-using' ] ) . $specialSearch->msg( 'cirrussearch-now-using' )->parse() . Xml::closeElement( 'div' ) ); } // Embed metrics if this was a Cirrus page $engine = $specialSearch->getSearchEngine(); if ( $engine instanceof CirrusSearch ) { $out->addJsConfigVars( $engine->getLastSearchMetrics() ); } return true; } /** * @param SpecialSearch $specialSearch * @param OutputPage $out * @param string $term * @return bool */ public static function onSpecialSearchResultsAppend( $specialSearch, $out, $term ) { global $wgCirrusSearchFeedbackLink; if ( $wgCirrusSearchFeedbackLink ) { self::addSearchFeedbackLink( $wgCirrusSearchFeedbackLink, $specialSearch, $out ); } return true; } /** * @param string $link * @param SpecialSearch $specialSearch * @param OutputPage $out */ private static function addSearchFeedbackLink( $link, SpecialSearch $specialSearch, OutputPage $out ) { $anchor = Xml::element( 'a', [ 'href' => $link ], $specialSearch->msg( 'cirrussearch-give-feedback' )->text() ); $block = Html::rawElement( 'div', [], $anchor ); $out->addHTML( $block ); } /** * Hooked to update the search index when pages change directly or when templates that * they include change. * @param LinksUpdate $linksUpdate source of all links update information * @return bool */ public static function onLinksUpdateCompleted( $linksUpdate ) { global $wgCirrusSearchLinkedArticlesToUpdate, $wgCirrusSearchUnlinkedArticlesToUpdate, $wgCirrusSearchUpdateDelay; // Titles that are created by a move don't need their own job. if ( in_array( $linksUpdate->getTitle()->getPrefixedDBkey(), self::$movingTitles ) ) { return true; } $params = [ 'addedLinks' => self::prepareTitlesForLinksUpdate( $linksUpdate->getAddedLinks(), $wgCirrusSearchLinkedArticlesToUpdate ), 'removedLinks' => self::prepareTitlesForLinksUpdate( $linksUpdate->getRemovedLinks(), $wgCirrusSearchUnlinkedArticlesToUpdate ), ]; // Prioritize jobs that are triggered from a web process. This should prioritize // single page update jobs over those triggered by template changes. if ( PHP_SAPI != 'cli' ) { $params[ 'prioritize' ] = true; } $job = new Job\LinksUpdate( $linksUpdate->getTitle(), $params ); $delay = $wgCirrusSearchUpdateDelay[ $job->isPrioritized() ? 'prioritized' : 'default' ]; $job->setDelay( $delay ); JobQueueGroup::singleton()->push( $job ); return true; } /** * Register Cirrus's unit tests. * @param array $files containing tests * @return bool */ public static function onUnitTestsList( &$files ) { // This is pretty much exactly how the Translate extension declares its // multiple test directories. There really isn't any excuse for doing // it any other way. $dir = __DIR__ . '/../tests/unit'; $directoryIterator = new RecursiveDirectoryIterator( $dir ); $fileIterator = new RecursiveIteratorIterator( $directoryIterator ); foreach ( $fileIterator as $fileInfo ) { if ( substr( $fileInfo->getFilename(), -8 ) === 'Test.php' ) { $files[] = $fileInfo->getPathname(); } } // a bit of a hack...but pull in abstract classes that arn't in the autoloader require_once $dir . '/Query/BaseSimpleKeywordFeatureTest.php'; return true; } /** * Extract namespaces from query string. * @param array $namespaces * @param string $search * @return boolean */ public static function prefixSearchExtractNamespace( &$namespaces, &$search ) { $searcher = new Searcher( self::getConnection(), 0, 1, null, $namespaces ); $searcher->updateNamespacesFromQuery( $search ); $namespaces = $searcher->getSearchContext()->getNamespaces(); return false; } /** * 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, [ $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 // construction 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; } /** * Before we've moved a title from $title to $newTitle. * @param Title $title old title * @param Title $newTitle new title * @param User $user User who made the move * @return bool should move move actions be precessed (yes) */ public static function onTitleMove( Title $title, Title $newTitle, $user ) { self::$movingTitles[] = $title->getPrefixedDBkey(); return true; } /** * When we've moved a Title from A to B. * @param Title $title The old title * @param Title $newTitle The new title * @param User $user User who made the move * @param int $oldId The page id of the old page. * @return bool */ public static function onTitleMoveComplete( Title $title, Title $newTitle, $user, $oldId ) { // When a page is moved the update and delete hooks are good enough to catch // almost everything. The only thing they miss is if a page moves from one // index to another. That only happens if it switches namespace. if ( $title->getNamespace() !== $newTitle->getNamespace() ) { $conn = self::getConnection(); $oldIndexType = $conn->getIndexSuffixForNamespace( $title->getNamespace() ); $job = new Job\DeletePages( $title, [ 'indexType' => $oldIndexType, 'docId' => self::getConfig()->makeId( $oldId ) ] ); // Push the job after DB commit but cancel on rollback wfGetDB( DB_MASTER )->onTransactionIdle( function() use ( $job ) { JobQueueGroup::singleton()->lazyPush( $job ); } ); } return true; } /** * Take a list of titles either linked or unlinked and prepare them for Job\LinksUpdate. * This includes limiting them to $max titles. * @param Title[] $titles titles to prepare * @param int $max maximum number of titles to return * @return array */ private static function prepareTitlesForLinksUpdate( $titles, $max ) { $titles = self::pickFromArray( $titles, $max ); $dBKeys = []; foreach ( $titles as $title ) { $dBKeys[] = $title->getPrefixedDBkey(); } return $dBKeys; } /** * Pick $num random entries from $array. * @param array $array Array to pick from * @param int $num Number of entries to pick * @return array of entries from $array */ private static function pickFromArray( $array, $num ) { if ( $num > count( $array ) ) { return $array; } if ( $num < 1 ) { return []; } $chosen = array_rand( $array, $num ); // If $num === 1 then array_rand will return a key rather than an array of keys. if ( !is_array( $chosen ) ) { return [ $array[ $chosen ] ]; } $result = []; foreach ( $chosen as $key ) { $result[] = $array[ $key ]; } return $result; } /** * ResourceLoaderGetConfigVars hook handler * This should be used for variables which vary with the html * and for variables this should work cross skin * @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderGetConfigVars * * @param array $vars * @return bool */ public static function onResourceLoaderGetConfigVars( &$vars ) { global $wgCirrusSearchEnableSearchLogging, $wgCirrusSearchFeedbackLink; $vars += [ 'wgCirrusSearchEnableSearchLogging' => $wgCirrusSearchEnableSearchLogging, 'wgCirrusSearchFeedbackLink' => $wgCirrusSearchFeedbackLink, ]; return true; } /** * @return SearchConfig */ private static function getConfig() { return MediaWikiServices::getInstance() ->getConfigFactory() ->makeConfig( 'CirrusSearch' ); } /** * @return Connection */ private static function getConnection() { return new Connection( self::getConfig() ); } /** * Add $wgCirrusSearchInterwikiProv to external results. * @param Title $title * @param mixed $text * @param mixed $result * @param mixed $terms * @param mixed $page * @param array $query */ public static function onShowSearchHitTitle( Title $title, &$text, $result, $terms, $page, &$query = [] ) { global $wgCirrusSearchInterwikiProv; if( $wgCirrusSearchInterwikiProv && $title->isExternal() ) { $query["wprov"] = $wgCirrusSearchInterwikiProv; } } /** * Activate Completion Suggester as a Beta Feature if available * @param User $user * @param array &$pref beta feature prefs * @return boolean */ public static function getBetaFeaturePreferences( User $user, &$pref ) { global $wgCirrusSearchUseCompletionSuggester, $wgExtensionAssetsPath; if ( $wgCirrusSearchUseCompletionSuggester !== 'beta' ) { return true; } $pref['cirrussearch-completionsuggester'] = [ 'label-message' => 'cirrussearch-completionsuggester-pref', 'desc-message' => 'cirrussearch-completionsuggester-desc', 'info-link' => '//mediawiki.org/wiki/Special:MyLanguage/Extension:CirrusSearch/CompletionSuggester', 'discussion-link' => '//mediawiki.org/wiki/Special:MyLanguage/Extension_talk:CirrusSearch/CompletionSuggester', 'screenshot' => [ 'ltr' => "$wgExtensionAssetsPath/CirrusSearch/resources/images/cirrus-beta-ltr.svg", 'rtl' => "$wgExtensionAssetsPath/CirrusSearch/resources/images/cirrus-beta-rtl.svg", ] ]; return true; } /** * @param ApiBase $module * @return bool */ public static function onAPIAfterExecute( $module ) { if ( !( $module instanceof ApiOpenSearch ) ) { return true; } $types = ElasticsearchIntermediary::getQueryTypesUsed(); if ( !$types ) { return true; } $response = $module->getContext()->getRequest()->response(); $response->header( 'X-OpenSearch-Type: ' . implode( ',', $types ) ); return true; } /** * @param string $term * @param SearchResultSet|null &$titleMatches * @param SearchResultSet|null &$textMatches */ public static function onSpecialSearchResults( $term, &$titleMatches, &$textMatches ) { global $wgOut; $wgOut->addModules( 'ext.cirrus.serp' ); $wgOut->addJsConfigVars( [ 'wgCirrusSearchRequestSetToken' => ElasticsearchIntermediary::getRequestSetToken(), ] ); // This ignores interwiki results for now...not sure what do do with those ElasticsearchIntermediary::setResultPages( [ $titleMatches, $textMatches ] ); } }