public $prefix = '';
/** @var int[]|null */
- public $namespaces = array( NS_MAIN );
+ public $namespaces = [ NS_MAIN ];
/** @var int */
protected $limit = 10;
protected $offset = 0;
/** @var array|string */
- protected $searchTerms = array();
+ protected $searchTerms = [];
/** @var bool */
protected $showSuggestion = true;
private $sort = 'relevance';
/** @var array Feature values */
- protected $features = array();
+ protected $features = [];
/**
* Perform a full text search query and return a result set.
- * If title searches are not supported or disabled, return null.
+ * If full text searches are not supported or disabled, return null.
* STUB
*
* @param string $term Raw search term
public static function getNearMatch( $searchterm ) {
$title = self::getNearMatchInternal( $searchterm );
- Hooks::run( 'SearchGetNearMatchComplete', array( $searchterm, &$title ) );
+ Hooks::run( 'SearchGetNearMatchComplete', [ $searchterm, &$title ] );
return $title;
}
private static function getNearMatchInternal( $searchterm ) {
global $wgContLang, $wgEnableSearchContributorsByIP;
- $allSearchTerms = array( $searchterm );
+ $allSearchTerms = [ $searchterm ];
if ( $wgContLang->hasVariants() ) {
$allSearchTerms = array_unique( array_merge(
}
$titleResult = null;
- if ( !Hooks::run( 'SearchGetNearMatchBefore', array( $allSearchTerms, &$titleResult ) ) ) {
+ if ( !Hooks::run( 'SearchGetNearMatchBefore', [ $allSearchTerms, &$titleResult ] ) ) {
return $titleResult;
}
return $title;
}
- if ( !Hooks::run( 'SearchAfterNoDirectMatch', array( $term, &$title ) ) ) {
+ if ( !Hooks::run( 'SearchAfterNoDirectMatch', [ $term, &$title ] ) ) {
return $title;
}
// Give hooks a chance at better match variants
$title = null;
- if ( !Hooks::run( 'SearchGetNearMatch', array( $term, &$title ) ) ) {
+ if ( !Hooks::run( 'SearchGetNearMatch', [ $term, &$title ] ) ) {
return $title;
}
}
}
# Quoted term? Try without the quotes...
- $matches = array();
+ $matches = [];
if ( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
return SearchEngine::getNearMatch( $matches[1] );
}
* @param int[]|null $namespaces
*/
function setNamespaces( $namespaces ) {
+ if ( $namespaces ) {
+ // Filter namespaces to only keep valid ones
+ $validNs = $this->searchableNamespaces();
+ $namespaces = array_filter( $namespaces, function( $ns ) use( $validNs ) {
+ return $ns < 0 || isset( $validNs[$ns] );
+ } );
+ } else {
+ $namespaces = [];
+ }
$this->namespaces = $namespaces;
}
* @return array(string) the valid sort directions for setSort
*/
public function getValidSorts() {
- return array( 'relevance' );
+ return [ 'relevance' ];
}
/**
$prefix = str_replace( ' ', '_', substr( $query, 0, strpos( $query, ':' ) ) );
$index = $wgContLang->getNsIndex( $prefix );
if ( $index !== false ) {
- $this->namespaces = array( $index );
+ $this->namespaces = [ $index ];
$parsed = substr( $query, strlen( $prefix ) + 1 );
}
}
*/
public static function searchableNamespaces() {
global $wgContLang;
- $arr = array();
+ $arr = [];
foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
if ( $ns >= NS_MAIN ) {
$arr[$ns] = $name;
}
}
- Hooks::run( 'SearchableNamespaces', array( &$arr ) );
+ Hooks::run( 'SearchableNamespaces', [ &$arr ] );
return $arr;
}
* @return array
*/
public static function userNamespaces( $user ) {
- $arr = array();
+ $arr = [];
foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) {
if ( $user->getOption( 'searchNs' . $ns ) ) {
$arr[] = $ns;
public static function userHighlightPrefs() {
$contextlines = 2; // Hardcode this. Old defaults sucked. :)
$contextchars = 75; // same as above.... :P
- return array( $contextlines, $contextchars );
+ return [ $contextlines, $contextchars ];
}
/**
public static function namespacesAsText( $namespaces ) {
global $wgContLang;
- $formatted = array_map( array( $wgContLang, 'getFormattedNsText' ), $namespaces );
+ $formatted = array_map( [ $wgContLang, 'getFormattedNsText' ], $namespaces );
foreach ( $formatted as $key => $ns ) {
if ( empty( $ns ) ) {
$formatted[$key] = wfMessage( 'blanknamespace' )->text();
public static function getSearchTypes() {
global $wgSearchType, $wgSearchTypeAlternatives;
- $alternatives = $wgSearchTypeAlternatives ?: array();
+ $alternatives = $wgSearchTypeAlternatives ?: [];
array_unshift( $alternatives, $wgSearchType );
return $alternatives;
public function textAlreadyUpdatedForIndex() {
return false;
}
+
+ /**
+ * Makes search simple string if it was namespaced.
+ * Sets namespaces of the search to namespaces extracted from string.
+ * @param string $search
+ * @return $string Simplified search string
+ */
+ protected function normalizeNamespaces( $search ) {
+ // Find a Title which is not an interwiki and is in NS_MAIN
+ $title = Title::newFromText( $search );
+ $ns = $this->namespaces;
+ if ( $title && !$title->isExternal() ) {
+ $ns = [ $title->getNamespace() ];
+ $search = $title->getText();
+ if ( $ns[0] == NS_MAIN ) {
+ $ns = $this->namespaces; // no explicit prefix, use default namespaces
+ Hooks::run( 'PrefixSearchExtractNamespace', [ &$ns, &$search ] );
+ }
+ } else {
+ $title = Title::newFromText( $search . 'Dummy' );
+ if ( $title && $title->getText() == 'Dummy'
+ && $title->getNamespace() != NS_MAIN
+ && !$title->isExternal() )
+ {
+ $ns = [ $title->getNamespace() ];
+ $search = '';
+ } else {
+ Hooks::run( 'PrefixSearchExtractNamespace', [ &$ns, &$search ] );
+ }
+ }
+
+ $ns = array_map( function( $space ) {
+ return $space == NS_MEDIA ? NS_FILE : $space;
+ }, $ns );
+
+ $this->setNamespaces( $ns );
+ return $search;
+ }
+
+ /**
+ * Perform a completion search.
+ * Does not resolve namespaces and does not check variants.
+ * Search engine implementations may want to override this function.
+ * @param string $search
+ * @return SearchSuggestionSet
+ */
+ protected function completionSearchBackend( $search ) {
+ $results = [];
+
+ $search = trim( $search );
+
+ if ( !in_array( NS_SPECIAL, $this->namespaces ) && // We do not run hook on Special: search
+ !Hooks::run( 'PrefixSearchBackend',
+ [ $this->namespaces, $search, $this->limit, &$results, $this->offset ]
+ ) ) {
+ // False means hook worked.
+ // FIXME: Yes, the API is weird. That's why it is going to be deprecated.
+
+ return SearchSuggestionSet::fromStrings( $results );
+ } else {
+ // Hook did not do the job, use default simple search
+ $results = $this->simplePrefixSearch( $search );
+ return SearchSuggestionSet::fromTitles( $results );
+ }
+ }
+
+ /**
+ * Perform a completion search.
+ * @param string $search
+ * @return SearchSuggestionSet
+ */
+ public function completionSearch( $search ) {
+ if ( trim( $search ) === '' ) {
+ return SearchSuggestionSet::emptySuggestionSet(); // Return empty result
+ }
+ $search = $this->normalizeNamespaces( $search );
+ return $this->processCompletionResults( $search, $this->completionSearchBackend( $search ) );
+ }
+
+ /**
+ * Perform a completion search with variants.
+ * @param string $search
+ * @return SearchSuggestionSet
+ */
+ public function completionSearchWithVariants( $search ) {
+ if ( trim( $search ) === '' ) {
+ return SearchSuggestionSet::emptySuggestionSet(); // Return empty result
+ }
+ $search = $this->normalizeNamespaces( $search );
+
+ $results = $this->completionSearchBackend( $search );
+ $fallbackLimit = $this->limit - $results->getSize();
+ if ( $fallbackLimit > 0 ) {
+ global $wgContLang;
+
+ $fallbackSearches = $wgContLang->autoConvertToAllVariants( $search );
+ $fallbackSearches = array_diff( array_unique( $fallbackSearches ), [ $search ] );
+
+ foreach ( $fallbackSearches as $fbs ) {
+ $this->setLimitOffset( $fallbackLimit );
+ $fallbackSearchResult = $this->completionSearch( $fbs );
+ $results->appendAll( $fallbackSearchResult );
+ $fallbackLimit -= count( $fallbackSearchResult );
+ if ( $fallbackLimit <= 0 ) {
+ break;
+ }
+ }
+ }
+ return $this->processCompletionResults( $search, $results );
+ }
+
+ /**
+ * Extract titles from completion results
+ * @param SearchSuggestionSet $completionResults
+ * @return Title[]
+ */
+ public function extractTitles( SearchSuggestionSet $completionResults ) {
+ return $completionResults->map( function( SearchSuggestion $sugg ) {
+ return $sugg->getSuggestedTitle();
+ } );
+ }
+
+ /**
+ * Process completion search results.
+ * Resolves the titles and rescores.
+ * @param SearchSuggestionSet $suggestions
+ * @return SearchSuggestionSet
+ */
+ protected function processCompletionResults( $search, SearchSuggestionSet $suggestions ) {
+ $search = trim( $search );
+ // preload the titles with LinkBatch
+ $titles = $suggestions->map( function( SearchSuggestion $sugg ) {
+ return $sugg->getSuggestedTitle();
+ } );
+ $lb = new LinkBatch( $titles );
+ $lb->setCaller( __METHOD__ );
+ $lb->execute();
+
+ $results = $suggestions->map( function( SearchSuggestion $sugg ) {
+ return $sugg->getSuggestedTitle()->getPrefixedText();
+ } );
+
+ // Rescore results with an exact title match
+ // NOTE: in some cases like cross-namespace redirects
+ // (frequently used as shortcuts e.g. WP:WP on huwiki) some
+ // backends like Cirrus will return no results. We should still
+ // try an exact title match to workaround this limitation
+ $rescorer = new SearchExactMatchRescorer();
+ $rescoredResults = $rescorer->rescore( $search, $this->namespaces, $results, $this->limit );
+
+ if ( count( $rescoredResults ) > 0 ) {
+ $found = array_search( $rescoredResults[0], $results );
+ if ( $found === false ) {
+ // If the first result is not in the previous array it
+ // means that we found a new exact match
+ $exactMatch = SearchSuggestion::fromTitle( 0, Title::newFromText( $rescoredResults[0] ) );
+ $suggestions->prepend( $exactMatch );
+ $suggestions->shrink( $this->limit );
+ } else {
+ // if the first result is not the same we need to rescore
+ if ( $found > 0 ) {
+ $suggestions->rescore( $found );
+ }
+ }
+ }
+
+ return $suggestions;
+ }
+
+ /**
+ * Simple prefix search for subpages.
+ * @param string $search
+ * @return Title[]
+ */
+ public function defaultPrefixSearch( $search ) {
+ if ( trim( $search ) === '' ) {
+ return [];
+ }
+
+ $search = $this->normalizeNamespaces( $search );
+ return $this->simplePrefixSearch( $search );
+ }
+
+ /**
+ * Call out to simple search backend.
+ * Defaults to TitlePrefixSearch.
+ * @param string $search
+ * @return Title[]
+ */
+ protected function simplePrefixSearch( $search ) {
+ // Use default database prefix search
+ $backend = new TitlePrefixSearch;
+ return $backend->defaultSearchBackend( $this->namespaces, $search, $this->limit, $this->offset );
+ }
+
}
/**