Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 269
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 / 269
0.00% covered (danger)
0.00%
0 / 7
3782
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
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 / 236
0.00% covered (danger)
0.00%
0 / 1
2550
 showExportForm
0.00% covered (danger)
0.00%
0 / 24
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 ContentHandler;
24use HTMLForm;
25use InvalidArgumentException;
26use MediaWiki\Extension\Gadgets\Gadget;
27use MediaWiki\Extension\Gadgets\GadgetRepo;
28use MediaWiki\Html\Html;
29use MediaWiki\MediaWikiServices;
30use MediaWiki\Parser\Sanitizer;
31use MediaWiki\SpecialPage\SpecialPage;
32use MediaWiki\Title\Title;
33
34/**
35 * Special:Gadgets renders the data of MediaWiki:Gadgets-definition.
36 *
37 * @copyright 2007 Daniel Kinzler
38 */
39class 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 = '&#160;&#160;' .
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}