Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 227
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ListPage
0.00% covered (danger)
0.00%
0 / 227
0.00% covered (danger)
0.00%
0 / 3
3080
0.00% covered (danger)
0.00%
0 / 1
 __construct
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 / 225
0.00% covered (danger)
0.00%
0 / 1
2862
 makeAnchor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Gadgets\Special;
4
5use MediaWiki\Content\ContentHandler;
6use MediaWiki\Extension\Gadgets\Gadget;
7use MediaWiki\Extension\Gadgets\GadgetRepo;
8use MediaWiki\Html\Html;
9use MediaWiki\Language\Language;
10use MediaWiki\Parser\Sanitizer;
11use MediaWiki\Skin\SkinFactory;
12use MediaWiki\Title\Title;
13
14class ListPage extends ActionPage {
15
16    public function __construct(
17        SpecialGadgets $specialPage,
18        private readonly GadgetRepo $gadgetRepo,
19        private readonly Language $contentLanguage,
20        private readonly SkinFactory $skinFactory,
21    ) {
22        parent::__construct( $specialPage );
23    }
24
25    /**
26     * Displays form showing the list of installed gadgets
27     */
28    public function execute( array $params ) {
29        $output = $this->specialPage->getOutput();
30        $output->setPageTitleMsg( $this->msg( 'gadgets-title' ) );
31        $output->addWikiMsg( 'gadgets-pagetext' );
32
33        $gadgets = $this->gadgetRepo->getStructuredList();
34        if ( !$gadgets ) {
35            return;
36        }
37
38        $output->disallowUserJs();
39        $lang = $this->specialPage->getLanguage();
40        $langSuffix = "";
41        if ( !$lang->equals( $this->contentLanguage ) ) {
42            $langSuffix = "/" . $lang->getCode();
43        }
44
45        $listOpen = false;
46
47        $editDefinitionMessage = $this->specialPage->getUser()->isAllowed( 'editsitejs' )
48            ? 'edit'
49            : 'viewsource';
50        $editInterfaceMessage = $this->specialPage->getUser()->isAllowed( 'editinterface' )
51            ? 'gadgets-editdescription'
52            : 'gadgets-viewdescription';
53        $editInterfaceMessageSection = $this->specialPage->getUser()->isAllowed( 'editinterface' )
54            ? 'gadgets-editsectiontitle'
55            : 'gadgets-viewsectiontitle';
56
57        $linkRenderer = $this->specialPage->getLinkRenderer();
58        foreach ( $gadgets as $section => $entries ) {
59            if ( $section !== false && $section !== '' ) {
60                if ( $listOpen ) {
61                    $output->addHTML( Html::closeElement( 'ul' ) . "\n" );
62                    $listOpen = false;
63                }
64
65                // H2 section heading
66                $headingText = $this->msg( "gadget-section-$section" )->parse();
67                $output->addHTML( Html::rawElement( 'h2', [], $headingText ) . "\n" );
68
69                // Edit link for the section heading
70                $title = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-section-$section$langSuffix" );
71                $linkTarget = $title
72                    ? $linkRenderer->makeLink( $title, $this->msg( $editInterfaceMessageSection )->text(),
73                        [], [ 'action' => 'edit' ] )
74                    : htmlspecialchars( $section );
75                $output->addHTML( Html::rawElement( 'p', [],
76                        $this->msg( 'parentheses' )->rawParams( $linkTarget )->escaped() ) . "\n" );
77            }
78
79            /**
80             * @var Gadget $gadget
81             */
82            foreach ( $entries as $gadget ) {
83                $name = $gadget->getName();
84                $title = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-{$name}$langSuffix" );
85                if ( !$title ) {
86                    continue;
87                }
88
89                $links = [];
90                $definitionTitle = $this->gadgetRepo->getGadgetDefinitionTitle( $name );
91                if ( $definitionTitle ) {
92                    $links[] = $linkRenderer->makeLink(
93                        $definitionTitle,
94                        $this->msg( $editDefinitionMessage )->text(),
95                        [],
96                        [ 'action' => 'edit' ]
97                    );
98                }
99                $links[] = $linkRenderer->makeLink(
100                    $title,
101                    $this->msg( $editInterfaceMessage )->text(),
102                    [],
103                    [ 'action' => 'edit' ]
104                );
105                $links[] = $linkRenderer->makeLink(
106                    $this->specialPage->getPageTitle( "export/{$name}" ),
107                    $this->msg( 'gadgets-export' )->text()
108                );
109
110                $nameHtml = $this->msg( "gadget-{$name}" )->parse();
111
112                if ( !$listOpen ) {
113                    $listOpen = true;
114                    $output->addHTML( Html::openElement( 'ul' ) );
115                }
116
117                $actionsHtml = '&#160;&#160;' .
118                    $this->msg( 'parentheses' )->rawParams( $lang->pipeList( $links ) )->escaped();
119                $output->addHTML(
120                    Html::openElement( 'li', [ 'id' => $this->makeAnchor( $name ) ] ) .
121                    $nameHtml . $actionsHtml
122                );
123                // Whether the next portion of the list item contents needs
124                // a line break between it and the next portion.
125                // This is set to false after lists, but true after lines of text.
126                $needLineBreakAfter = true;
127
128                // Portion: Show files, dependencies, speers
129                if ( $needLineBreakAfter ) {
130                    $output->addHTML( '<br />' );
131                }
132                $output->addHTML(
133                    $this->msg( 'gadgets-uses' )->escaped() .
134                    $this->msg( 'colon-separator' )->escaped()
135                );
136                $links = [];
137                foreach ( $gadget->getPeers() as $peer ) {
138                    $links[] = Html::element(
139                        'a',
140                        [ 'href' => '#' . $this->makeAnchor( $peer ) ],
141                        $peer
142                    );
143                }
144                foreach ( $gadget->getScriptsAndStyles() as $codePage ) {
145                    $title = Title::newFromText( $codePage );
146                    if ( !$title ) {
147                        continue;
148                    }
149                    $links[] = $linkRenderer->makeLink( $title, $title->getText() );
150                }
151                $output->addHTML( $lang->commaList( $links ) );
152
153                if ( $gadget->isPackaged() ) {
154                    if ( $needLineBreakAfter ) {
155                        $output->addHTML( '<br />' );
156                    }
157                    $output->addHTML( $this->msg( 'gadgets-packaged',
158                        $this->gadgetRepo->titleWithoutPrefix( $gadget->getScripts()[0], $gadget->getName() ) ) );
159                    $needLineBreakAfter = true;
160                }
161
162                // Portion: Legacy scripts
163                if ( $gadget->getLegacyScripts() ) {
164                    if ( $needLineBreakAfter ) {
165                        $output->addHTML( '<br />' );
166                    }
167                    $output->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
168                    $output->addHTML( Html::errorBox(
169                        $this->msg( 'gadgets-legacy' )->parse(),
170                        '',
171                        'mw-gadget-legacy'
172                    ) );
173                    $needLineBreakAfter = false;
174                }
175
176                if ( $gadget->requiresES6() ) {
177                    if ( $needLineBreakAfter ) {
178                        $output->addHTML( '<br />' );
179                    }
180                    $output->addHTML(
181                        $this->msg( 'gadgets-requires-es6' )->parse()
182                    );
183                    $needLineBreakAfter = true;
184                }
185
186                // Portion: Show required rights (optional)
187                $rights = [];
188                foreach ( $gadget->getRequiredRights() as $right ) {
189                    $rights[] = Html::element(
190                        'code',
191                        [ 'title' => $this->msg( "right-$right" )->plain() ],
192                        $right
193                    );
194                }
195                if ( $rights ) {
196                    if ( $needLineBreakAfter ) {
197                        $output->addHTML( '<br />' );
198                    }
199                    $output->addHTML(
200                        $this->msg( 'gadgets-required-rights', $lang->commaList( $rights ), count( $rights ) )->parse()
201                    );
202                    $needLineBreakAfter = true;
203                }
204
205                // Portion: Show required skins (optional)
206                $requiredSkins = $gadget->getRequiredSkins();
207                $skins = [];
208                $validskins = $this->skinFactory->getInstalledSkins();
209                foreach ( $requiredSkins as $skinid ) {
210                    if ( isset( $validskins[$skinid] ) ) {
211                        $skins[] = $this->msg( "skinname-$skinid" )->plain();
212                    } else {
213                        $skins[] = $skinid;
214                    }
215                }
216                if ( $skins ) {
217                    if ( $needLineBreakAfter ) {
218                        $output->addHTML( '<br />' );
219                    }
220                    $output->addHTML(
221                        $this->msg( 'gadgets-required-skins', $lang->commaList( $skins ) )
222                            ->numParams( count( $skins ) )->parse()
223                    );
224                    $needLineBreakAfter = true;
225                }
226
227                // Portion: Show required actions (optional)
228                $actions = [];
229                foreach ( $gadget->getRequiredActions() as $action ) {
230                    $actions[] = Html::element( 'code', [], $action );
231                }
232                if ( $actions ) {
233                    if ( $needLineBreakAfter ) {
234                        $output->addHTML( '<br />' );
235                    }
236                    $output->addHTML(
237                        $this->msg( 'gadgets-required-actions', $lang->commaList( $actions ) )
238                            ->numParams( count( $actions ) )->parse()
239                    );
240                    $needLineBreakAfter = true;
241                }
242
243                // Portion: Show required namespaces (optional)
244                $namespaces = $gadget->getRequiredNamespaces();
245                if ( $namespaces ) {
246                    if ( $needLineBreakAfter ) {
247                        $output->addHTML( '<br />' );
248                    }
249                    $output->addHTML(
250                        $this->msg(
251                            'gadgets-required-namespaces',
252                            $lang->commaList( array_map( function ( int $ns ) use ( $lang ) {
253                                return $ns == NS_MAIN
254                                    ? $this->msg( 'blanknamespace' )->text()
255                                    : $lang->getFormattedNsText( $ns );
256                            }, $namespaces ) )
257                        )->numParams( count( $namespaces ) )->parse()
258                    );
259                    $needLineBreakAfter = true;
260                }
261
262                // Portion: Show required content models (optional)
263                $contentModels = [];
264                foreach ( $gadget->getRequiredContentModels() as $model ) {
265                    $contentModels[] = Html::element(
266                        'code',
267                        [ 'title' => ContentHandler::getLocalizedName( $model, $lang ) ],
268                        $model
269                    );
270                }
271                if ( $contentModels ) {
272                    if ( $needLineBreakAfter ) {
273                        $output->addHTML( '<br />' );
274                    }
275                    $output->addHTML(
276                        $this->msg( 'gadgets-required-contentmodels',
277                            $lang->commaList( $contentModels ),
278                            count( $contentModels )
279                        )->parse()
280                    );
281                    $needLineBreakAfter = true;
282                }
283
284                // Portion: Show required categories (optional)
285                $categories = [];
286                foreach ( $gadget->getRequiredCategories() as $category ) {
287                    $title = Title::makeTitleSafe( NS_CATEGORY, $category );
288                    $categories[] = $title
289                        ? $linkRenderer->makeLink( $title, $category )
290                        : htmlspecialchars( $category );
291                }
292                if ( $categories ) {
293                    if ( $needLineBreakAfter ) {
294                        $output->addHTML( '<br />' );
295                    }
296                    $output->addHTML(
297                        $this->msg( 'gadgets-required-categories' )
298                            ->rawParams( $lang->commaList( $categories ) )
299                            ->numParams( count( $categories ) )->parse()
300                    );
301                    $needLineBreakAfter = true;
302                }
303                // Show if hidden
304                if ( $gadget->isHidden() ) {
305                    if ( $needLineBreakAfter ) {
306                        $output->addHTML( '<br />' );
307                    }
308                    $output->addHTML( $this->msg( 'gadgets-hidden' )->parse() );
309                    $needLineBreakAfter = true;
310                }
311
312                // Show if supports URL load
313                if ( $gadget->supportsUrlLoad() ) {
314                    if ( $needLineBreakAfter ) {
315                        $output->addHTML( '<br />' );
316                    }
317                    $output->addHTML( $this->msg( 'gadgets-supports-urlload' )->parse() );
318                    $needLineBreakAfter = true;
319                }
320
321                // Portion: Show on by default (optional)
322                if ( $gadget->isOnByDefault() ) {
323                    if ( $needLineBreakAfter ) {
324                        $output->addHTML( '<br />' );
325                    }
326                    $output->addHTML( $this->msg( 'gadgets-default' )->parse() );
327                    $needLineBreakAfter = true;
328                }
329
330                // Show warnings
331                $warnings = $this->gadgetRepo->validationWarnings( $gadget );
332
333                if ( $warnings ) {
334                    $output->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
335                    $output->addHTML( Html::warningBox( implode( '<br/>', array_map( static function ( $msg ) {
336                        return $msg->parse();
337                    }, $warnings ) ) ) );
338                    $needLineBreakAfter = false;
339                }
340
341                $output->addHTML( Html::closeElement( 'li' ) . "\n" );
342            }
343        }
344
345        if ( $listOpen ) {
346            $output->addHTML( Html::closeElement( 'ul' ) . "\n" );
347        }
348    }
349
350    /**
351     * @param string $gadgetName
352     * @return string
353     */
354    private function makeAnchor( $gadgetName ) {
355        return 'gadget-' . Sanitizer::escapeIdForAttribute( $gadgetName );
356    }
357
358}