X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FPrefixSearch.php;h=2885679c4706cd046622e3ba3863ecc8c1712e83;hb=4d78d40d821227a9368e87306f447ffd3be5db88;hp=5df0ce715b980af63975454cddf1ad8e3cf5a34f;hpb=201928033d59d5f5bccadac48bc4a10c27a0384a;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index 5df0ce715b..2885679c47 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,10 +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? @@ -74,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 ); } /** @@ -84,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 ); @@ -109,7 +115,6 @@ abstract class PrefixSearch { } } } - wfProfileOut( __METHOD__ ); return $searches; } @@ -137,48 +142,125 @@ 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( $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 { - // 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. - - // Pick namespace (based on PrefixSearch::defaultSearchBackend) - $ns = in_array( NS_MAIN, $namespaces ) ? NS_MAIN : $namespaces[0]; - $t = Title::newFromText( $search, $ns ); - if ( $t ) { - // If text is a valid title and is in the search results - $string = $t->getPrefixedText(); - $key = array_search( $string, $srchres ); - if ( $key !== false ) { - // Move it to the front - $cut = array_splice( $srchres, $key, 1 ); - array_unshift( $srchres, $cut[0] ); - } elseif ( $t->exists() ) { - // Add it in front - array_unshift( $srchres, $string ); - - if ( count( $srchres ) > $limit ) { - array_pop( $srchres ); - } - } + $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; + } - return $this->strings( $srchres ); + private function getRedirectTarget( $title ) { + $page = WikiPage::factory( $title ); + if ( !$page->exists() ) { + return null; + } + return $page->getRedirectTarget()->getPrefixedText(); } /** @@ -186,9 +268,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 ); @@ -204,7 +287,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 ); @@ -236,12 +319,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 ); } @@ -262,9 +350,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 @@ -280,7 +369,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 ) {