'SearchDatabase' => __DIR__ . '/includes/search/SearchDatabase.php',
'SearchDump' => __DIR__ . '/maintenance/dumpIterator.php',
'SearchEngine' => __DIR__ . '/includes/search/SearchEngine.php',
+ 'SearchEngineConfig' => __DIR__ . '/includes/search/SearchEngineConfig.php',
'SearchEngineDummy' => __DIR__ . '/includes/search/SearchEngine.php',
+ 'SearchEngineFactory' => __DIR__ . '/includes/search/SearchEngineFactory.php',
'SearchExactMatchRescorer' => __DIR__ . '/includes/search/SearchExactMatchRescorer.php',
'SearchHighlighter' => __DIR__ . '/includes/search/SearchHighlighter.php',
'SearchMssql' => __DIR__ . '/includes/search/SearchMssql.php',
'SearchMySQL' => __DIR__ . '/includes/search/SearchMySQL.php',
'SearchNearMatchResultSet' => __DIR__ . '/includes/search/SearchNearMatchResultSet.php',
+ 'SearchNearMatcher' => __DIR__ . '/includes/search/SearchNearMatcher.php',
'SearchOracle' => __DIR__ . '/includes/search/SearchOracle.php',
'SearchPostgres' => __DIR__ . '/includes/search/SearchPostgres.php',
'SearchResult' => __DIR__ . '/includes/search/SearchResult.php',
return $this->getService( 'EventRelayerGroup' );
}
+ /**
+ * @return SearchEngine
+ */
+ public function newSearchEngine() {
+ // New engine object every time, since they keep state
+ return $this->getService( 'SearchEngineFactory' )->create();
+ }
+
+ /**
+ * @return SearchEngineFactory
+ */
+ public function getSearchEngineFactory() {
+ return $this->getService( 'SearchEngineFactory' );
+ }
+
+ /**
+ * @return SearchEngineConfig
+ */
+ public function getSearchEngineConfig() {
+ return $this->getService( 'SearchEngineConfig' );
+ }
+
///////////////////////////////////////////////////////////////////////////
// NOTE: When adding a service getter here, don't forget to add a test
// case for it in MediaWikiServicesTest::provideGetters() and in
return new EventRelayerGroup( $services->getMainConfig()->get( 'EventRelayerConfig' ) );
},
+ 'SearchEngineFactory' => function( MediaWikiServices $services ) {
+ // Create search engine
+ return new SearchEngineFactory( $services->getService( 'SearchEngineConfig' ) );
+ },
+
+ 'SearchEngineConfig' => function( MediaWikiServices $services ) {
+ // Create a search engine config from main config.
+ $config = $services->getService( 'MainConfig' );
+ return new SearchEngineConfig( $config );
+ }
+
///////////////////////////////////////////////////////////////////////////
// NOTE: When adding a service here, don't forget to add a getter function
// in the MediaWikiServices class. The convenience getter should just call
* @file
*/
+use MediaWiki\MediaWikiServices;
+
/**
* @ingroup API
*/
* @param array &$results Put results here. Keys have to be integers.
*/
protected function search( $search, $limit, $namespaces, $resolveRedir, &$results ) {
-
- $searchEngine = SearchEngine::create();
+ $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
$searchEngine->setLimitOffset( $limit );
$searchEngine->setNamespaces( $namespaces );
$titles = $searchEngine->extractTitles( $searchEngine->completionSearchWithVariants( $search ) );
* @throws MWException
*/
public static function getOpenSearchTemplate( $type ) {
- global $wgOpenSearchTemplate, $wgCanonicalServer;
+ $config = MediaWikiServices::getInstance()->getSearchEngineConfig();
+ $template = $config->getConfig()->get( 'OpenSearchTemplate' );
- if ( $wgOpenSearchTemplate && $type === 'application/x-suggestions+json' ) {
- return $wgOpenSearchTemplate;
+ if ( $template && $type === 'application/x-suggestions+json' ) {
+ return $template;
}
- $ns = implode( '|', SearchEngine::defaultNamespaces() );
+ $ns = implode( '|', $config->defaultNamespaces() );
if ( !$ns ) {
$ns = '0';
}
switch ( $type ) {
case 'application/x-suggestions+json':
- return $wgCanonicalServer . wfScript( 'api' )
+ return $config->getConfig()->get( 'CanonicalServer' ) . wfScript( 'api' )
. '?action=opensearch&search={searchTerms}&namespace=' . $ns;
case 'application/x-suggestions+xml':
- return $wgCanonicalServer . wfScript( 'api' )
+ return $config->getConfig()->get( 'CanonicalServer' ) . wfScript( 'api' )
. '?action=opensearch&format=xml&search={searchTerms}&namespace=' . $ns;
default:
<?php
+use MediaWiki\MediaWikiServices;
+
/**
* 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
$namespaces = $params['namespace'];
$offset = $params['offset'];
- $searchEngine = SearchEngine::create();
+ $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
$searchEngine->setLimitOffset( $limit + 1, $offset );
$searchEngine->setNamespaces( $namespaces );
$titles = $searchEngine->extractTitles( $searchEngine->completionSearchWithVariants( $search ) );
* @file
*/
+use MediaWiki\MediaWikiServices;
+
/**
* Query module to perform full text search within wiki titles and content
*
}
// Create search engine instance and set options
- $search = isset( $params['backend'] ) && $params['backend'] != self::BACKEND_NULL_PARAM ?
- SearchEngine::create( $params['backend'] ) : SearchEngine::create();
+ $type = isset( $params['backend'] ) && $params['backend'] != self::BACKEND_NULL_PARAM ?
+ $params['backend'] : null;
+ $search = MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $type );
$search->setLimitOffset( $limit + 1, $params['offset'] );
$search->setNamespaces( $params['namespace'] );
$search->setFeatureData( 'rewrite', (bool)$params['enablerewrites'] );
} elseif ( $what == 'nearmatch' ) {
// near matches must receive the user input as provided, otherwise
// the near matches within namespaces are lost.
- $matches = SearchEngine::getNearMatchResultSet( $params['search'] );
+ $matches = $search->getNearMatcher( $this->getConfig() )
+ ->getNearMatchResultSet( $params['search'] );
} else {
// We default to title searches; this is a terrible legacy
// of the way we initially set up the MySQL fulltext-based
'enablerewrites' => false,
];
- $alternatives = SearchEngine::getSearchTypes();
+ $searchConfig = MediaWikiServices::getInstance()->getSearchEngineConfig();
+ $alternatives = $searchConfig->getSearchTypes();
if ( count( $alternatives ) > 1 ) {
if ( $alternatives[0] === null ) {
$alternatives[0] = self::BACKEND_NULL_PARAM;
}
$params['backend'] = [
- ApiBase::PARAM_DFLT => $this->getConfig()->get( 'SearchType' ),
+ ApiBase::PARAM_DFLT => $searchConfig->getSearchType(),
ApiBase::PARAM_TYPE => $alternatives,
];
}
* @ingroup Search
*/
+use MediaWiki\MediaWikiServices;
+
/**
* Database independant search index updater
*
* Perform actual update for the entry
*/
public function doUpdate() {
- global $wgDisableSearchUpdate;
+ $config = MediaWikiServices::getInstance()->getSearchEngineConfig();
- if ( $wgDisableSearchUpdate || !$this->id ) {
+ if ( $config->getConfig()->get( 'DisableSearchUpdate' ) || !$this->id ) {
return;
}
- foreach ( SearchEngine::getSearchTypes() as $type ) {
- $search = SearchEngine::create( $type );
+ $seFactory = MediaWikiServices::getInstance()->getSearchEngineFactory();
+ foreach ( $config->getSearchTypes() as $type ) {
+ $search = $seFactory->create( $type );
if ( !$search->supports( 'search-update' ) ) {
continue;
}
$text = $search->getTextFromContent( $this->title, $this->content );
if ( !$search->textAlreadyUpdatedForIndex() ) {
- $text = self::updateText( $text );
+ $text = $this->updateText( $text, $search );
}
# Perform the actual update
* If you're using a real search engine, you'll probably want to override
* this behavior and do something nicer with the original wikitext.
* @param string $text
+ * @param SearchEngine $se Search engine
* @return string
*/
- public static function updateText( $text ) {
+ public function updateText( $text, SearchEngine $se = null ) {
global $wgContLang;
# Language-specific strip/conversion
$text = $wgContLang->normalizeForSearch( $text );
- $lc = SearchEngine::legalSearchChars() . '&#;';
+ $se = $se ?: MediaWikiServices::getInstance()->newSearchEngine();
+ $lc = $se->legalSearchChars() . '&#;';
$text = preg_replace( "/<\\/?\\s*[A-Za-z][^>]*?>/",
' ', $wgContLang->lc( " " . $text . " " ) ); # Strip HTML markup
* @defgroup Search Search
*/
+use MediaWiki\MediaWikiServices;
+
/**
* Contain a class for special pages
* @ingroup Search
*/
-class SearchEngine {
+abstract class SearchEngine {
/** @var string */
public $prefix = '';
* @param string $term
* @return string
*/
- function transformSearchTerm( $term ) {
+ public function transformSearchTerm( $term ) {
return $term;
}
+ /**
+ * Get service class to finding near matches.
+ * @param Config $config Configuration to use for the matcher.
+ * @return SearchNearMatcher
+ */
+ public function getNearMatcher( Config $config ) {
+ return new SearchNearMatcher( $config );
+ }
+
+ /**
+ * Get near matcher for default SearchEngine.
+ * @return SearchNearMatcher
+ */
+ protected static function defaultNearMatcher() {
+ $config = MediaWikiServices::getInstance()->getMainConfig();
+ return MediaWikiServices::getInstance()->newSearchEngine()->getNearMatcher( $config );
+ }
+
/**
* If an exact title match can be found, or a very slightly close match,
* return the title. If no match, returns NULL.
- *
+ * @deprecated since 1.27; Use SearchEngine::getNearMatcher()
* @param string $searchterm
* @return Title
*/
public static function getNearMatch( $searchterm ) {
- $title = self::getNearMatchInternal( $searchterm );
-
- Hooks::run( 'SearchGetNearMatchComplete', [ $searchterm, &$title ] );
- return $title;
+ return static::defaultNearMatcher()->getNearMatch( $searchterm );
}
/**
* Do a near match (see SearchEngine::getNearMatch) and wrap it into a
* SearchResultSet.
- *
+ * @deprecated since 1.27; Use SearchEngine::getNearMatcher()
* @param string $searchterm
* @return SearchResultSet
*/
public static function getNearMatchResultSet( $searchterm ) {
- return new SearchNearMatchResultSet( self::getNearMatch( $searchterm ) );
+ return static::defaultNearMatcher()->getNearMatchResultSet( $searchterm );
}
/**
- * Really find the title match.
- * @param string $searchterm
- * @return null|Title
+ * Get chars legal for search.
+ * NOTE: usage as static is deprecated and preserved only as BC measure
+ * @return string
*/
- private static function getNearMatchInternal( $searchterm ) {
- global $wgContLang, $wgEnableSearchContributorsByIP;
-
- $allSearchTerms = [ $searchterm ];
-
- if ( $wgContLang->hasVariants() ) {
- $allSearchTerms = array_unique( array_merge(
- $allSearchTerms,
- $wgContLang->autoConvertToAllVariants( $searchterm )
- ) );
- }
-
- $titleResult = null;
- if ( !Hooks::run( 'SearchGetNearMatchBefore', [ $allSearchTerms, &$titleResult ] ) ) {
- return $titleResult;
- }
-
- foreach ( $allSearchTerms as $term ) {
-
- # Exact match? No need to look further.
- $title = Title::newFromText( $term );
- if ( is_null( $title ) ) {
- return null;
- }
-
- # Try files if searching in the Media: namespace
- if ( $title->getNamespace() == NS_MEDIA ) {
- $title = Title::makeTitle( NS_FILE, $title->getText() );
- }
-
- if ( $title->isSpecialPage() || $title->isExternal() || $title->exists() ) {
- return $title;
- }
-
- # See if it still otherwise has content is some sane sense
- $page = WikiPage::factory( $title );
- if ( $page->hasViewableContent() ) {
- return $title;
- }
-
- if ( !Hooks::run( 'SearchAfterNoDirectMatch', [ $term, &$title ] ) ) {
- return $title;
- }
-
- # Now try all lower case (i.e. first letter capitalized)
- $title = Title::newFromText( $wgContLang->lc( $term ) );
- if ( $title && $title->exists() ) {
- return $title;
- }
-
- # Now try capitalized string
- $title = Title::newFromText( $wgContLang->ucwords( $term ) );
- if ( $title && $title->exists() ) {
- return $title;
- }
-
- # Now try all upper case
- $title = Title::newFromText( $wgContLang->uc( $term ) );
- if ( $title && $title->exists() ) {
- return $title;
- }
-
- # Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
- $title = Title::newFromText( $wgContLang->ucwordbreaks( $term ) );
- if ( $title && $title->exists() ) {
- return $title;
- }
-
- // Give hooks a chance at better match variants
- $title = null;
- if ( !Hooks::run( 'SearchGetNearMatch', [ $term, &$title ] ) ) {
- return $title;
- }
- }
-
- $title = Title::newFromText( $searchterm );
-
- # Entering an IP address goes to the contributions page
- if ( $wgEnableSearchContributorsByIP ) {
- if ( ( $title->getNamespace() == NS_USER && User::isIP( $title->getText() ) )
- || User::isIP( trim( $searchterm ) ) ) {
- return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
- }
- }
-
- # Entering a user goes to the user page whether it's there or not
- if ( $title->getNamespace() == NS_USER ) {
- return $title;
- }
-
- # Go to images that exist even if there's no local page.
- # There may have been a funny upload, or it may be on a shared
- # file repository such as Wikimedia Commons.
- if ( $title->getNamespace() == NS_FILE ) {
- $image = wfFindFile( $title );
- if ( $image ) {
- return $title;
- }
- }
-
- # MediaWiki namespace? Page may be "implied" if not customized.
- # Just return it, with caps forced as the message system likes it.
- if ( $title->getNamespace() == NS_MEDIAWIKI ) {
- return Title::makeTitle( NS_MEDIAWIKI, $wgContLang->ucfirst( $title->getText() ) );
- }
-
- # Quoted term? Try without the quotes...
- $matches = [];
- if ( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
- return SearchEngine::getNearMatch( $matches[1] );
- }
-
- return null;
- }
-
public static function legalSearchChars() {
return "A-Za-z_'.0-9\\x80-\\xFF\\-";
}
return $parsed;
}
- /**
- * Make a list of searchable namespaces and their canonical names.
- * @return array
- */
- public static function searchableNamespaces() {
- global $wgContLang;
- $arr = [];
- foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
- if ( $ns >= NS_MAIN ) {
- $arr[$ns] = $name;
- }
- }
-
- Hooks::run( 'SearchableNamespaces', [ &$arr ] );
- return $arr;
- }
-
- /**
- * Extract default namespaces to search from the given user's
- * settings, returning a list of index numbers.
- *
- * @param user $user
- * @return array
- */
- public static function userNamespaces( $user ) {
- $arr = [];
- foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) {
- if ( $user->getOption( 'searchNs' . $ns ) ) {
- $arr[] = $ns;
- }
- }
-
- return $arr;
- }
-
/**
* Find snippet highlight settings for all users
- *
* @return array Contextlines, contextchars
*/
public static function userHighlightPrefs() {
return [ $contextlines, $contextchars ];
}
- /**
- * An array of namespaces indexes to be searched by default
- *
- * @return array
- */
- public static function defaultNamespaces() {
- global $wgNamespacesToBeSearchedDefault;
-
- return array_keys( $wgNamespacesToBeSearchedDefault, true );
- }
-
- /**
- * Get a list of namespace names useful for showing in tooltips
- * and preferences
- *
- * @param array $namespaces
- * @return array
- */
- public static function namespacesAsText( $namespaces ) {
- global $wgContLang;
-
- $formatted = array_map( [ $wgContLang, 'getFormattedNsText' ], $namespaces );
- foreach ( $formatted as $key => $ns ) {
- if ( empty( $ns ) ) {
- $formatted[$key] = wfMessage( 'blanknamespace' )->text();
- }
- }
- return $formatted;
- }
-
- /**
- * Load up the appropriate search engine class for the currently
- * active database backend, and return a configured instance.
- *
- * @param string $type Type of search backend, if not the default
- * @return SearchEngine
- */
- public static function create( $type = null ) {
- global $wgSearchType;
- $dbr = null;
-
- $alternatives = self::getSearchTypes();
-
- if ( $type && in_array( $type, $alternatives ) ) {
- $class = $type;
- } elseif ( $wgSearchType !== null ) {
- $class = $wgSearchType;
- } else {
- $dbr = wfGetDB( DB_SLAVE );
- $class = $dbr->getSearchEngine();
- }
-
- $search = new $class( $dbr );
- return $search;
- }
-
- /**
- * Return the search engines we support. If only $wgSearchType
- * is set, it'll be an array of just that one item.
- *
- * @return array
- */
- public static function getSearchTypes() {
- global $wgSearchType, $wgSearchTypeAlternatives;
-
- $alternatives = $wgSearchTypeAlternatives ?: [];
- array_unshift( $alternatives, $wgSearchType );
-
- return $alternatives;
- }
-
/**
* Create or update the search index record for the given page.
* Title and text should be pre-processed.
return $backend->defaultSearchBackend( $this->namespaces, $search, $this->limit, $this->offset );
}
+ /**
+ * Make a list of searchable namespaces and their canonical names.
+ * @deprecated since 1.27; use SearchEngineConfig::searchableNamespaces()
+ * @return array
+ */
+ public static function searchableNamespaces() {
+ return MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
+ }
+
+ /**
+ * Extract default namespaces to search from the given user's
+ * settings, returning a list of index numbers.
+ * @deprecated since 1.27; use SearchEngineConfig::userNamespaces()
+ * @param user $user
+ * @return array
+ */
+ public static function userNamespaces( $user ) {
+ return MediaWikiServices::getInstance()->getSearchEngineConfig()->userNamespaces( $user );
+ }
+
+ /**
+ * An array of namespaces indexes to be searched by default
+ * @deprecated since 1.27; use SearchEngineConfig::defaultNamespaces()
+ * @return array
+ */
+ public static function defaultNamespaces() {
+ return MediaWikiServices::getInstance()->getSearchEngineConfig()->defaultNamespaces();
+ }
+
+ /**
+ * Get a list of namespace names useful for showing in tooltips
+ * and preferences
+ * @deprecated since 1.27; use SearchEngineConfig::namespacesAsText()
+ * @param array $namespaces
+ * @return array
+ */
+ public static function namespacesAsText( $namespaces ) {
+ return MediaWikiServices::getInstance()->getSearchEngineConfig()->namespacesAsText();
+ }
+
+ /**
+ * Load up the appropriate search engine class for the currently
+ * active database backend, and return a configured instance.
+ * @deprecated since 1.27; Use SearchEngineFactory::create
+ * @param string $type Type of search backend, if not the default
+ * @return SearchEngine
+ */
+ public static function create( $type = null ) {
+ return MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $type );
+ }
+
+ /**
+ * Return the search engines we support. If only $wgSearchType
+ * is set, it'll be an array of just that one item.
+ * @deprecated since 1.27; use SearchEngineConfig::getSearchTypes()
+ * @return array
+ */
+ public static function getSearchTypes() {
+ return MediaWikiServices::getInstance()->getSearchEngineConfig()->getSearchTypes();
+ }
+
}
/**
--- /dev/null
+<?php
+
+/**
+ * Configuration handling class for SearchEngine.
+ * Provides added service over plain configuration.
+ *
+ * @since 1.27
+ */
+class SearchEngineConfig {
+
+ /**
+ * Config object from which the settings will be derived.
+ * @var Config
+ */
+ private $config;
+
+ public function __construct( Config $config ) {
+ $this->config = $config;
+ }
+
+ /**
+ * Retrieve original config.
+ * @return Config
+ */
+ public function getConfig() {
+ return $this->config;
+ }
+
+ /**
+ * Make a list of searchable namespaces and their canonical names.
+ * @return array Namespace ID => name
+ */
+ public function searchableNamespaces() {
+ $arr = [];
+ foreach ( $this->config->get( 'ContLang' )->getNamespaces() as $ns => $name ) {
+ if ( $ns >= NS_MAIN ) {
+ $arr[$ns] = $name;
+ }
+ }
+
+ Hooks::run( 'SearchableNamespaces', [ &$arr ] );
+ return $arr;
+ }
+
+ /**
+ * Extract default namespaces to search from the given user's
+ * settings, returning a list of index numbers.
+ *
+ * @param user $user
+ * @return int[]
+ */
+ public function userNamespaces( $user ) {
+ $arr = [];
+ foreach ( $this->searchableNamespaces() as $ns => $name ) {
+ if ( $user->getOption( 'searchNs' . $ns ) ) {
+ $arr[] = $ns;
+ }
+ }
+
+ return $arr;
+ }
+
+ /**
+ * An array of namespaces indexes to be searched by default
+ *
+ * @return int[] Namespace IDs
+ */
+ public function defaultNamespaces() {
+ return array_keys( $this->config->get( 'NamespacesToBeSearchedDefault' ), true );
+ }
+
+ /**
+ * Return the search engines we support. If only $wgSearchType
+ * is set, it'll be an array of just that one item.
+ *
+ * @return array
+ */
+ public function getSearchTypes() {
+ $alternatives = $this->config->get( 'SearchTypeAlternatives' ) ?: [];
+ array_unshift( $alternatives, $this->config->get( 'SearchType' ) );
+
+ return $alternatives;
+ }
+
+ /**
+ * Return the search engine configured in $wgSearchType, etc.
+ *
+ * @return string|null
+ */
+ public function getSearchType() {
+ return $this->config->get( 'SearchType' );
+ }
+
+ /**
+ * Get a list of namespace names useful for showing in tooltips
+ * and preferences.
+ *
+ * @param int[] $namespaces
+ * @return string[] List of names
+ */
+ public function namespacesAsText( $namespaces ) {
+ $formatted = array_map( [ $this->config->get( 'ContLang' ), 'getFormattedNsText' ], $namespaces );
+ foreach ( $formatted as $key => $ns ) {
+ if ( empty( $ns ) ) {
+ $formatted[$key] = wfMessage( 'blanknamespace' )->text();
+ }
+ }
+ return $formatted;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Factory class for SearchEngine.
+ * Allows to create engine of the specific type.
+ */
+class SearchEngineFactory {
+
+ /**
+ * Configuration for SearchEngine classes.
+ * @var SearchEngineConfig
+ */
+ private $config;
+
+ public function __construct( SearchEngineConfig $config ) {
+ $this->config = $config;
+ }
+
+ /**
+ * Create SearchEngine of the given type.
+ * @param string $type
+ * @return SearchEngine
+ */
+ public function create( $type = null ) {
+ $dbr = null;
+
+ $configType = $this->config->getSearchType();
+ $alternatives = $this->config->getSearchTypes();
+
+ if ( $type && in_array( $type, $alternatives ) ) {
+ $class = $type;
+ } elseif ( $configType !== null ) {
+ $class = $configType;
+ } else {
+ $dbr = wfGetDB( DB_SLAVE );
+ $class = $dbr->getSearchEngine();
+ }
+
+ $search = new $class( $dbr );
+ return $search;
+ }
+}
<?php
/**
- * A SearchResultSet wrapper for SearchEngine::getNearMatch
+ * A SearchResultSet wrapper for SearchNearMatcher
*/
class SearchNearMatchResultSet extends SearchResultSet {
private $fetched = false;
--- /dev/null
+<?php
+
+/**
+ * Implementation of near match title search.
+ * TODO: split into service/implementation.
+ */
+class SearchNearMatcher {
+ /**
+ * Configuration object.
+ * @param Config $config
+ */
+ protected $config;
+
+ public function __construct( Config $config ) {
+
+ $this->config = $config;
+ }
+
+ /**
+ * If an exact title match can be found, or a very slightly close match,
+ * return the title. If no match, returns NULL.
+ *
+ * @param string $searchterm
+ * @return Title
+ */
+ public function getNearMatch( $searchterm ) {
+ $title = $this->getNearMatchInternal( $searchterm );
+
+ Hooks::run( 'SearchGetNearMatchComplete', [ $searchterm, &$title ] );
+ return $title;
+ }
+
+ /**
+ * Do a near match (see SearchEngine::getNearMatch) and wrap it into a
+ * SearchResultSet.
+ *
+ * @param string $searchterm
+ * @return SearchResultSet
+ */
+ public function getNearMatchResultSet( $searchterm ) {
+ return new SearchNearMatchResultSet( $this->getNearMatch( $searchterm ) );
+ }
+
+ /**
+ * Really find the title match.
+ * @param string $searchterm
+ * @return null|Title
+ */
+ protected function getNearMatchInternal( $searchterm ) {
+ $lang = $this->config->get( 'ContLang' );
+
+ $allSearchTerms = [ $searchterm ];
+
+ if ( $lang->hasVariants() ) {
+ $allSearchTerms = array_unique( array_merge(
+ $allSearchTerms,
+ $lang->autoConvertToAllVariants( $searchterm )
+ ) );
+ }
+
+ $titleResult = null;
+ if ( !Hooks::run( 'SearchGetNearMatchBefore', [ $allSearchTerms, &$titleResult ] ) ) {
+ return $titleResult;
+ }
+
+ foreach ( $allSearchTerms as $term ) {
+
+ # Exact match? No need to look further.
+ $title = Title::newFromText( $term );
+ if ( is_null( $title ) ) {
+ return null;
+ }
+
+ # Try files if searching in the Media: namespace
+ if ( $title->getNamespace() == NS_MEDIA ) {
+ $title = Title::makeTitle( NS_FILE, $title->getText() );
+ }
+
+ if ( $title->isSpecialPage() || $title->isExternal() || $title->exists() ) {
+ return $title;
+ }
+
+ # See if it still otherwise has content is some sane sense
+ $page = WikiPage::factory( $title );
+ if ( $page->hasViewableContent() ) {
+ return $title;
+ }
+
+ if ( !Hooks::run( 'SearchAfterNoDirectMatch', [ $term, &$title ] ) ) {
+ return $title;
+ }
+
+ # Now try all lower case (i.e. first letter capitalized)
+ $title = Title::newFromText( $lang->lc( $term ) );
+ if ( $title && $title->exists() ) {
+ return $title;
+ }
+
+ # Now try capitalized string
+ $title = Title::newFromText( $lang->ucwords( $term ) );
+ if ( $title && $title->exists() ) {
+ return $title;
+ }
+
+ # Now try all upper case
+ $title = Title::newFromText( $lang->uc( $term ) );
+ if ( $title && $title->exists() ) {
+ return $title;
+ }
+
+ # Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
+ $title = Title::newFromText( $lang->ucwordbreaks( $term ) );
+ if ( $title && $title->exists() ) {
+ return $title;
+ }
+
+ // Give hooks a chance at better match variants
+ $title = null;
+ if ( !Hooks::run( 'SearchGetNearMatch', [ $term, &$title ] ) ) {
+ return $title;
+ }
+ }
+
+ $title = Title::newFromText( $searchterm );
+
+ # Entering an IP address goes to the contributions page
+ if ( $this->config->get( 'EnableSearchContributorsByIP' ) ) {
+ if ( ( $title->getNamespace() == NS_USER && User::isIP( $title->getText() ) )
+ || User::isIP( trim( $searchterm ) ) ) {
+ return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
+ }
+ }
+
+ # Entering a user goes to the user page whether it's there or not
+ if ( $title->getNamespace() == NS_USER ) {
+ return $title;
+ }
+
+ # Go to images that exist even if there's no local page.
+ # There may have been a funny upload, or it may be on a shared
+ # file repository such as Wikimedia Commons.
+ if ( $title->getNamespace() == NS_FILE ) {
+ $image = wfFindFile( $title );
+ if ( $image ) {
+ return $title;
+ }
+ }
+
+ # MediaWiki namespace? Page may be "implied" if not customized.
+ # Just return it, with caps forced as the message system likes it.
+ if ( $title->getNamespace() == NS_MEDIAWIKI ) {
+ return Title::makeTitle( NS_MEDIAWIKI, $lang->ucfirst( $title->getText() ) );
+ }
+
+ # Quoted term? Try without the quotes...
+ $matches = [];
+ if ( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
+ return self::getNearMatch( $matches[1] );
+ }
+
+ return null;
+ }
+}
* @ingroup Search
*/
+use MediaWiki\MediaWikiServices;
+
/**
* @todo FIXME: This class is horribly factored. It would probably be better to
* have a useful base class to which you pass some standard information, then
*/
protected $mText;
+ /**
+ * @var SearchEngine
+ */
+ protected $searchEngine;
+
/**
* Return a new SearchResult and initializes it with a title.
*
* @return SearchResult
*/
public static function newFromTitle( $title ) {
- $result = new self();
+ $result = new static();
$result->initFromTitle( $title );
return $result;
}
$this->mImage = wfFindFile( $this->mTitle );
}
}
+ $this->searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
}
/**
protected function initText() {
if ( !isset( $this->mText ) ) {
if ( $this->mRevision != null ) {
- $this->mText = SearchEngine::create()
- ->getTextFromContent( $this->mTitle, $this->mRevision->getContent() );
+ $this->mText = $this->searchEngine->getTextFromContent(
+ $this->mTitle, $this->mRevision->getContent() );
} else { // TODO: can we fetch raw wikitext for commons images?
$this->mText = '';
}
$this->initText();
// TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
- list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs();
+ list( $contextlines, $contextchars ) = $this->searchEngine->userHighlightPrefs();
$h = new SearchHighlighter();
if ( count( $terms ) > 0 ) {
<?php
+use MediaWiki\MediaWikiServices;
+
/**
* Parent class for all special pages.
*
return [];
}
- $searchEngine = SearchEngine::create();
+ $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
$searchEngine->setLimitOffset( $limit, $offset );
$searchEngine->setNamespaces( [] );
$result = $searchEngine->defaultPrefixSearch( $search );
<?php
+use MediaWiki\MediaWikiServices;
+
/**
* Implements Special:FileDuplicateSearch
*
// No prefix suggestion outside of file namespace
return [];
}
- $searchEngine = SearchEngine::create();
+ $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
$searchEngine->setLimitOffset( $limit, $offset );
// Autocomplete subpage the same as a normal search, but just for files
$searchEngine->setNamespaces( [ NS_FILE ] );
* @ingroup SpecialPage
*/
+use MediaWiki\MediaWikiServices;
+
/**
* implements Special:Search - Run text & title search and display the output
* @ingroup SpecialPage
*/
protected $customCaptions;
+ /**
+ * Search engine configurations.
+ * @var SearchEngineConfig
+ */
+ protected $searchConfig;
+
const NAMESPACES_CURRENT = 'sense';
public function __construct() {
parent::__construct( 'Search' );
+ $this->searchConfig = MediaWikiServices::getInstance()->getSearchEngineConfig();
}
/**
$nslist = $this->powerSearch( $request );
if ( !count( $nslist ) ) {
# Fallback to user preference
- $nslist = SearchEngine::userNamespaces( $user );
+ $nslist = $this->searchConfig->userNamespaces( $user );
}
$profile = null;
return;
}
# If there's an exact or very near match, jump right there.
- $title = SearchEngine::getNearMatch( $term );
+ $title = $this->newSearchEngine()->
+ getNearMatcher( $this->getConfig() )->getNearMatch( $term );
if ( !is_null( $title ) &&
Hooks::run( 'SpecialSearchGoResult', [ $term, $title, &$url ] )
*/
protected function powerSearch( &$request ) {
$arr = [];
- foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) {
+ foreach ( $this->searchConfig->searchableNamespaces() as $ns => $name ) {
if ( $request->getCheck( 'ns' . $ns ) ) {
$arr[] = $ns;
}
// Groups namespaces into rows according to subject
$rows = [];
- foreach ( SearchEngine::searchableNamespaces() as $namespace => $name ) {
+ foreach ( $this->searchConfig->searchableNamespaces() as $namespace => $name ) {
$subject = MWNamespace::getSubject( $namespace );
if ( !array_key_exists( $subject, $rows ) ) {
$rows[$subject] = "";
*/
protected function getSearchProfiles() {
// Builds list of Search Types (profiles)
- $nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
-
+ $nsAllSet = array_keys( $this->searchConfig->searchableNamespaces() );
+ $defaultNs = $this->searchConfig->defaultNamespaces();
$profiles = [
'default' => [
'message' => 'searchprofile-articles',
'tooltip' => 'searchprofile-articles-tooltip',
- 'namespaces' => SearchEngine::defaultNamespaces(),
- 'namespace-messages' => SearchEngine::namespacesAsText(
- SearchEngine::defaultNamespaces()
+ 'namespaces' => $defaultNs,
+ 'namespace-messages' => $this->searchConfig->namespacesAsText(
+ $defaultNs
),
],
'images' => [
public function getSearchEngine() {
if ( $this->searchEngine === null ) {
$this->searchEngine = $this->searchEngineType ?
- SearchEngine::create( $this->searchEngineType ) : SearchEngine::create();
+ MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $this->searchEngineType ) :
+ MediaWikiServices::getInstance()->newSearchEngine();
}
return $this->searchEngine;
* @file
*/
+use MediaWiki\MediaWikiServices;
use MediaWiki\Session\SessionManager;
use MediaWiki\Session\Token;
foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
$defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
}
- foreach ( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
+ $namespaces = MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
+ foreach ( $namespaces as $nsnum => $nsname ) {
$defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
}
$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
'SiteLookup' => [ 'getSiteLookup', SiteLookup::class ],
'StatsdDataFactory' => [ 'getStatsdDataFactory', StatsdDataFactory::class ],
'EventRelayerGroup' => [ 'getEventRelayerGroup', EventRelayerGroup::class ],
+ 'SearchEngine' => [ 'newSearchEngine', SearchEngine::class ],
+ 'SearchEngineFactory' => [ 'getSearchEngineFactory', SearchEngineFactory::class ],
+ 'SearchEngineConfig' => [ 'getSearchEngineConfig', SearchEngineConfig::class ],
];
}
'SiteLookup' => [ 'SiteLookup', SiteLookup::class ],
'StatsdDataFactory' => [ 'StatsdDataFactory', StatsdDataFactory::class ],
'EventRelayerGroup' => [ 'EventRelayerGroup', EventRelayerGroup::class ],
+ 'SearchEngineFactory' => [ 'SearchEngineFactory', SearchEngineFactory::class ],
+ 'SearchEngineConfig' => [ 'SearchEngineConfig', SearchEngineConfig::class ],
];
}
*/
class SearchUpdateTest extends MediaWikiTestCase {
+ /**
+ * @var SearchUpdate
+ */
+ private $su;
+
protected function setUp() {
parent::setUp();
$this->setMwGlobals( 'wgSearchType', 'MockSearch' );
+ $this->su = new SearchUpdate( 0, "" );
}
public function updateText( $text ) {
- return trim( SearchUpdate::updateText( $text ) );
+ return trim( $this->su->updateText( $text ) );
}
/**
<?php
+use MediaWiki\MediaWikiServices;
+
/**
* @group Search
* @group Database
// Avoid special pages from extensions interferring with the tests
$this->setMwGlobals( 'wgSpecialPages', [] );
- $this->search = SearchEngine::create();
+ $this->search = MediaWikiServices::getInstance()->newSearchEngine();
$this->search->setNamespaces( [] );
}
<?php
+use MediaWiki\MediaWikiServices;
+
/**
* Test class for SpecialSearch class
* Copyright © 2012, Antoine Musso
* @author Antoine Musso
* @group Database
*/
-
class SpecialSearchTest extends MediaWikiTestCase {
/**
}
public static function provideSearchOptionsTests() {
- $defaultNS = SearchEngine::defaultNamespaces();
+ $defaultNS = MediaWikiServices::getInstance()->getSearchEngineConfig()->defaultNamespaces();
$EMPTY_REQUEST = [];
$NO_USER_PREF = null;