diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2015-06-04 07:31:04 +0200 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2015-06-04 07:58:39 +0200 |
commit | f6d65e533c62f6deb21342d4901ece24497b433e (patch) | |
tree | f28adf0362d14bcd448f7b65a7aaf38650f923aa /includes/PrefixSearch.php | |
parent | c27b2e832fe25651ef2410fae85b41072aae7519 (diff) |
Update to MediaWiki 1.25.1
Diffstat (limited to 'includes/PrefixSearch.php')
-rw-r--r-- | includes/PrefixSearch.php | 161 |
1 files changed, 141 insertions, 20 deletions
diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index 718750f5..55a4f49b 100644 --- a/includes/PrefixSearch.php +++ b/includes/PrefixSearch.php @@ -34,11 +34,12 @@ abstract class PrefixSearch { * @param string $search * @param int $limit * @param array $namespaces Used if query is not explicitly prefixed + * @param int $offset How many results to offset from the beginning * @return array Array of strings */ - public static function titleSearch( $search, $limit, $namespaces = array() ) { + public static function titleSearch( $search, $limit, $namespaces = array(), $offset = 0 ) { $prefixSearch = new StringPrefixSearch; - return $prefixSearch->search( $search, $limit, $namespaces ); + return $prefixSearch->search( $search, $limit, $namespaces, $offset ); } /** @@ -47,9 +48,10 @@ abstract class PrefixSearch { * @param string $search * @param int $limit * @param array $namespaces Used if query is not explicitly prefixed + * @param int $offset How many results to offset from the beginning * @return array Array of strings or Title objects */ - public function search( $search, $limit, $namespaces = array() ) { + public function search( $search, $limit, $namespaces = array(), $offset = 0 ) { $search = trim( $search ); if ( $search == '' ) { return array(); // Return empty result @@ -60,11 +62,12 @@ abstract class PrefixSearch { $title = Title::newFromText( $search ); if ( $title && !$title->isExternal() ) { $ns = array( $title->getNamespace() ); + $search = $title->getText(); if ( $ns[0] == NS_MAIN ) { $ns = $namespaces; // no explicit prefix, use default namespaces + Hooks::run( 'PrefixSearchExtractNamespace', array( &$ns, &$search ) ); } - return $this->searchBackend( - $ns, $title->getText(), $limit ); + return $this->searchBackend( $ns, $search, $limit, $offset ); } // Is this a namespace prefix? @@ -75,9 +78,11 @@ abstract class PrefixSearch { { $namespaces = array( $title->getNamespace() ); $search = ''; + } else { + Hooks::run( 'PrefixSearchExtractNamespace', array( &$namespaces, &$search ) ); } - return $this->searchBackend( $namespaces, $search, $limit ); + return $this->searchBackend( $namespaces, $search, $limit, $offset ); } /** @@ -85,12 +90,12 @@ abstract class PrefixSearch { * @param string $search * @param int $limit * @param array $namespaces + * @param int $offset How many results to offset from the beginning * * @return array */ - public function searchWithVariants( $search, $limit, array $namespaces ) { - wfProfileIn( __METHOD__ ); - $searches = $this->search( $search, $limit, $namespaces ); + public function searchWithVariants( $search, $limit, array $namespaces, $offset = 0 ) { + $searches = $this->search( $search, $limit, $namespaces, $offset ); // if the content language has variants, try to retrieve fallback results $fallbackLimit = $limit - count( $searches ); @@ -110,7 +115,6 @@ abstract class PrefixSearch { } } } - wfProfileOut( __METHOD__ ); return $searches; } @@ -138,22 +142,128 @@ abstract class PrefixSearch { * @param array $namespaces * @param string $search * @param int $limit + * @param int $offset How many results to offset from the beginning * @return array Array of strings */ - protected function searchBackend( $namespaces, $search, $limit ) { + protected function searchBackend( $namespaces, $search, $limit, $offset ) { if ( count( $namespaces ) == 1 ) { $ns = $namespaces[0]; if ( $ns == NS_MEDIA ) { $namespaces = array( NS_FILE ); } elseif ( $ns == NS_SPECIAL ) { - return $this->titles( $this->specialSearch( $search, $limit ) ); + return $this->titles( $this->specialSearch( $search, $limit, $offset ) ); } } $srchres = array(); - if ( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres ) ) ) { - return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit ) ); + if ( Hooks::run( + 'PrefixSearchBackend', + array( $namespaces, $search, $limit, &$srchres, $offset ) + ) ) { + return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit, $offset ) ); } - return $this->strings( $srchres ); + return $this->strings( $this->handleResultFromHook( $srchres, $namespaces, $search, $limit ) ); + } + + /** + * Default search backend does proper prefix searching, but custom backends + * may sort based on other algorythms that may cause the exact title match + * to not be in the results or be lower down the list. + * @param array $srchres results from the hook + * @return array munged results from the hook + */ + private function handleResultFromHook( $srchres, $namespaces, $search, $limit ) { + // Pick namespace (based on PrefixSearch::defaultSearchBackend) + $ns = in_array( NS_MAIN, $namespaces ) ? NS_MAIN : $namespaces[0]; + $t = Title::newFromText( $search, $ns ); + if ( !$t || !$t->exists() ) { + // No exact match so just return the search results + return $srchres; + } + $string = $t->getPrefixedText(); + $key = array_search( $string, $srchres ); + if ( $key !== false ) { + // Exact match was in the results so just move it to the front + return $this->pullFront( $key, $srchres ); + } + // Exact match not in the search results so check for some redirect handling cases + if ( $t->isRedirect() ) { + $target = $this->getRedirectTarget( $t ); + $key = array_search( $target, $srchres ); + if ( $key !== false ) { + // Exact match is a redirect to one of the returned matches so pull the + // returned match to the front. This might look odd but the alternative + // is to put the redirect in front and drop the match. The name of the + // found match is often more descriptive/better formed than the name of + // the redirect AND by definition they share a prefix. Hopefully this + // choice is less confusing and more helpful. But it might not be. But + // it is the choice we're going with for now. + return $this->pullFront( $key, $srchres ); + } + $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres ); + if ( isset( $redirectTargetsToRedirect[$target] ) ) { + // The exact match and something in the results list are both redirects + // to the same thing! In this case we'll pull the returned match to the + // top following the same logic above. Again, it might not be a perfect + // choice but it'll do. + return $this->pullFront( $redirectTargetsToRedirect[$target], $srchres ); + } + } else { + $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres ); + if ( isset( $redirectTargetsToRedirect[$string] ) ) { + // The exact match is the target of a redirect already in the results list so remove + // the redirect from the results list and push the exact match to the front + array_splice( $srchres, $redirectTargetsToRedirect[$string], 1 ); + array_unshift( $srchres, $string ); + return $srchres; + } + } + + // Exact match is totally unique from the other results so just add it to the front + array_unshift( $srchres, $string ); + // And roll one off the end if the results are too long + if ( count( $srchres ) > $limit ) { + array_pop( $srchres ); + } + return $srchres; + } + + /** + * @param Array(string) $titles as strings + * @return Array(string => int) redirect target prefixedText to index of title in titles + * that is a redirect to it. + */ + private function redirectTargetsToRedirect( $titles ) { + $result = array(); + foreach ( $titles as $key => $titleText ) { + $title = Title::newFromText( $titleText ); + if ( !$title || !$title->isRedirect() ) { + continue; + } + $target = $this->getRedirectTarget( $title ); + if ( !$target ) { + continue; + } + $result[$target] = $key; + } + return $result; + } + + /** + * @param int $key key to pull to the front + * @return array $array with the item at $key pulled to the front + */ + private function pullFront( $key, $array ) { + $cut = array_splice( $array, $key, 1 ); + array_unshift( $array, $cut[0] ); + return $array; + } + + private function getRedirectTarget( $title ) { + $page = WikiPage::factory( $title ); + if ( !$page->exists() ) { + return null; + } + return $page->getRedirectTarget()->getPrefixedText(); } /** @@ -161,9 +271,10 @@ abstract class PrefixSearch { * * @param string $search Term * @param int $limit Max number of items to return + * @param int $offset Number of items to offset * @return array */ - protected function specialSearch( $search, $limit ) { + protected function specialSearch( $search, $limit, $offset ) { global $wgContLang; $searchParts = explode( '/', $search, 2 ); @@ -179,7 +290,7 @@ abstract class PrefixSearch { } $special = SpecialPageFactory::getPage( $specialTitle->getText() ); if ( $special ) { - $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit ); + $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit, $offset ); return array_map( function ( $sub ) use ( $specialTitle ) { return $specialTitle->getSubpage( $sub ); }, $subpages ); @@ -195,7 +306,7 @@ abstract class PrefixSearch { // Unlike SpecialPage itself, we want the canonical forms of both // canonical and alias title forms... $keys = array(); - foreach ( SpecialPageFactory::getNames() as $page ) { + foreach ( SpecialPageFactory::getNames() as $page ) { $keys[$wgContLang->caseFold( $page )] = $page; } @@ -211,12 +322,17 @@ abstract class PrefixSearch { ksort( $keys ); $srchres = array(); + $skipped = 0; foreach ( $keys as $pageKey => $page ) { if ( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) { // bug 27671: Don't use SpecialPage::getTitleFor() here because it // localizes its input leading to searches for e.g. Special:All // returning Spezial:MediaWiki-Systemnachrichten and returning // Spezial:Alle_Seiten twice when $wgLanguageCode == 'de' + if ( $offset > 0 && $skipped < $offset ) { + $skipped++; + continue; + } $srchres[] = Title::makeTitleSafe( NS_SPECIAL, $page ); } @@ -237,9 +353,10 @@ abstract class PrefixSearch { * @param array $namespaces Namespaces to search in * @param string $search Term * @param int $limit Max number of items to return + * @param int $offset Number of items to skip * @return array Array of Title objects */ - protected function defaultSearchBackend( $namespaces, $search, $limit ) { + protected function defaultSearchBackend( $namespaces, $search, $limit, $offset ) { $ns = array_shift( $namespaces ); // support only one namespace if ( in_array( NS_MAIN, $namespaces ) ) { $ns = NS_MAIN; // if searching on many always default to main @@ -255,7 +372,11 @@ abstract class PrefixSearch { 'page_title ' . $dbr->buildLike( $prefix, $dbr->anyString() ) ), __METHOD__, - array( 'LIMIT' => $limit, 'ORDER BY' => 'page_title' ) + array( + 'LIMIT' => $limit, + 'ORDER BY' => 'page_title', + 'OFFSET' => $offset + ) ); $srchres = array(); foreach ( $res as $row ) { |