%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
] );
}
}