Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 273
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialGadgets
0.00% covered (danger)
0.00%
0 / 273
0.00% covered (danger)
0.00%
0 / 7
4032
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getDescription
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 makeAnchor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 showMainForm
0.00% covered (danger)
0.00%
0 / 239
0.00% covered (danger)
0.00%
0 / 1
2756
 showExportForm
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
12
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
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
21namespace MediaWiki\Extension\Gadgets\Special;
22
23use InvalidArgumentException;
24use MediaWiki\Content\ContentHandler;
25use MediaWiki\Extension\Gadgets\Gadget;
26use MediaWiki\Extension\Gadgets\GadgetRepo;
27use MediaWiki\Html\Html;
28use MediaWiki\HTMLForm\HTMLForm;
29use MediaWiki\Language\Language;
30use MediaWiki\MainConfigNames;
31use MediaWiki\Message\Message;
32use MediaWiki\Parser\Sanitizer;
33use MediaWiki\SpecialPage\SpecialPage;
34use MediaWiki\Title\Title;
35use SkinFactory;
36
37/**
38 * Special:Gadgets renders the data of MediaWiki:Gadgets-definition.
39 *
40 * @copyright 2007 Daniel Kinzler
41 */
42class 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 = '&#160;&#160;' .
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}