Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 45 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
NearMatchPicker | |
0.00% |
0 / 45 |
|
0.00% |
0 / 4 |
380 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
pickBest | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
132 | |||
checkAllMatches | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
42 | |||
checkOneMatch | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace CirrusSearch; |
4 | |
5 | use MediaWiki\Language\Language; |
6 | use MediaWiki\Logger\LoggerFactory; |
7 | use MediaWiki\Title\Title; |
8 | |
9 | /** |
10 | * Picks the best "near match" title. |
11 | * |
12 | * This program is free software; you can redistribute it and/or modify |
13 | * it under the terms of the GNU General Public License as published by |
14 | * the Free Software Foundation; either version 2 of the License, or |
15 | * (at your option) any later version. |
16 | * |
17 | * This program is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | * GNU General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU General Public License along |
23 | * with this program; if not, write to the Free Software Foundation, Inc., |
24 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
25 | * http://www.gnu.org/copyleft/gpl.html |
26 | */ |
27 | class NearMatchPicker { |
28 | /** |
29 | * @var Language language to use during normalization process |
30 | */ |
31 | private $language; |
32 | /** |
33 | * @var string the search term |
34 | */ |
35 | private $term; |
36 | /** |
37 | * @var array[] Potential near matches |
38 | */ |
39 | private $titles; |
40 | |
41 | /** |
42 | * @param Language $language to use during normalization process |
43 | * @param string $term the search term |
44 | * @param array[] $titles Array of arrays, each with optional keys: |
45 | * titleMatch => a title if the title matched |
46 | * redirectMatches => an array of redirect matches, one per matched redirect |
47 | */ |
48 | public function __construct( $language, $term, $titles ) { |
49 | $this->language = $language; |
50 | $this->term = $term; |
51 | $this->titles = $titles; |
52 | } |
53 | |
54 | /** |
55 | * Pick the best near match if possible. |
56 | * |
57 | * @return Title|null title if there is a near match and null otherwise |
58 | */ |
59 | public function pickBest() { |
60 | if ( !$this->titles ) { |
61 | return null; |
62 | } |
63 | if ( !$this->term ) { |
64 | return null; |
65 | } |
66 | if ( count( $this->titles ) === 1 ) { |
67 | if ( isset( $this->titles[ 0 ][ 'titleMatch' ] ) ) { |
68 | return $this->titles[ 0 ][ 'titleMatch' ]; |
69 | } |
70 | if ( isset( $this->titles[ 0 ][ 'redirectMatches' ][ 0 ] ) ) { |
71 | return $this->titles[ 0 ][ 'redirectMatches' ][ 0 ]; |
72 | } |
73 | LoggerFactory::getInstance( 'CirrusSearch' )->info( |
74 | 'NearMatchPicker built with busted matches. Assuming no near match' ); |
75 | return null; |
76 | } |
77 | |
78 | $transformers = [ |
79 | static function ( $term ) { |
80 | return $term; |
81 | }, |
82 | [ $this->language, 'lc' ], |
83 | [ $this->language, 'ucwords' ], |
84 | ]; |
85 | |
86 | foreach ( $transformers as $transformer ) { |
87 | $transformedTerm = call_user_func( $transformer, $this->term ); |
88 | $found = null; |
89 | foreach ( $this->titles as $title ) { |
90 | $match = $this->checkAllMatches( $transformer, $transformedTerm, $title ); |
91 | if ( $match ) { |
92 | // @phan-suppress-next-line PhanSuspiciousValueComparisonInLoop |
93 | if ( $found === null ) { |
94 | $found = $match; |
95 | } else { |
96 | // Found more than one result so we try another transformer |
97 | $found = null; |
98 | break; |
99 | } |
100 | } |
101 | |
102 | } |
103 | if ( $found ) { |
104 | return $found; |
105 | } |
106 | } |
107 | |
108 | // Didn't find anything |
109 | return null; |
110 | } |
111 | |
112 | /** |
113 | * Check a single title's worth of matches. The big thing here is that titles cannot compete with themselves. |
114 | * @param callable $transformer |
115 | * @param string $transformedTerm |
116 | * @param array $allMatchedTitles |
117 | * @return null|Title null if no title matches and the actual title (either of the page or of a redirect to the |
118 | * page) if one did match |
119 | */ |
120 | private function checkAllMatches( $transformer, $transformedTerm, $allMatchedTitles ) { |
121 | if ( isset( $allMatchedTitles[ 'titleMatch' ] ) && |
122 | $this->checkOneMatch( $transformer, $transformedTerm, $allMatchedTitles[ 'titleMatch' ] ) ) { |
123 | return $allMatchedTitles[ 'titleMatch' ]; |
124 | } |
125 | if ( isset( $allMatchedTitles[ 'redirectMatches' ] ) ) { |
126 | foreach ( $allMatchedTitles[ 'redirectMatches' ] as $redirectMatch ) { |
127 | if ( $this->checkOneMatch( $transformer, $transformedTerm, $redirectMatch ) ) { |
128 | return $redirectMatch; |
129 | } |
130 | } |
131 | } |
132 | return null; |
133 | } |
134 | |
135 | /** |
136 | * @param callable $transformer |
137 | * @param string $transformedTerm |
138 | * @param Title $matchedTitle |
139 | * @return bool |
140 | */ |
141 | private function checkOneMatch( $transformer, $transformedTerm, $matchedTitle ) { |
142 | $transformedTitle = call_user_func( $transformer, $matchedTitle->getText() ); |
143 | return $transformedTerm === $transformedTitle; |
144 | } |
145 | } |