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