Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 269 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
SpecialGadgets | |
0.00% |
0 / 269 |
|
0.00% |
0 / 7 |
3782 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getDescription | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 | |||
makeAnchor | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
showMainForm | |
0.00% |
0 / 236 |
|
0.00% |
0 / 1 |
2550 | |||
showExportForm | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
12 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Extension\Gadgets\Special; |
22 | |
23 | use ContentHandler; |
24 | use HTMLForm; |
25 | use InvalidArgumentException; |
26 | use MediaWiki\Extension\Gadgets\Gadget; |
27 | use MediaWiki\Extension\Gadgets\GadgetRepo; |
28 | use MediaWiki\Html\Html; |
29 | use MediaWiki\MediaWikiServices; |
30 | use MediaWiki\Parser\Sanitizer; |
31 | use MediaWiki\SpecialPage\SpecialPage; |
32 | use MediaWiki\Title\Title; |
33 | |
34 | /** |
35 | * Special:Gadgets renders the data of MediaWiki:Gadgets-definition. |
36 | * |
37 | * @copyright 2007 Daniel Kinzler |
38 | */ |
39 | class SpecialGadgets extends SpecialPage { |
40 | private GadgetRepo $gadgetRepo; |
41 | |
42 | public function __construct( GadgetRepo $gadgetRepo ) { |
43 | parent::__construct( 'Gadgets' ); |
44 | $this->gadgetRepo = $gadgetRepo; |
45 | } |
46 | |
47 | /** |
48 | * Return title for Special:Gadgets heading and Special:Specialpages link. |
49 | * |
50 | * @return \Message |
51 | */ |
52 | public function getDescription() { |
53 | return $this->msg( 'special-gadgets' ); |
54 | } |
55 | |
56 | /** |
57 | * @param string|null $par Parameters passed to the page |
58 | */ |
59 | public function execute( $par ) { |
60 | $parts = $par !== null ? explode( '/', $par ) : []; |
61 | |
62 | if ( count( $parts ) === 2 && $parts[0] === 'export' ) { |
63 | $this->showExportForm( $parts[1] ); |
64 | } else { |
65 | $this->showMainForm(); |
66 | } |
67 | } |
68 | |
69 | /** |
70 | * @param string $gadgetName |
71 | * @return string |
72 | */ |
73 | private function makeAnchor( $gadgetName ) { |
74 | return 'gadget-' . Sanitizer::escapeIdForAttribute( $gadgetName ); |
75 | } |
76 | |
77 | /** |
78 | * Displays form showing the list of installed gadgets |
79 | */ |
80 | public function showMainForm() { |
81 | $output = $this->getOutput(); |
82 | $this->setHeaders(); |
83 | $this->addHelpLink( 'Extension:Gadgets' ); |
84 | $output->setPageTitleMsg( $this->msg( 'gadgets-title' ) ); |
85 | $output->addWikiMsg( 'gadgets-pagetext' ); |
86 | |
87 | $gadgets = $this->gadgetRepo->getStructuredList(); |
88 | if ( !$gadgets ) { |
89 | return; |
90 | } |
91 | |
92 | $services = MediaWikiServices::getInstance(); |
93 | |
94 | $output->disallowUserJs(); |
95 | $lang = $this->getLanguage(); |
96 | $langSuffix = ""; |
97 | if ( !$lang->equals( $services->getContentLanguage() ) ) { |
98 | $langSuffix = "/" . $lang->getCode(); |
99 | } |
100 | |
101 | $listOpen = false; |
102 | |
103 | $editDefinitionMessage = $this->getUser()->isAllowed( 'editsitejs' ) |
104 | ? 'edit' |
105 | : 'viewsource'; |
106 | $editInterfaceMessage = $this->getUser()->isAllowed( 'editinterface' ) |
107 | ? 'gadgets-editdescription' |
108 | : 'gadgets-viewdescription'; |
109 | |
110 | $linkRenderer = $this->getLinkRenderer(); |
111 | $skinFactory = $services->getSkinFactory(); |
112 | foreach ( $gadgets as $section => $entries ) { |
113 | if ( $section !== false && $section !== '' ) { |
114 | if ( $listOpen ) { |
115 | $output->addHTML( Html::closeElement( 'ul' ) . "\n" ); |
116 | $listOpen = false; |
117 | } |
118 | |
119 | // H2 section heading |
120 | $headingText = Html::rawElement( |
121 | 'span', |
122 | [ 'class' => 'mw-headline' ], |
123 | $this->msg( "gadget-section-$section" )->parse() |
124 | ); |
125 | $title = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-section-$section$langSuffix" ); |
126 | $leftBracket = Html::rawElement( |
127 | 'span', |
128 | [ 'class' => 'mw-editsection-bracket' ], |
129 | '[' |
130 | ); |
131 | $linkTarget = $title |
132 | ? $linkRenderer->makeLink( $title, $this->msg( $editInterfaceMessage )->text(), |
133 | [], [ 'action' => 'edit' ] ) |
134 | : htmlspecialchars( $section ); |
135 | $rightBracket = Html::rawElement( |
136 | 'span', |
137 | [ 'class' => 'mw-editsection-bracket' ], |
138 | ']' |
139 | ); |
140 | $editDescriptionLink = Html::rawElement( |
141 | 'span', |
142 | [ 'class' => 'mw-editsection' ], |
143 | $leftBracket . $linkTarget . $rightBracket |
144 | ); |
145 | $output->addHTML( Html::rawElement( 'h2', [], $headingText . $editDescriptionLink ) . "\n" ); |
146 | } |
147 | |
148 | /** |
149 | * @var Gadget $gadget |
150 | */ |
151 | foreach ( $entries as $gadget ) { |
152 | $name = $gadget->getName(); |
153 | $title = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-{$name}$langSuffix" ); |
154 | if ( !$title ) { |
155 | continue; |
156 | } |
157 | |
158 | $links = []; |
159 | $definitionTitle = $this->gadgetRepo->getGadgetDefinitionTitle( $name ); |
160 | if ( $definitionTitle ) { |
161 | $links[] = $linkRenderer->makeLink( |
162 | $definitionTitle, |
163 | $this->msg( $editDefinitionMessage )->text(), |
164 | [], |
165 | [ 'action' => 'edit' ] |
166 | ); |
167 | } |
168 | $links[] = $linkRenderer->makeLink( |
169 | $title, |
170 | $this->msg( $editInterfaceMessage )->text(), |
171 | [], |
172 | [ 'action' => 'edit' ] |
173 | ); |
174 | $links[] = $linkRenderer->makeLink( |
175 | $this->getPageTitle( "export/{$name}" ), |
176 | $this->msg( 'gadgets-export' )->text() |
177 | ); |
178 | |
179 | $nameHtml = $this->msg( "gadget-{$name}" )->parse(); |
180 | |
181 | if ( !$listOpen ) { |
182 | $listOpen = true; |
183 | $output->addHTML( Html::openElement( 'ul' ) ); |
184 | } |
185 | |
186 | $actionsHtml = '  ' . |
187 | $this->msg( 'parentheses' )->rawParams( $lang->pipeList( $links ) )->escaped(); |
188 | $output->addHTML( |
189 | Html::openElement( 'li', [ 'id' => $this->makeAnchor( $name ) ] ) . |
190 | $nameHtml . $actionsHtml |
191 | ); |
192 | // Whether the next portion of the list item contents needs |
193 | // a line break between it and the next portion. |
194 | // This is set to false after lists, but true after lines of text. |
195 | $needLineBreakAfter = true; |
196 | |
197 | // Portion: Show files, dependencies, speers |
198 | if ( $needLineBreakAfter ) { |
199 | $output->addHTML( '<br />' ); |
200 | } |
201 | $output->addHTML( |
202 | $this->msg( 'gadgets-uses' )->escaped() . |
203 | $this->msg( 'colon-separator' )->escaped() |
204 | ); |
205 | $links = []; |
206 | foreach ( $gadget->getPeers() as $peer ) { |
207 | $links[] = Html::element( |
208 | 'a', |
209 | [ 'href' => '#' . $this->makeAnchor( $peer ) ], |
210 | $peer |
211 | ); |
212 | } |
213 | foreach ( $gadget->getScriptsAndStyles() as $codePage ) { |
214 | $title = Title::newFromText( $codePage ); |
215 | if ( !$title ) { |
216 | continue; |
217 | } |
218 | $links[] = $linkRenderer->makeLink( $title, $title->getText() ); |
219 | } |
220 | $output->addHTML( $lang->commaList( $links ) ); |
221 | |
222 | if ( $gadget->isPackaged() ) { |
223 | if ( $needLineBreakAfter ) { |
224 | $output->addHTML( '<br />' ); |
225 | } |
226 | $output->addHTML( $this->msg( 'gadgets-packaged', |
227 | $this->gadgetRepo->titleWithoutPrefix( $gadget->getScripts()[0], $gadget->getName() ) ) ); |
228 | $needLineBreakAfter = true; |
229 | } |
230 | |
231 | // Portion: Legacy scripts |
232 | if ( $gadget->getLegacyScripts() ) { |
233 | if ( $needLineBreakAfter ) { |
234 | $output->addHTML( '<br />' ); |
235 | } |
236 | $output->addHTML( Html::errorBox( |
237 | $this->msg( 'gadgets-legacy' )->parse(), |
238 | '', |
239 | 'mw-gadget-legacy' |
240 | ) ); |
241 | $needLineBreakAfter = false; |
242 | } |
243 | |
244 | if ( $gadget->requiresES6() ) { |
245 | if ( $needLineBreakAfter ) { |
246 | $output->addHTML( '<br />' ); |
247 | } |
248 | $output->addHTML( |
249 | $this->msg( 'gadgets-requires-es6' )->parse() |
250 | ); |
251 | $needLineBreakAfter = true; |
252 | } |
253 | |
254 | // Portion: Show required rights (optional) |
255 | $rights = []; |
256 | foreach ( $gadget->getRequiredRights() as $right ) { |
257 | $rights[] = Html::element( |
258 | 'code', |
259 | [ 'title' => $this->msg( "right-$right" )->plain() ], |
260 | $right |
261 | ); |
262 | } |
263 | if ( $rights ) { |
264 | if ( $needLineBreakAfter ) { |
265 | $output->addHTML( '<br />' ); |
266 | } |
267 | $output->addHTML( |
268 | $this->msg( 'gadgets-required-rights', $lang->commaList( $rights ), count( $rights ) )->parse() |
269 | ); |
270 | $needLineBreakAfter = true; |
271 | } |
272 | |
273 | // Portion: Show required skins (optional) |
274 | $requiredSkins = $gadget->getRequiredSkins(); |
275 | $skins = []; |
276 | $validskins = $skinFactory->getSkinNames(); |
277 | foreach ( $requiredSkins as $skinid ) { |
278 | if ( isset( $validskins[$skinid] ) ) { |
279 | $skins[] = $this->msg( "skinname-$skinid" )->plain(); |
280 | } else { |
281 | $skins[] = $skinid; |
282 | } |
283 | } |
284 | if ( $skins ) { |
285 | if ( $needLineBreakAfter ) { |
286 | $output->addHTML( '<br />' ); |
287 | } |
288 | $output->addHTML( |
289 | $this->msg( 'gadgets-required-skins', $lang->commaList( $skins ) ) |
290 | ->numParams( count( $skins ) )->parse() |
291 | ); |
292 | $needLineBreakAfter = true; |
293 | } |
294 | |
295 | // Portion: Show required actions (optional) |
296 | $actions = []; |
297 | foreach ( $gadget->getRequiredActions() as $action ) { |
298 | $actions[] = Html::element( 'code', [], $action ); |
299 | } |
300 | if ( $actions ) { |
301 | if ( $needLineBreakAfter ) { |
302 | $output->addHTML( '<br />' ); |
303 | } |
304 | $output->addHTML( |
305 | $this->msg( 'gadgets-required-actions', $lang->commaList( $actions ) ) |
306 | ->numParams( count( $actions ) )->parse() |
307 | ); |
308 | $needLineBreakAfter = true; |
309 | } |
310 | |
311 | // Portion: Show required namespaces (optional) |
312 | $namespaces = $gadget->getRequiredNamespaces(); |
313 | if ( $namespaces ) { |
314 | if ( $needLineBreakAfter ) { |
315 | $output->addHTML( '<br />' ); |
316 | } |
317 | $output->addHTML( |
318 | $this->msg( |
319 | 'gadgets-required-namespaces', |
320 | $lang->commaList( array_map( function ( int $ns ) use ( $lang ) { |
321 | return $ns == NS_MAIN |
322 | ? $this->msg( 'blanknamespace' )->text() |
323 | : $lang->getFormattedNsText( $ns ); |
324 | }, $namespaces ) ) |
325 | )->numParams( count( $namespaces ) )->parse() |
326 | ); |
327 | $needLineBreakAfter = true; |
328 | } |
329 | |
330 | // Portion: Show required content models (optional) |
331 | $contentModels = []; |
332 | foreach ( $gadget->getRequiredContentModels() as $model ) { |
333 | $contentModels[] = Html::element( |
334 | 'code', |
335 | [ 'title' => ContentHandler::getLocalizedName( $model, $lang ) ], |
336 | $model |
337 | ); |
338 | } |
339 | if ( $contentModels ) { |
340 | if ( $needLineBreakAfter ) { |
341 | $output->addHTML( '<br />' ); |
342 | } |
343 | $output->addHTML( |
344 | $this->msg( 'gadgets-required-contentmodels', |
345 | $lang->commaList( $contentModels ), |
346 | count( $contentModels ) |
347 | )->parse() |
348 | ); |
349 | $needLineBreakAfter = true; |
350 | } |
351 | |
352 | // Portion: Show required categories (optional) |
353 | $categories = []; |
354 | foreach ( $gadget->getRequiredCategories() as $category ) { |
355 | $title = Title::makeTitleSafe( NS_CATEGORY, $category ); |
356 | $categories[] = $title |
357 | ? $linkRenderer->makeLink( $title, $category ) |
358 | : htmlspecialchars( $category ); |
359 | } |
360 | if ( $categories ) { |
361 | if ( $needLineBreakAfter ) { |
362 | $output->addHTML( '<br />' ); |
363 | } |
364 | $output->addHTML( |
365 | $this->msg( 'gadgets-required-categories' ) |
366 | ->rawParams( $lang->commaList( $categories ) ) |
367 | ->numParams( count( $categories ) )->parse() |
368 | ); |
369 | $needLineBreakAfter = true; |
370 | } |
371 | |
372 | if ( $gadget->supportsUrlLoad() ) { |
373 | if ( $needLineBreakAfter ) { |
374 | $output->addHTML( '<br />' ); |
375 | } |
376 | $output->addHTML( $this->msg( 'gadgets-supports-urlload' )->parse() ); |
377 | $needLineBreakAfter = true; |
378 | } |
379 | |
380 | // Portion: Show on by default (optional) |
381 | if ( $gadget->isOnByDefault() ) { |
382 | if ( $needLineBreakAfter ) { |
383 | $output->addHTML( '<br />' ); |
384 | } |
385 | $output->addHTML( $this->msg( 'gadgets-default' )->parse() ); |
386 | $needLineBreakAfter = true; |
387 | } |
388 | |
389 | // Show warnings |
390 | $warnings = GadgetRepo::singleton()->validationWarnings( $gadget ); |
391 | |
392 | if ( count( $warnings ) > 0 ) { |
393 | $output->addHTML( Html::warningBox( implode( '<br/>', array_map( static function ( $msg ) { |
394 | return $msg->parse(); |
395 | }, $warnings ) ) ) ); |
396 | $needLineBreakAfter = false; |
397 | } |
398 | |
399 | $output->addHTML( Html::closeElement( 'li' ) . "\n" ); |
400 | } |
401 | } |
402 | |
403 | if ( $listOpen ) { |
404 | $output->addHTML( Html::closeElement( 'ul' ) . "\n" ); |
405 | } |
406 | } |
407 | |
408 | /** |
409 | * Exports a gadget with its dependencies in a serialized form |
410 | * @param string $gadget Name of gadget to export |
411 | */ |
412 | public function showExportForm( $gadget ) { |
413 | global $wgScript; |
414 | |
415 | $this->addHelpLink( 'Extension:Gadgets' ); |
416 | $output = $this->getOutput(); |
417 | try { |
418 | $g = $this->gadgetRepo->getGadget( $gadget ); |
419 | } catch ( InvalidArgumentException $e ) { |
420 | $output->showErrorPage( 'error', 'gadgets-not-found', [ $gadget ] ); |
421 | return; |
422 | } |
423 | |
424 | $this->setHeaders(); |
425 | $output->setPageTitleMsg( $this->msg( 'gadgets-export-title' ) ); |
426 | $output->addWikiMsg( 'gadgets-export-text', $gadget, $g->getDefinition() ); |
427 | |
428 | $exportList = "MediaWiki:gadget-$gadget\n"; |
429 | foreach ( $g->getScriptsAndStyles() as $page ) { |
430 | $exportList .= "$page\n"; |
431 | } |
432 | |
433 | $htmlForm = HTMLForm::factory( 'ooui', [], $this->getContext() ); |
434 | $htmlForm |
435 | ->setTitle( SpecialPage::getTitleFor( 'Export' ) ) |
436 | ->addHiddenField( 'pages', $exportList ) |
437 | ->addHiddenField( 'wpDownload', '1' ) |
438 | ->addHiddenField( 'templates', '1' ) |
439 | ->setAction( $wgScript ) |
440 | ->setMethod( 'get' ) |
441 | ->setSubmitText( $this->msg( 'gadgets-export-download' )->text() ) |
442 | ->prepareForm() |
443 | ->displayForm( false ); |
444 | } |
445 | |
446 | /** |
447 | * @inheritDoc |
448 | */ |
449 | protected function getGroupName() { |
450 | return 'wiki'; |
451 | } |
452 | } |