Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 131
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialSpecialPages
0.00% covered (danger)
0.00%
0 / 130
0.00% covered (danger)
0.00%
0 / 4
870
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 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 getPageGroups
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 outputPageList
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 1
420
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Specials;
8
9use MediaWiki\Html\Html;
10use MediaWiki\SpecialPage\UnlistedSpecialPage;
11use MediaWiki\Title\Title;
12use OOUI\FieldLayout;
13use OOUI\SearchInputWidget;
14use Wikimedia\Parsoid\Core\SectionMetadata;
15use Wikimedia\Parsoid\Core\TOCData;
16
17/**
18 * A special page that lists special pages
19 *
20 * @ingroup SpecialPage
21 */
22class SpecialSpecialPages extends UnlistedSpecialPage {
23
24    public function __construct() {
25        parent::__construct( 'Specialpages' );
26    }
27
28    /** @inheritDoc */
29    public function execute( $par ) {
30        $out = $this->getOutput();
31        $this->setHeaders();
32        $this->outputHeader();
33        $out->getMetadata()->setPreventClickjacking( false );
34        $out->addModuleStyles( 'mediawiki.special' );
35
36        $groups = $this->getPageGroups();
37
38        if ( $groups === false ) {
39            return;
40        }
41
42        $this->addHelpLink( 'Help:Special pages' );
43        $this->outputPageList( $groups );
44    }
45
46    /** @return array[][]|false */
47    private function getPageGroups() {
48        $pages = $this->getSpecialPageFactory()->getUsablePages( $this->getUser(), $this->getContext() );
49
50        if ( $pages === [] ) {
51            // Yeah, that was pointless. Thanks for coming.
52            return false;
53        }
54
55        // Put them into a sortable array
56        $groups = [];
57        foreach ( $pages as $page ) {
58            $group = $page->getFinalGroupName();
59            $desc = $page->getDescription();
60            // (T360723) Only show an entry if the message isn't blanked, to allow on-wiki unlisting
61            if ( !$desc->isDisabled() ) {
62                $groups[$group][$desc->text()] = [
63                    $page->getPageTitle(),
64                    $page->isRestricted(),
65                    $page->isCached()
66                ];
67            }
68        }
69
70        // Sort
71        foreach ( $groups as $group => $sortedPages ) {
72            ksort( $groups[$group] );
73        }
74
75        // Always move "other" to end
76        if ( array_key_exists( 'other', $groups ) ) {
77            $other = $groups['other'];
78            unset( $groups['other'] );
79            $groups['other'] = $other;
80        }
81
82        return $groups;
83    }
84
85    private function outputPageList( array $groups ) {
86        $out = $this->getOutput();
87        $aliases = $this->getSpecialPageFactory()->getAliasList();
88        $out->addModules( 'mediawiki.special.specialpages' );
89        $out->enableOOUI();
90
91        // Legend
92        $includesRestrictedPages = false;
93        $includesCachedPages = false;
94        foreach ( $groups as $group => $sortedPages ) {
95            foreach ( $sortedPages as $desc => [ $title, $restricted, $cached ] ) {
96                if ( $cached ) {
97                    $includesCachedPages = true;
98                }
99                if ( $restricted ) {
100                    $includesRestrictedPages = true;
101                }
102            }
103        }
104
105        $notes = [];
106        if ( $includesRestrictedPages ) {
107            $restricedMsg = $this->msg( 'specialpages-note-restricted' );
108            if ( !$restricedMsg->isDisabled() ) {
109                $notes[] = $restricedMsg->parse();
110            }
111        }
112        if ( $includesCachedPages ) {
113            $cachedMsg = $this->msg( 'specialpages-note-cached' );
114            if ( !$cachedMsg->isDisabled() ) {
115                $notes[] = $cachedMsg->parse();
116            }
117        }
118        if ( $notes !== [] ) {
119            $legendHeading = $this->msg( 'specialpages-note-top' )->parse();
120
121            $legend = Html::rawElement(
122                'div',
123                [ 'class' => [ 'mw-changeslist-legend', 'mw-specialpages-notes' ] ],
124                $legendHeading . implode( "\n", $notes )
125            );
126
127            $out->addHTML( $legend );
128            $out->addModuleStyles( 'mediawiki.special.changeslist.legend' );
129        }
130
131        $out->addHTML( ( new FieldLayout(
132            new SearchInputWidget( [
133                'placeholder' => $this->msg( 'specialpages-header-search' )->text(),
134            ] ),
135            [
136                'classes' => [ 'mw-special-pages-search' ],
137                'label' => $this->msg( 'specialpages-header-search' )->text(),
138                'invisibleLabel' => true,
139                'infusable' => true,
140            ]
141        ) )->toString() );
142
143        // Format table of contents
144        $tocData = new TOCData();
145        $tocLength = 0;
146        foreach ( $groups as $group => $sortedPages ) {
147            if ( !str_contains( $group, '/' ) ) {
148                ++$tocLength;
149                $tocData->addSection( new SectionMetadata(
150                    1,
151                    2,
152                    $this->msg( "specialpages-group-$group" )->escaped(),
153                    $this->getLanguage()->formatNum( $tocLength ),
154                    (string)$tocLength,
155                    null,
156                    null,
157                    "mw-specialpagesgroup-$group",
158                    "mw-specialpagesgroup-$group"
159                ) );
160            }
161        }
162
163        $out->addTOCPlaceholder( $tocData );
164
165        // Format contents
166        $language = $this->getLanguage();
167        foreach ( $groups as $group => $sortedPages ) {
168            if ( str_contains( $group, '/' ) ) {
169                [ $group, $subGroup ] = explode( '/', $group, 2 );
170                $out->addHTML( Html::element(
171                    'h3',
172                    [ 'class' => "mw-specialpagessubgroup" ],
173                    $this->msg( "specialpages-group-$group-$subGroup" )->text()
174                ) . "\n" );
175            } else {
176                $out->addHTML( Html::element(
177                    'h2',
178                    [ 'class' => "mw-specialpagesgroup", 'id' => "mw-specialpagesgroup-$group" ],
179                    $this->msg( "specialpages-group-$group" )->text()
180                ) . "\n" );
181            }
182            $out->addHTML(
183                Html::openElement( 'div', [ 'class' => 'mw-specialpages-list' ] )
184                . '<ul>'
185            );
186            foreach ( $sortedPages as $desc => [ $title, $restricted, $cached ] ) {
187                $indexAttr = [ 'data-search-index-0' => $language->lc( $title->getText() ) ];
188                $c = 1;
189                foreach ( $aliases as $alias => $target ) {
190                    /** @var Title $title */
191                    if (
192                        $target == $title->getText() &&
193                        $language->lc( $alias ) !== $language->lc( $title->getText() )
194                    ) {
195                        $indexAttr['data-search-index-' . $c ] = $language->lc( $alias );
196                        ++$c;
197                    }
198                }
199                $pageClasses = [];
200                if ( $cached ) {
201                    $pageClasses[] = 'mw-specialpagecached';
202                }
203                if ( $restricted ) {
204                    $pageClasses[] = 'mw-specialpagerestricted';
205                }
206
207                $link = $this->getLinkRenderer()->makeKnownLink( $title, $desc );
208                $out->addHTML( Html::rawElement(
209                        'li',
210                        $indexAttr + [ 'class' => $pageClasses ],
211                        $link
212                    ) . "\n" );
213            }
214            $out->addHTML(
215                Html::closeElement( 'ul' ) .
216                Html::closeElement( 'div' )
217            );
218        }
219    }
220}
221
222/**
223 * Retain the old class name for backwards compatibility.
224 * @deprecated since 1.41
225 */
226class_alias( SpecialSpecialPages::class, 'SpecialSpecialpages' );