]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/search/SearchExactMatchRescorer.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / includes / search / SearchExactMatchRescorer.php
1 <?php
2 /**
3  * Rescores results from a prefix search/opensearch to make sure the
4  * exact match is the first result.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  * http://www.gnu.org/copyleft/gpl.html
20  *
21  * @file
22  */
23
24 /**
25  * An utility class to rescore search results by looking for an exact match
26  * in the db and add the page found to the first position.
27  *
28  * NOTE: extracted from TitlePrefixSearch
29  * @ingroup Search
30  */
31 class SearchExactMatchRescorer {
32         /**
33          * Default search backend does proper prefix searching, but custom backends
34          * may sort based on other algorithms that may cause the exact title match
35          * to not be in the results or be lower down the list.
36          * @param string $search the query
37          * @param int[] $namespaces the namespaces
38          * @param string[] $srchres results
39          * @param int $limit the max number of results to return
40          * @return string[] munged results
41          */
42         public function rescore( $search, $namespaces, $srchres, $limit ) {
43                 // Pick namespace (based on PrefixSearch::defaultSearchBackend)
44                 $ns = in_array( NS_MAIN, $namespaces ) ? NS_MAIN : reset( $namespaces );
45                 $t = Title::newFromText( $search, $ns );
46                 if ( !$t || !$t->exists() ) {
47                         // No exact match so just return the search results
48                         return $srchres;
49                 }
50                 $string = $t->getPrefixedText();
51                 $key = array_search( $string, $srchres );
52                 if ( $key !== false ) {
53                         // Exact match was in the results so just move it to the front
54                         return $this->pullFront( $key, $srchres );
55                 }
56                 // Exact match not in the search results so check for some redirect handling cases
57                 if ( $t->isRedirect() ) {
58                         $target = $this->getRedirectTarget( $t );
59                         $key = array_search( $target, $srchres );
60                         if ( $key !== false ) {
61                                 // Exact match is a redirect to one of the returned matches so pull the
62                                 // returned match to the front.  This might look odd but the alternative
63                                 // is to put the redirect in front and drop the match.  The name of the
64                                 // found match is often more descriptive/better formed than the name of
65                                 // the redirect AND by definition they share a prefix.  Hopefully this
66                                 // choice is less confusing and more helpful.  But it might not be.  But
67                                 // it is the choice we're going with for now.
68                                 return $this->pullFront( $key, $srchres );
69                         }
70                         $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres );
71                         if ( isset( $redirectTargetsToRedirect[$target] ) ) {
72                                 // The exact match and something in the results list are both redirects
73                                 // to the same thing!  In this case we'll pull the returned match to the
74                                 // top following the same logic above.  Again, it might not be a perfect
75                                 // choice but it'll do.
76                                 return $this->pullFront( $redirectTargetsToRedirect[$target], $srchres );
77                         }
78                 } else {
79                         $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres );
80                         if ( isset( $redirectTargetsToRedirect[$string] ) ) {
81                                 // The exact match is the target of a redirect already in the results list so remove
82                                 // the redirect from the results list and push the exact match to the front
83                                 array_splice( $srchres, $redirectTargetsToRedirect[$string], 1 );
84                                 array_unshift( $srchres, $string );
85                                 return $srchres;
86                         }
87                 }
88
89                 // Exact match is totally unique from the other results so just add it to the front
90                 array_unshift( $srchres, $string );
91                 // And roll one off the end if the results are too long
92                 if ( count( $srchres ) > $limit ) {
93                         array_pop( $srchres );
94                 }
95                 return $srchres;
96         }
97
98         /**
99          * @param string[] $titles as strings
100          * @return array redirect target prefixedText to index of title in titles
101          *   that is a redirect to it.
102          */
103         private function redirectTargetsToRedirect( $titles ) {
104                 $result = [];
105                 foreach ( $titles as $key => $titleText ) {
106                         $title = Title::newFromText( $titleText );
107                         if ( !$title || !$title->isRedirect() ) {
108                                 continue;
109                         }
110                         $target = $this->getRedirectTarget( $title );
111                         if ( !$target ) {
112                                 continue;
113                         }
114                         $result[$target] = $key;
115                 }
116                 return $result;
117         }
118
119         /**
120          * Returns an array where the element of $array at index $key becomes
121          * the first element.
122          * @param int $key key to pull to the front
123          * @return array $array with the item at $key pulled to the front
124          */
125         private function pullFront( $key, $array ) {
126                 $cut = array_splice( $array, $key, 1 );
127                 array_unshift( $array, $cut[0] );
128                 return $array;
129         }
130
131         /**
132          * Get a redirect's destination from a title
133          * @param Title $title A title to redirect. It may not redirect or even exist
134          * @return null|string If title exists and redirects, get the destination's prefixed name
135          */
136         private function getRedirectTarget( $title ) {
137                 $page = WikiPage::factory( $title );
138                 if ( !$page->exists() ) {
139                         return null;
140                 }
141                 $redir = $page->getRedirectTarget();
142                 return $redir ? $redir->getPrefixedText() : null;
143         }
144 }