MediaWiki master
SpecialAllPages.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Specials;
25
38
46
52 protected $maxPerPage = 345;
53
59 protected $nsfromMsg = 'allpagesfrom';
60
61 private IConnectionProvider $dbProvider;
62 private SearchEngineFactory $searchEngineFactory;
63 private PageStore $pageStore;
64
65 public function __construct(
66 IConnectionProvider $dbProvider = null,
67 SearchEngineFactory $searchEngineFactory = null,
68 PageStore $pageStore = null
69 ) {
70 parent::__construct( 'Allpages' );
71 // This class is extended and therefore falls back to global state - T265309
73 $this->dbProvider = $dbProvider ?? $services->getConnectionProvider();
74 $this->searchEngineFactory = $searchEngineFactory ?? $services->getSearchEngineFactory();
75 $this->pageStore = $pageStore ?? $services->getPageStore();
76 }
77
83 public function execute( $par ) {
84 $request = $this->getRequest();
85 $out = $this->getOutput();
86
87 $this->setHeaders();
88 $this->outputHeader();
89 $out->setPreventClickjacking( false );
90
91 # GET values
92 $from = $request->getVal( 'from', null );
93 $to = $request->getVal( 'to', null );
94 $namespace = $request->getInt( 'namespace' );
95
96 $miserMode = (bool)$this->getConfig()->get( MainConfigNames::MiserMode );
97
98 // Redirects filter is disabled in MiserMode
99 $hideredirects = $request->getBool( 'hideredirects', false ) && !$miserMode;
100
101 $namespaces = $this->getLanguage()->getNamespaces();
102
103 $out->setPageTitleMsg(
104 ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) ) ?
105 $this->msg( 'allinnamespace' )->plaintextParams( str_replace( '_', ' ', $namespaces[$namespace] ) ) :
106 $this->msg( 'allarticles' )
107 );
108 $out->addModuleStyles( 'mediawiki.special' );
109
110 if ( $par !== null ) {
111 $this->showChunk( $namespace, $par, $to, $hideredirects );
112 } elseif ( $from !== null && $to === null ) {
113 $this->showChunk( $namespace, $from, $to, $hideredirects );
114 } else {
115 $this->showToplevel( $namespace, $from, $to, $hideredirects );
116 }
117 }
118
127 protected function outputHTMLForm( $namespace = NS_MAIN,
128 $from = '', $to = '', $hideRedirects = false
129 ) {
130 $miserMode = (bool)$this->getConfig()->get( MainConfigNames::MiserMode );
131 $formDescriptor = [
132 'from' => [
133 'type' => 'text',
134 'name' => 'from',
135 'id' => 'nsfrom',
136 'size' => 30,
137 'label-message' => 'allpagesfrom',
138 'default' => str_replace( '_', ' ', $from ),
139 ],
140 'to' => [
141 'type' => 'text',
142 'name' => 'to',
143 'id' => 'nsto',
144 'size' => 30,
145 'label-message' => 'allpagesto',
146 'default' => str_replace( '_', ' ', $to ),
147 ],
148 'namespace' => [
149 'type' => 'namespaceselect',
150 'name' => 'namespace',
151 'id' => 'namespace',
152 'label-message' => 'namespace',
153 'all' => null,
154 'default' => $namespace,
155 ],
156 'hideredirects' => [
157 'type' => 'check',
158 'name' => 'hideredirects',
159 'id' => 'hidredirects',
160 'label-message' => 'allpages-hide-redirects',
161 'value' => $hideRedirects,
162 ],
163 ];
164
165 if ( $miserMode ) {
166 unset( $formDescriptor['hideredirects'] );
167 }
168
169 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
170 $htmlForm
171 ->setMethod( 'get' )
172 ->setTitle( $this->getPageTitle() ) // Remove subpage
173 ->setWrapperLegendMsg( 'allpages' )
174 ->setSubmitTextMsg( 'allpagessubmit' )
175 ->prepareForm()
176 ->displayForm( false );
177 }
178
185 private function showToplevel(
186 $namespace = NS_MAIN, $from = null, $to = null, $hideredirects = false
187 ) {
188 $from = $from ? Title::makeTitleSafe( $namespace, $from ) : null;
189 $to = $to ? Title::makeTitleSafe( $namespace, $to ) : null;
190 $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
191 $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
192
193 $this->showChunk( $namespace, $from, $to, $hideredirects );
194 }
195
202 private function showChunk(
203 $namespace = NS_MAIN, $from = null, $to = null, $hideredirects = false
204 ) {
205 $output = $this->getOutput();
206
207 $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
208 $toList = $this->getNamespaceKeyAndText( $namespace, $to );
209 $namespaces = $this->getContext()->getLanguage()->getNamespaces();
210 $n = 0;
211 $prevTitle = null;
212
213 if ( !$fromList || !$toList ) {
214 $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
215 } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
216 // Show errormessage and reset to NS_MAIN
217 $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
218 $namespace = NS_MAIN;
219 } else {
220 [ $namespace, $fromKey, $from ] = $fromList;
221 [ , $toKey, $to ] = $toList;
222
223 $dbr = $this->dbProvider->getReplicaDatabase();
224 $filterConds = [ 'page_namespace' => $namespace ];
225 if ( $hideredirects ) {
226 $filterConds['page_is_redirect'] = 0;
227 }
228
229 $conds = $filterConds;
230 $conds[] = $dbr->expr( 'page_title', '>=', $fromKey );
231 if ( $toKey !== "" ) {
232 $conds[] = $dbr->expr( 'page_title', '<=', $toKey );
233 }
234
235 $res = $this->pageStore->newSelectQueryBuilder()
236 ->where( $conds )
237 ->caller( __METHOD__ )
238 ->orderBy( 'page_title' )
239 ->limit( $this->maxPerPage + 1 )
240 ->useIndex( 'page_name_title' )
241 ->fetchPageRecords();
242
243 // Eagerly fetch the set of pages to be displayed and warm up LinkCache (T328174).
244 // Note that we can't use fetchPageRecordArray() here as that returns an array keyed
245 // by page IDs; we need a simple sequence.
247 $pages = iterator_to_array( $res );
248
249 $linkRenderer = $this->getLinkRenderer();
250 if ( count( $pages ) > 0 ) {
251 $out = Html::openElement( 'ul', [ 'class' => 'mw-allpages-chunk' ] );
252
253 while ( $n < $this->maxPerPage && $n < count( $pages ) ) {
254 $page = $pages[$n];
255 $attributes = $page->isRedirect() ? [ 'class' => 'allpagesredirect' ] : [];
256
257 $out .= Html::rawElement( 'li', $attributes, $linkRenderer->makeKnownLink( $page ) ) . "\n";
258 $n++;
259 }
260 $out .= Html::closeElement( 'ul' );
261
262 if ( count( $pages ) > 2 ) {
263 // Only apply CSS column styles if there's more than 2 entries.
264 // Otherwise, rendering is broken as "mw-allpages-body"'s CSS column count is 3.
265 $out = Html::rawElement( 'div', [ 'class' => 'mw-allpages-body' ], $out );
266 }
267 } else {
268 $out = '';
269 }
270
271 if ( $fromKey !== '' && !$this->including() ) {
272 # Get the first title from previous chunk
273 $prevConds = $filterConds;
274 $prevConds[] = $dbr->expr( 'page_title', '<', $fromKey );
275 $prevKey = $dbr->newSelectQueryBuilder()
276 ->select( 'page_title' )
277 ->from( 'page' )
278 ->where( $prevConds )
279 ->orderBy( 'page_title', SelectQueryBuilder::SORT_DESC )
280 ->offset( $this->maxPerPage - 1 )
281 ->caller( __METHOD__ )->fetchField();
282
283 if ( $prevKey === false ) {
284 # The previous chunk is not complete, need to link to the very first title
285 # available in the database
286 $prevKey = $dbr->newSelectQueryBuilder()
287 ->select( 'page_title' )
288 ->from( 'page' )
289 ->where( $prevConds )
290 ->orderBy( 'page_title' )
291 ->caller( __METHOD__ )->fetchField();
292 }
293
294 if ( $prevKey !== false ) {
295 $prevTitle = Title::makeTitle( $namespace, $prevKey );
296 }
297 }
298 }
299
300 if ( $this->including() ) {
301 $output->addHTML( $out );
302 return;
303 }
304
305 $navLinks = [];
306 $self = $this->getPageTitle();
307
308 $linkRenderer = $this->getLinkRenderer();
309 // Generate a "previous page" link if needed
310 if ( $prevTitle ) {
311 $query = [ 'from' => $prevTitle->getText() ];
312
313 if ( $namespace ) {
314 $query['namespace'] = $namespace;
315 }
316
317 if ( $hideredirects ) {
318 $query['hideredirects'] = $hideredirects;
319 }
320
321 $navLinks[] = $linkRenderer->makeKnownLink(
322 $self,
323 $this->msg( 'prevpage', $prevTitle->getText() )->text(),
324 [],
325 $query
326 );
327
328 }
329
330 // Generate a "next page" link if needed
331 if ( $n === $this->maxPerPage && isset( $pages[$n] ) ) {
332 # $t is the first link of the next chunk
333 $t = TitleValue::newFromPage( $pages[$n] );
334 $query = [ 'from' => $t->getText() ];
335
336 if ( $namespace ) {
337 $query['namespace'] = $namespace;
338 }
339
340 if ( $hideredirects ) {
341 $query['hideredirects'] = $hideredirects;
342 }
343
344 $navLinks[] = $linkRenderer->makeKnownLink(
345 $self,
346 $this->msg( 'nextpage', $t->getText() )->text(),
347 [],
348 $query
349 );
350 }
351
352 $this->outputHTMLForm( $namespace, $from, $to, $hideredirects );
353
354 if ( count( $navLinks ) ) {
355 // Add pagination links
356 $pagination = Html::rawElement( 'div',
357 [ 'class' => 'mw-allpages-nav' ],
358 $this->getLanguage()->pipeList( $navLinks )
359 );
360
361 $output->addHTML( $pagination );
362 $out .= Html::element( 'hr' ) . $pagination; // Footer
363 }
364
365 $output->addHTML( $out );
366 }
367
373 protected function getNamespaceKeyAndText( $ns, $text ) {
374 if ( $text == '' ) {
375 # shortcut for common case
376 return [ $ns, '', '' ];
377 }
378
379 $t = Title::makeTitleSafe( $ns, $text );
380 if ( $t && $t->isLocal() ) {
381 return [ $t->getNamespace(), $t->getDBkey(), $t->getText() ];
382 } elseif ( $t ) {
383 return null;
384 }
385
386 # try again, in case the problem was an empty pagename
387 $text = preg_replace( '/(#|$)/', 'X$1', $text );
388 $t = Title::makeTitleSafe( $ns, $text );
389 if ( $t && $t->isLocal() ) {
390 return [ $t->getNamespace(), '', '' ];
391 } else {
392 return null;
393 }
394 }
395
404 public function prefixSearchSubpages( $search, $limit, $offset ) {
405 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
406 }
407
408 protected function getGroupName() {
409 return 'pages';
410 }
411}
412
416class_alias( SpecialAllPages::class, 'SpecialAllPages' );
const NS_MAIN
Definition Defines.php:64
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:206
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
A class containing constants representing the names of configuration variables.
const MiserMode
Name constant for the MiserMode setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Shortcut to construct an includable special page.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
prefixSearchString( $search, $limit, $offset, SearchEngineFactory $searchEngineFactory=null)
Perform a regular substring search for prefixSearchSubpages.
getPageTitle( $subpage=false)
Get a self-referential title object.
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
including( $x=null)
Whether the special page is being evaluated via transclusion.
getLanguage()
Shortcut to get user's language.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Implements Special:Allpages.
string $nsfromMsg
Determines, which message describes the input field 'nsfrom'.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
int $maxPerPage
Maximum number of pages to show on single subpage.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
execute( $par)
Entry point : initialise variables and call subfunctions.
outputHTMLForm( $namespace=NS_MAIN, $from='', $to='', $hideRedirects=false)
Outputs the HTMLForm used on this page.
__construct(IConnectionProvider $dbProvider=null, SearchEngineFactory $searchEngineFactory=null, PageStore $pageStore=null)
Represents the target of a wiki link.
Represents a title within MediaWiki.
Definition Title.php:78
Factory class for SearchEngine.
Build SELECT queries with a fluent interface.
Data record representing a page that currently exists as an editable page on a wiki.
Provide primary and replica IDatabase connections.
element(SerializerNode $parent, SerializerNode $node, $contents)
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...