Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 173 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
SpecialGlobalUsage | |
0.00% |
0 / 173 |
|
0.00% |
0 / 8 |
812 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
showForm | |
0.00% |
0 / 73 |
|
0.00% |
0 / 1 |
20 | |||
showResult | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
42 | |||
formatItem | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
getNavBar | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
30 | |||
prefixSearchSubpages | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Special page to show global file usage. Also contains hook functions for |
4 | * showing usage on an image page. |
5 | */ |
6 | |
7 | namespace MediaWiki\Extension\GlobalUsage; |
8 | |
9 | use MediaWiki\Html\Html; |
10 | use MediaWiki\Linker\Linker; |
11 | use MediaWiki\MediaWikiServices; |
12 | use MediaWiki\Navigation\PagerNavigationBuilder; |
13 | use MediaWiki\SpecialPage\SpecialPage; |
14 | use MediaWiki\Title\Title; |
15 | use MediaWiki\WikiMap\WikiMap; |
16 | use OOUI\ButtonInputWidget; |
17 | use OOUI\CheckboxInputWidget; |
18 | use OOUI\FieldLayout; |
19 | use OOUI\FieldsetLayout; |
20 | use OOUI\FormLayout; |
21 | use OOUI\HtmlSnippet; |
22 | use OOUI\PanelLayout; |
23 | use OOUI\TextInputWidget; |
24 | |
25 | class SpecialGlobalUsage extends SpecialPage { |
26 | /** |
27 | * @var Title |
28 | */ |
29 | protected $target; |
30 | |
31 | /** |
32 | * @var bool |
33 | */ |
34 | protected $filterLocal; |
35 | |
36 | public function __construct() { |
37 | parent::__construct( 'GlobalUsage' ); |
38 | } |
39 | |
40 | /** |
41 | * Entry point |
42 | * @param string $par |
43 | */ |
44 | public function execute( $par ) { |
45 | $target = $par ?: $this->getRequest()->getVal( 'target' ); |
46 | $this->target = Title::makeTitleSafe( NS_FILE, $target ); |
47 | |
48 | $this->filterLocal = $this->getRequest()->getCheck( 'filterlocal' ); |
49 | |
50 | $this->setHeaders(); |
51 | $this->getOutput()->addWikiMsg( 'globalusage-header' ); |
52 | if ( $this->target !== null ) { |
53 | $this->getOutput()->addWikiMsg( 'globalusage-header-image', $this->target->getText() ); |
54 | } |
55 | $this->showForm(); |
56 | |
57 | if ( $this->target === null ) { |
58 | $this->getOutput()->setPageTitleMsg( $this->msg( 'globalusage' ) ); |
59 | return; |
60 | } |
61 | |
62 | $this->getOutput()->setPageTitleMsg( |
63 | $this->msg( 'globalusage-for', $this->target->getPrefixedText() ) ); |
64 | |
65 | $this->showResult(); |
66 | } |
67 | |
68 | /** |
69 | * Shows the search form |
70 | */ |
71 | private function showForm() { |
72 | global $wgScript; |
73 | |
74 | $this->getOutput()->enableOOUI(); |
75 | /* Build form */ |
76 | $form = new FormLayout( [ |
77 | 'method' => 'get', |
78 | 'action' => $wgScript, |
79 | ] ); |
80 | |
81 | $fields = []; |
82 | $fields[] = new FieldLayout( |
83 | new TextInputWidget( [ |
84 | 'name' => 'target', |
85 | 'id' => 'target', |
86 | 'autosize' => true, |
87 | 'infusable' => true, |
88 | 'value' => $this->target === null ? '' : $this->target->getText(), |
89 | ] ), |
90 | [ |
91 | 'label' => $this->msg( 'globalusage-filename' )->text(), |
92 | 'align' => 'top', |
93 | ] |
94 | ); |
95 | |
96 | // Filter local checkbox |
97 | $fields[] = new FieldLayout( |
98 | new CheckboxInputWidget( [ |
99 | 'name' => 'filterlocal', |
100 | 'id' => 'mw-filterlocal', |
101 | 'value' => '1', |
102 | 'selected' => $this->filterLocal, |
103 | ] ), |
104 | [ |
105 | 'align' => 'inline', |
106 | 'label' => $this->msg( 'globalusage-filterlocal' )->text(), |
107 | ] |
108 | ); |
109 | |
110 | // Submit button |
111 | $fields[] = new FieldLayout( |
112 | new ButtonInputWidget( [ |
113 | 'value' => $this->msg( 'globalusage-ok' )->text(), |
114 | 'label' => $this->msg( 'globalusage-ok' )->text(), |
115 | 'flags' => [ 'primary', 'progressive' ], |
116 | 'type' => 'submit', |
117 | ] ), |
118 | [ |
119 | 'align' => 'top', |
120 | ] |
121 | ); |
122 | |
123 | $fieldset = new FieldsetLayout( [ |
124 | 'label' => $this->msg( 'globalusage-text' )->text(), |
125 | 'id' => 'globalusage-text', |
126 | 'items' => $fields, |
127 | ] ); |
128 | |
129 | $form->appendContent( |
130 | $fieldset, |
131 | new HtmlSnippet( |
132 | Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) . |
133 | Html::hidden( 'limit', $this->getRequest()->getInt( 'limit', 50 ) ) |
134 | ) |
135 | ); |
136 | |
137 | $this->getOutput()->addHTML( |
138 | new PanelLayout( [ |
139 | 'expanded' => false, |
140 | 'padded' => true, |
141 | 'framed' => true, |
142 | 'content' => $form, |
143 | ] ) |
144 | ); |
145 | |
146 | if ( $this->target !== null ) { |
147 | $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $this->target ); |
148 | if ( $file ) { |
149 | // Show the image if it exists |
150 | $html = Linker::makeThumbLinkObj( |
151 | $this->target, |
152 | $file, |
153 | /* $label */ $this->target->getPrefixedText(), |
154 | /* $alt */ '', /* $align */ $this->getLanguage()->alignEnd() |
155 | ); |
156 | $this->getOutput()->addHtml( $html ); |
157 | } |
158 | } |
159 | } |
160 | |
161 | /** |
162 | * Creates as queryer and executes it based on $this->getRequest() |
163 | */ |
164 | private function showResult() { |
165 | $query = new GlobalUsageQuery( $this->target ); |
166 | $request = $this->getRequest(); |
167 | |
168 | // Extract params from $request. |
169 | if ( $request->getText( 'from' ) ) { |
170 | $query->setOffset( $request->getText( 'from' ) ); |
171 | } elseif ( $request->getText( 'to' ) ) { |
172 | $query->setOffset( $request->getText( 'to' ), true ); |
173 | } |
174 | $query->setLimit( $request->getInt( 'limit', 50 ) ); |
175 | $query->filterLocal( $this->filterLocal ); |
176 | |
177 | // Perform query |
178 | $query->execute(); |
179 | |
180 | // Don't show form element if there is no data |
181 | if ( $query->count() == 0 ) { |
182 | $this->getOutput()->addWikiMsg( 'globalusage-no-results', $this->target->getPrefixedText() ); |
183 | return; |
184 | } |
185 | |
186 | $navbar = $this->getNavBar( $query ); |
187 | $targetName = $this->target->getText(); |
188 | $out = $this->getOutput(); |
189 | |
190 | // Top navbar |
191 | $out->addHtml( $navbar ); |
192 | |
193 | $out->addHtml( '<div id="mw-globalusage-result">' ); |
194 | foreach ( $query->getSingleImageResult() as $wiki => $result ) { |
195 | $out->addHtml( |
196 | '<h2>' . $this->msg( |
197 | 'globalusage-on-wiki', |
198 | $targetName, WikiMap::getWikiName( $wiki ) )->parse() |
199 | . "</h2><ul>\n" ); |
200 | foreach ( $result as $item ) { |
201 | $out->addHtml( "\t<li>" . self::formatItem( $item ) . "</li>\n" ); |
202 | } |
203 | $out->addHtml( "</ul>\n" ); |
204 | } |
205 | $out->addHtml( '</div>' ); |
206 | |
207 | // Bottom navbar |
208 | $out->addHtml( $navbar ); |
209 | } |
210 | |
211 | /** |
212 | * Helper to format a specific item |
213 | * @param array $item |
214 | * @return string |
215 | */ |
216 | public static function formatItem( $item ) { |
217 | if ( !$item['namespace'] ) { |
218 | $page = $item['title']; |
219 | } else { |
220 | $page = "{$item['namespace']}:{$item['title']}"; |
221 | } |
222 | |
223 | $link = WikiMap::makeForeignLink( |
224 | $item['wiki'], $page, |
225 | str_replace( '_', ' ', $page ) |
226 | ); |
227 | // Return only the title if no link can be constructed |
228 | return $link === false ? htmlspecialchars( $page ) : $link; |
229 | } |
230 | |
231 | /** |
232 | * Helper function to create the navbar |
233 | * |
234 | * @param GlobalUsageQuery $query An executed GlobalUsageQuery object |
235 | * @return string Navbar HTML |
236 | */ |
237 | protected function getNavBar( $query ) { |
238 | $target = $this->target->getText(); |
239 | $limit = $query->getLimit(); |
240 | |
241 | // Find out which strings are for the prev and which for the next links |
242 | $offset = $query->getOffsetString(); |
243 | $continue = $query->getContinueString(); |
244 | if ( $query->isReversed() ) { |
245 | $from = $offset; |
246 | $to = $continue; |
247 | } else { |
248 | $from = $continue; |
249 | $to = $offset; |
250 | } |
251 | |
252 | // Fetch the title object |
253 | $title = $this->getPageTitle(); |
254 | |
255 | $navBuilder = new PagerNavigationBuilder( $this ); |
256 | $navBuilder |
257 | ->setPage( $title ) |
258 | ->setPrevTooltipMsg( 'prevn-title' ) |
259 | ->setNextTooltipMsg( 'nextn-title' ) |
260 | ->setLimitTooltipMsg( 'shown-title' ); |
261 | |
262 | // Default query for all links, including nulls to ensure consistent order of parameters. |
263 | // 'from'/'to' parameters are overridden for the 'previous'/'next' links below. |
264 | $q = [ |
265 | 'target' => $target, |
266 | 'filterlocal' => null, |
267 | 'from' => $to, |
268 | 'to' => null, |
269 | 'limit' => (string)$limit, |
270 | ]; |
271 | if ( $this->filterLocal ) { |
272 | $q['filterlocal'] = '1'; |
273 | } |
274 | $navBuilder->setLinkQuery( $q ); |
275 | |
276 | // Make 'previous' link |
277 | if ( $to ) { |
278 | $q = [ 'from' => null, 'to' => $to ]; |
279 | $navBuilder->setPrevLinkQuery( $q ); |
280 | } |
281 | // Make 'next' link |
282 | if ( $from ) { |
283 | $q = [ 'from' => $from, 'to' => null ]; |
284 | $navBuilder->setNextLinkQuery( $q ); |
285 | } |
286 | // Make links to set number of items per page |
287 | $navBuilder |
288 | ->setLimitLinkQueryParam( 'limit' ) |
289 | ->setCurrentLimit( $limit ); |
290 | |
291 | return $navBuilder->getHtml(); |
292 | } |
293 | |
294 | /** |
295 | * Return an array of subpages beginning with $search that this special page will accept. |
296 | * |
297 | * @param string $search Prefix to search for |
298 | * @param int $limit Maximum number of results to return (usually 10) |
299 | * @param int $offset Number of results to skip (usually 0) |
300 | * @return string[] Matching subpages |
301 | */ |
302 | public function prefixSearchSubpages( $search, $limit, $offset ) { |
303 | if ( !GlobalUsage::onSharedRepo() ) { |
304 | // Local files on non-shared wikis are not useful as suggestion |
305 | return []; |
306 | } |
307 | $title = Title::newFromText( $search, NS_FILE ); |
308 | if ( !$title || $title->getNamespace() !== NS_FILE ) { |
309 | // No prefix suggestion outside of file namespace |
310 | return []; |
311 | } |
312 | $searchEngine = MediaWikiServices::getInstance()->getSearchEngineFactory()->create(); |
313 | $searchEngine->setLimitOffset( $limit, $offset ); |
314 | // Autocomplete subpage the same as a normal search, but just for (local) files |
315 | $searchEngine->setNamespaces( [ NS_FILE ] ); |
316 | $result = $searchEngine->defaultPrefixSearch( $search ); |
317 | |
318 | return array_map( static function ( Title $t ) { |
319 | // Remove namespace in search suggestion |
320 | return $t->getText(); |
321 | }, $result ); |
322 | } |
323 | |
324 | protected function getGroupName() { |
325 | return 'media'; |
326 | } |
327 | } |