Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 161
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialPrefixIndex
0.00% covered (danger)
0.00%
0 / 160
0.00% covered (danger)
0.00%
0 / 5
1122
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
 execute
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
110
 namespacePrefixForm
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
2
 showPrefixChunk
0.00% covered (danger)
0.00%
0 / 91
0.00% covered (danger)
0.00%
0 / 1
420
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Implements Special:Prefixindex
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup SpecialPage
22 */
23
24namespace MediaWiki\Specials;
25
26use HTMLCheckField;
27use MediaWiki\Cache\LinkCache;
28use MediaWiki\Html\Html;
29use MediaWiki\HTMLForm\HTMLForm;
30use MediaWiki\Title\Title;
31use Wikimedia\Rdbms\IConnectionProvider;
32use Wikimedia\Rdbms\IExpression;
33use Wikimedia\Rdbms\LikeValue;
34
35/**
36 * Implements Special:Prefixindex
37 *
38 * @ingroup SpecialPage
39 */
40class SpecialPrefixIndex extends SpecialAllPages {
41
42    /**
43     * Whether to remove the searched prefix from the displayed link. Useful
44     * for inclusion of a set of subpages in a root page.
45     */
46    protected $stripPrefix = false;
47
48    protected $hideRedirects = false;
49
50    // Inherit $maxPerPage
51
52    // phpcs:ignore MediaWiki.Commenting.PropertyDocumentation.WrongStyle
53    private IConnectionProvider $dbProvider;
54    private LinkCache $linkCache;
55
56    /**
57     * @param IConnectionProvider $dbProvider
58     * @param LinkCache $linkCache
59     */
60    public function __construct(
61        IConnectionProvider $dbProvider,
62        LinkCache $linkCache
63    ) {
64        parent::__construct( $dbProvider );
65        $this->mName = 'Prefixindex';
66        $this->dbProvider = $dbProvider;
67        $this->linkCache = $linkCache;
68    }
69
70    /**
71     * Entry point: initialise variables and call subfunctions.
72     * @param string|null $par Becomes "FOO" when called like Special:Prefixindex/FOO
73     */
74    public function execute( $par ) {
75        $this->setHeaders();
76        $this->outputHeader();
77
78        $out = $this->getOutput();
79        $out->addModuleStyles( 'mediawiki.special' );
80
81        # GET values
82        $request = $this->getRequest();
83        $from = $request->getVal( 'from', '' );
84        $prefix = $request->getVal( 'prefix', '' );
85        $ns = $request->getIntOrNull( 'namespace' );
86        $namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN).
87        $this->hideRedirects = $request->getBool( 'hideredirects', $this->hideRedirects );
88        $this->stripPrefix = $request->getBool( 'stripprefix', $this->stripPrefix );
89
90        $namespaces = $this->getContentLanguage()->getNamespaces();
91        $out->setPageTitleMsg(
92            ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) )
93                ? $this->msg( 'prefixindex-namespace' )->plaintextParams(
94                    str_replace( '_', ' ', $namespaces[$namespace] )
95                )
96                : $this->msg( 'prefixindex' )
97        );
98
99        $showme = '';
100        if ( $par !== null ) {
101            $showme = $par;
102        } elseif ( $prefix != '' ) {
103            $showme = $prefix;
104        } elseif ( $from != '' && $ns === null ) {
105            // For back-compat with Special:Allpages
106            // Don't do this if namespace is passed, so paging works when doing NS views.
107            $showme = $from;
108        }
109
110        // T29864: if transcluded, show all pages instead of the form.
111        if ( $this->including() || $showme != '' || $ns !== null ) {
112            $this->showPrefixChunk( $namespace, $showme, $from );
113        } else {
114            $out->addHTML( $this->namespacePrefixForm( $namespace, '' )->getHTML( false ) );
115        }
116    }
117
118    /**
119     * Prepared HTMLForm object for the top form
120     * @param int $namespace A namespace constant (default NS_MAIN).
121     * @param string $from DbKey we are starting listing at.
122     * @return HTMLForm
123     */
124    protected function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ): HTMLForm {
125        $formDescriptor = [
126            'prefix' => [
127                'label-message' => 'allpagesprefix',
128                'name' => 'prefix',
129                'id' => 'nsfrom',
130                'type' => 'text',
131                'size' => '30',
132                'default' => str_replace( '_', ' ', $from ),
133            ],
134            'namespace' => [
135                'type' => 'namespaceselect',
136                'name' => 'namespace',
137                'id' => 'namespace',
138                'label-message' => 'namespace',
139                'all' => null,
140                'default' => $namespace,
141            ],
142            'hidedirects' => [
143                'class' => HTMLCheckField::class,
144                'name' => 'hideredirects',
145                'label-message' => 'allpages-hide-redirects',
146            ],
147            'stripprefix' => [
148                'class' => HTMLCheckField::class,
149                'name' => 'stripprefix',
150                'label-message' => 'prefixindex-strip',
151            ],
152        ];
153
154        $this->getHookRunner()->onSpecialPrefixIndexGetFormFilters( $this->getContext(), $formDescriptor );
155
156        $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
157            ->setMethod( 'get' )
158            ->setTitle( $this->getPageTitle() ) // Remove subpage
159            ->setWrapperLegendMsg( 'prefixindex' )
160            ->setSubmitTextMsg( 'prefixindex-submit' );
161
162        return $htmlForm->prepareForm();
163    }
164
165    /**
166     * @param int $namespace
167     * @param string $prefix
168     * @param string|null $from List all pages from this name (default false)
169     */
170    protected function showPrefixChunk( $namespace, $prefix, $from = null ) {
171        if ( $from === null ) {
172            $from = $prefix;
173        }
174
175        $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
176        $prefixList = $this->getNamespaceKeyAndText( $namespace, $prefix );
177        $namespaces = $this->getContentLanguage()->getNamespaces();
178        $res = null;
179        $n = 0;
180        $nextRow = null;
181        $preparedHtmlForm = $this->namespacePrefixForm( $namespace, $prefix );
182
183        if ( !$prefixList || !$fromList ) {
184            $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
185        } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
186            // Show errormessage and reset to NS_MAIN
187            $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
188            $namespace = NS_MAIN;
189        } else {
190            [ $namespace, $prefixKey, $prefix ] = $prefixList;
191            [ /* $fromNS */, $fromKey, ] = $fromList;
192
193            # ## @todo FIXME: Should complain if $fromNs != $namespace
194
195            $dbr = $this->dbProvider->getReplicaDatabase();
196            $queryBuiler = $dbr->newSelectQueryBuilder()
197                ->select( LinkCache::getSelectFields() )
198                ->from( 'page' )
199                ->where( [
200                    'page_namespace' => $namespace,
201                    $dbr->expr(
202                        'page_title',
203                        IExpression::LIKE,
204                        new LikeValue( $prefixKey, $dbr->anyString() )
205                    ),
206                    $dbr->expr( 'page_title', '>=', $fromKey ),
207                ] )
208                ->orderBy( 'page_title' )
209                ->limit( $this->maxPerPage + 1 )
210                ->useIndex( 'page_name_title' );
211
212            if ( $this->hideRedirects ) {
213                $queryBuiler->andWhere( [ 'page_is_redirect' => 0 ] );
214            }
215
216            $this->getHookRunner()->onSpecialPrefixIndexQuery( $preparedHtmlForm->mFieldData, $queryBuiler );
217
218            $res = $queryBuiler->caller( __METHOD__ )->fetchResultSet();
219
220            // @todo FIXME: Side link to previous
221
222            if ( $res->numRows() > 0 ) {
223                $out = Html::openElement( 'ul', [ 'class' => 'mw-prefixindex-list' ] );
224
225                $prefixLength = strlen( $prefix );
226                foreach ( $res as $row ) {
227                    if ( $n >= $this->maxPerPage ) {
228                        $nextRow = $row;
229                        break;
230                    }
231                    $title = Title::newFromRow( $row );
232                    // Make sure it gets into LinkCache
233                    $this->linkCache->addGoodLinkObjFromRow( $title, $row );
234                    $displayed = $title->getText();
235                    // Try not to generate unclickable links
236                    if ( $this->stripPrefix && $prefixLength !== strlen( $displayed ) ) {
237                        $displayed = substr( $displayed, $prefixLength );
238                    }
239                    $link = ( $title->isRedirect() ? '<div class="allpagesredirect">' : '' ) .
240                        $this->getLinkRenderer()->makeKnownLink(
241                            $title,
242                            $displayed
243                        ) .
244                        ( $title->isRedirect() ? '</div>' : '' );
245
246                    $out .= "<li>$link</li>\n";
247                    $n++;
248
249                }
250                $out .= Html::closeElement( 'ul' );
251
252                if ( $res->numRows() > 2 ) {
253                    // Only apply CSS column styles if there are more than 2 entries.
254                    // Otherwise, rendering is broken as "mw-prefixindex-body"'s CSS column count is 3.
255                    $out = Html::rawElement( 'div', [ 'class' => 'mw-prefixindex-body' ], $out );
256                }
257            } else {
258                $out = '';
259            }
260        }
261
262        $output = $this->getOutput();
263
264        if ( $this->including() ) {
265            // We don't show the nav-links and the form when included in other
266            // pages, so let's just finish here.
267            $output->addHTML( $out );
268            return;
269        }
270
271        $topOut = $preparedHtmlForm->getHTML( false );
272
273        if ( $res && ( $n == $this->maxPerPage ) && $nextRow ) {
274            $query = [
275                'from' => $nextRow->page_title,
276                'prefix' => $prefix,
277                'hideredirects' => $this->hideRedirects,
278                'stripprefix' => $this->stripPrefix,
279            ];
280
281            if ( $namespace || $prefix == '' ) {
282                // Keep the namespace even if it's 0 for empty prefixes.
283                // This tells us we're not just a holdover from old links.
284                $query['namespace'] = $namespace;
285            }
286
287            $nextLink = $this->getLinkRenderer()->makeKnownLink(
288                $this->getPageTitle(),
289                $this->msg( 'nextpage', str_replace( '_', ' ', $nextRow->page_title ) )->text(),
290                [],
291                $query
292            );
293
294            // Link shown at the top of the page below the form
295            $topOut .= Html::rawElement( 'div',
296                [ 'class' => 'mw-prefixindex-nav' ],
297                $nextLink
298            );
299
300            // Link shown at the footer
301            $out .= "\n" . Html::element( 'hr' ) .
302                Html::rawElement(
303                    'div',
304                    [ 'class' => 'mw-prefixindex-nav' ],
305                    $nextLink
306                );
307
308        }
309
310        $output->addHTML( $topOut . $out );
311    }
312
313    protected function getGroupName() {
314        return 'pages';
315    }
316}
317
318/**
319 * Retain the old class name for backwards compatibility.
320 * @deprecated since 1.41
321 */
322class_alias( SpecialPrefixIndex::class, 'SpecialPrefixindex' );