Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
5.17% |
9 / 174 |
|
21.43% |
3 / 14 |
CRAP | |
0.00% |
0 / 1 |
QuickSearchLookup | |
5.17% |
9 / 174 |
|
21.43% |
3 / 14 |
2096.37 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMain | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
setInstance | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRequest | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
msg | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setFirstResult | |
55.56% |
5 / 9 |
|
0.00% |
0 / 1 |
9.16 | |||
setTitle | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
needsFirstResult | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
outputLookup | |
1.11% |
1 / 90 |
|
0.00% |
0 / 1 |
87.33 | |||
getPageMeta | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
20 | |||
getTextExtract | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
getPageImage | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getPageCoord | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
72 | |||
buildOSMParams | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | |
3 | use MediaWiki\MediaWikiServices; |
4 | |
5 | class QuickSearchLookup { |
6 | private static $instance = null; |
7 | |
8 | /** @var Title|null the Title object for this QuickSearchLookup object */ |
9 | private $title; |
10 | |
11 | /** @var RequestContext Main RequestContext object */ |
12 | private $context; |
13 | |
14 | /** @var array Page metadata */ |
15 | private $metadata; |
16 | |
17 | /** |
18 | * Constructor |
19 | * |
20 | * @param RequestContext $context |
21 | */ |
22 | public function __construct( IContextSource $context ) { |
23 | $this->context = $context; |
24 | } |
25 | |
26 | /** |
27 | * Singleton function |
28 | * |
29 | * @return QuickSearchLookup |
30 | */ |
31 | public static function getMain() { |
32 | if ( !isset( self::$instance ) ) { |
33 | self::$instance = new self( RequestContext::getMain() ); |
34 | } |
35 | return self::$instance; |
36 | } |
37 | |
38 | /** |
39 | * Set a main instance. |
40 | * @param QuickSearchLookup|null $instance |
41 | */ |
42 | public static function setInstance( $instance ) { |
43 | self::$instance = $instance; |
44 | } |
45 | |
46 | /** |
47 | * Helper function, returns RequestContext::getRequest |
48 | * |
49 | * @return WebRequest |
50 | */ |
51 | private function getRequest() { |
52 | return $this->context->getRequest(); |
53 | } |
54 | |
55 | /** |
56 | * Helper function, same as RequestContext::msg() |
57 | * |
58 | * @param string $key |
59 | * @return Message |
60 | */ |
61 | private function msg( $key ) { |
62 | return $this->context->msg( $key ); |
63 | } |
64 | |
65 | /** |
66 | * The given title will be used as the Title in this QuickSearchLookup object. |
67 | * |
68 | * @param string|Title $titleTerm The Title object of the frist search result, |
69 | * or a search term user as a first Title |
70 | * |
71 | * @return bool |
72 | */ |
73 | public function setFirstResult( $titleTerm ) { |
74 | if ( $titleTerm instanceof Title && $titleTerm->exists() ) { |
75 | $this->setTitle( $titleTerm ); |
76 | return true; |
77 | } elseif ( is_string( $titleTerm ) ) { |
78 | // check, if the term is the exact name of a title in this wiki |
79 | $title = Title::newFromText( $titleTerm ); |
80 | if ( $title && $title->exists() ) { |
81 | $this->setTitle( $title ); |
82 | return true; |
83 | } |
84 | } |
85 | return false; |
86 | } |
87 | |
88 | /** |
89 | * Does various checks and set's the given title. |
90 | * |
91 | * @param Title $title |
92 | */ |
93 | private function setTitle( Title $title ) { |
94 | // check for redirects |
95 | if ( $title->isRedirect() ) { |
96 | // get the new target (the redirect target) |
97 | if ( method_exists( MediaWikiServices::class, 'getWikiPageFactory' ) ) { |
98 | // MW 1.36+ |
99 | $page = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title ); |
100 | } else { |
101 | $page = WikiPage::factory( $title ); |
102 | } |
103 | if ( !$page->exists() ) { |
104 | return; |
105 | } |
106 | $target = $page->getRedirectTarget(); |
107 | } else { |
108 | $target = $title; |
109 | } |
110 | $this->title = $target; |
111 | } |
112 | |
113 | /** |
114 | * Checks, if the first title is already set |
115 | * |
116 | * @return bool |
117 | */ |
118 | public function needsFirstResult() { |
119 | return !isset( $this->title ); |
120 | } |
121 | |
122 | /** |
123 | * Adds the QuickSearchLookup panel element to the given OutputPage object |
124 | * |
125 | * @param OutputPage $out |
126 | */ |
127 | public function outputLookup( OutputPage $out ) { |
128 | global $wgLang; |
129 | |
130 | // only add the panel, if the given title exist to avoid |
131 | // an empty panel |
132 | if ( $this->title && $this->title->exists() ) { |
133 | // the panel is build with OOUI, enable it |
134 | $out->enableOOUI(); |
135 | $title = $this->title->getText(); |
136 | $elements = []; |
137 | $out->addModuleStyles( [ 'ext.QuickSearchLookup' ] ); |
138 | |
139 | // get the info about the Page images added to the firs title |
140 | $imageInfo = $this->getPageImage( $title ); |
141 | |
142 | // If there is a page image, add it in the correct orientation |
143 | if ( $imageInfo ) { |
144 | // get the orientation for this image |
145 | $orientation = ( $imageInfo['thumb']['width'] < $imageInfo['thumb']['height'] ? 'upright' : 'cross' ); |
146 | $imageTag = new OOUI\Tag( 'img' ); |
147 | $imageTag->setAttributes( [ |
148 | 'src' => $imageInfo['thumb']['source'], |
149 | 'class' => 'mw-search-quicklookup-image mw-search-quicklookup-image-' . $orientation, |
150 | ] ); |
151 | $imageLink = Title::newFromText( $imageInfo['pageimage'], NS_FILE ); |
152 | $linkTag = new OOUI\Tag( 'a' ); |
153 | $linkTag |
154 | ->setAttributes( [ |
155 | 'href' => $imageLink->getLocalURL(), |
156 | 'class' => 'image', |
157 | ] ) |
158 | ->appendContent( $imageTag ); |
159 | $elements[] = $linkTag; |
160 | } |
161 | |
162 | // try to get some text from the page |
163 | $text = $this->getTextExtract( $title ); |
164 | if ( $text ) { |
165 | // the layout for the text, with an additional css class to add a margin for |
166 | // the ButtonWidget |
167 | $layout = new OOUI\Layout(); |
168 | $layout |
169 | ->appendContent( $text ) |
170 | ->addClasses( [ |
171 | 'mw-search-quicklookup-text', |
172 | // this class adds space between the text and the read more button (which is positioned |
173 | // aboslute) and will be removed if the expand map button is present |
174 | 'mw-search-quicklookup-textmargin' |
175 | ] ); |
176 | |
177 | // if there are page coordinates, add an OSM map |
178 | $coord = $this->getPageCoord( $title ); |
179 | if ( $coord ) { |
180 | // add the JavaScript module to expand the map |
181 | $out->addModules( [ 'ext.QuickSearchLookup.script' ] ); |
182 | |
183 | // add the params to the url params list |
184 | $urlParamsArray = [ |
185 | 'params' => $this->buildOSMParams( $coord ), |
186 | 'title' => $title, |
187 | 'lang' => MediaWikiServices::getInstance()->getContentLanguage()->getCode(), |
188 | 'uselang' => $wgLang->getCode(), |
189 | ]; |
190 | // convert array to url encoded list |
191 | $urlParams = wfArrayToCgi( $urlParamsArray ); |
192 | // built the complete URL |
193 | $iframeLink = wfAppendQuery( "//tools.wmflabs.org/wiwosm/osm-on-ol/kml-on-ol.php", $urlParams ); |
194 | |
195 | // create a new iframe tag to add OSM map under the text snippet |
196 | $iframe = new OOUI\Tag( 'iframe' ); |
197 | $iframe->setAttributes( [ |
198 | 'id' => 'openstreetmap', |
199 | 'class' => 'mw-search-quicklookup-osm', |
200 | 'src' => $iframeLink, |
201 | 'width' => '100%', |
202 | 'height' => '100%', |
203 | ] ); |
204 | // the expand button allows a user to make the map bigger without clicking on permalink |
205 | $expandButton = new OOUI\ButtonWidget( [ |
206 | 'label' => $this->msg( 'quicksearchlookup-expand' )->text(), |
207 | ] ); |
208 | $expandButton->addClasses( [ |
209 | 'mw-search-quicklookup-expand', |
210 | // the button is hidden by default and will be visible if JS is enabled |
211 | 'hidden' |
212 | ] ); |
213 | // add OSM map to the layout |
214 | $layout |
215 | ->appendContent( $iframe ); |
216 | } |
217 | |
218 | $elements[] = $layout; |
219 | } |
220 | |
221 | // if there are elements, add them to the output in a PanelLayout |
222 | if ( $elements ) { |
223 | // build a ButtonWidget, with a custom class to position is absolute |
224 | $button = new OOUI\ButtonWidget( [ |
225 | 'label' => $this->msg( 'quicksearchlookup-readmore' )->text(), |
226 | 'href' => $this->title->getLocalUrl(), |
227 | ] ); |
228 | $button->addClasses( [ |
229 | 'mw-search-quicklookup-readmore' |
230 | ] ); |
231 | |
232 | // if there is an OSM map, show an "Expand" button at the right sode |
233 | if ( isset( $expandButton ) ) { |
234 | $elements[] = $expandButton; |
235 | } |
236 | |
237 | // then add the read more button |
238 | $elements[] = new OOUI\FieldLayout( $button ); |
239 | |
240 | $panel = new OOUI\PanelLayout( [ |
241 | 'expanded' => false, |
242 | 'padded' => true, |
243 | 'framed' => true, |
244 | ] ); |
245 | |
246 | $panel->appendContent( |
247 | new OOUI\FieldsetLayout( [ |
248 | 'label' => $title, |
249 | 'items' => $elements, |
250 | ] ) |
251 | ); |
252 | $out->addHtml( Html::rawElement( |
253 | 'div', |
254 | [ |
255 | 'class' => 'mw-search-quicklookup', |
256 | ], |
257 | $panel |
258 | ) |
259 | ); |
260 | } |
261 | } |
262 | } |
263 | |
264 | /** |
265 | * If not already done, performs an internal Api request to get |
266 | * page data like page images and a short text snippet. |
267 | * |
268 | * @param string $title The title to lookup |
269 | * @return array The page meta data |
270 | */ |
271 | protected function getPageMeta( $title ) { |
272 | if ( !$this->metadata ) { |
273 | $params = new DerivativeRequest( |
274 | $this->getRequest(), |
275 | [ |
276 | 'action' => 'query', |
277 | 'prop' => 'extracts|pageimages|coordinates', |
278 | 'pithumbsize' => 800, |
279 | 'exchars' => 450, |
280 | 'explaintext' => true, |
281 | 'exintro' => true, |
282 | 'coprop' => 'type|name|dim|country|region', |
283 | 'titles' => $title, |
284 | ], |
285 | true |
286 | ); |
287 | $api = new ApiMain( $params ); |
288 | $api->execute(); |
289 | $data = $api->getResult()->getResultData(); |
290 | foreach ( $data['query']['pages'] as $id => $page ) { |
291 | if ( isset( $page['pageid'] ) ) { |
292 | $this->metadata = $page; |
293 | } |
294 | } |
295 | } |
296 | return $this->metadata; |
297 | } |
298 | |
299 | /** |
300 | * Get the TextExtract specific data from page meta data, |
301 | * if any, otherwise an empty string. |
302 | * |
303 | * @param string $title The title to lookup |
304 | * @return string |
305 | */ |
306 | private function getTextExtract( $title ) { |
307 | // try to get text from TextExtracts |
308 | $page = $this->getPageMeta( $title ); |
309 | if ( $page && isset( $page['extract']['*'] ) ) { |
310 | return $page['extract']['*']; |
311 | } |
312 | |
313 | return ''; |
314 | } |
315 | |
316 | /** |
317 | * Get the PageImages specific data from page meta data, |
318 | * if any, otherwise false. |
319 | * |
320 | * @param string $title The title to lookup |
321 | * @return array|bool |
322 | */ |
323 | private function getPageImage( $title ) { |
324 | // try to get a page image |
325 | $page = $this->getPageMeta( $title ); |
326 | if ( $page && isset( $page['thumbnail'] ) ) { |
327 | $data = [ 'thumb' => $page['thumbnail'] ]; |
328 | $data['pageimage'] = $page['pageimage']; |
329 | return $data; |
330 | } |
331 | |
332 | return false; |
333 | } |
334 | |
335 | /** |
336 | * Extracts any GeoData related information from the API respond. |
337 | * |
338 | * @param string $title The title to lookup |
339 | * @return array|bool |
340 | */ |
341 | private function getPageCoord( $title ) { |
342 | $page = $this->getPageMeta( $title ); |
343 | // check, if there are coordinates for this title and if they are on earth |
344 | if ( |
345 | $page && |
346 | isset( $page['coordinates'] ) |
347 | ) { |
348 | if ( isset( $page['coordinates'][0]['globe'] ) && $page['coordinates'][0]['globe'] !== "earth" ) { |
349 | return false; |
350 | } |
351 | $info = $page['coordinates'][0]; |
352 | return [ |
353 | 'lat' => $info['lat'], |
354 | 'lon' => $info['lon'], |
355 | 'region' => isset( $info['region'] ) ? $info['region'] : null, |
356 | 'type' => isset( $info['type'] ) ? $info['type'] : null, |
357 | 'dim' => isset( $info['dim'] ) ? $info['dim'] : null, |
358 | ]; |
359 | } |
360 | |
361 | return false; |
362 | } |
363 | |
364 | /** |
365 | * Helper to generate a list of parameters for the "params" url parameter. |
366 | * |
367 | * @param array $data Data to check and add in key/value format |
368 | * @return string |
369 | */ |
370 | private function buildOSMParams( array $data ) { |
371 | $res = ''; |
372 | // build the params for the URL |
373 | if ( $data['lat'] < 0 ) { |
374 | $res .= $data['lat'] . '_S_'; |
375 | } else { |
376 | $res .= $data['lat'] . '_N_'; |
377 | } |
378 | |
379 | if ( $data['long'] < 0 ) { |
380 | $res .= $data['lon'] . '_W_'; |
381 | } else { |
382 | $res .= $data['lon'] . '_E_'; |
383 | } |
384 | unset( $data['lat'], $data['lon'] ); |
385 | foreach ( $data as $type => $info ) { |
386 | if ( $info ) { |
387 | $res .= '_' . $type . ':' . $info; |
388 | } |
389 | } |
390 | return $res; |
391 | } |
392 | } |