MediaWiki  master
SpecialAllPages.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Specials;
25 
26 use HTMLForm;
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
72  $services = MediaWikiServices::getInstance();
73  $this->dbProvider = $dbProvider ?? $services->getDBLoadBalancerFactory();
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[] = 'page_title >= ' . $dbr->addQuotes( $fromKey );
231  if ( $toKey !== "" ) {
232  $conds[] = 'page_title <= ' . $dbr->addQuotes( $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[] = 'page_title < ' . $dbr->addQuotes( $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 
416 class_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:158
static factory( $displayFormat, $descriptor, IContextSource $context, $messagePrefix='')
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:360
This class is a collection of static functions that serve two purposes:
Definition: Html.php:57
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:288
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:239
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:352
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:264
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.
Definition: TitleValue.php:44
static newFromPage(PageReference $page)
Create a TitleValue from a local PageReference.
Definition: TitleValue.php:107
Represents a title within MediaWiki.
Definition: Title.php:76
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:650
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:624
Factory class for SearchEngine.
Build SELECT queries with a fluent interface.
$self
Data record representing a page that currently exists as an editable page on a wiki.
Provide primary and replica IDatabase connections.