MediaWiki  master
SpecialWhatLinksHere.php
Go to the documentation of this file.
1 <?php
31 
39  protected $opts;
40 
41  protected $selfTitle;
42 
44  protected $target;
45 
47  private $loadBalancer;
48 
50  private $linkBatchFactory;
51 
53  private $contentHandlerFactory;
54 
56  private $searchEngineFactory;
57 
59  private $namespaceInfo;
60 
62  private $titleFactory;
63 
65  private $linksMigration;
66 
67  protected $limits = [ 20, 50, 100, 250, 500 ];
68 
78  public function __construct(
79  ILoadBalancer $loadBalancer,
80  LinkBatchFactory $linkBatchFactory,
81  IContentHandlerFactory $contentHandlerFactory,
82  SearchEngineFactory $searchEngineFactory,
83  NamespaceInfo $namespaceInfo,
84  TitleFactory $titleFactory,
85  LinksMigration $linksMigration
86  ) {
87  parent::__construct( 'Whatlinkshere' );
88  $this->loadBalancer = $loadBalancer;
89  $this->linkBatchFactory = $linkBatchFactory;
90  $this->contentHandlerFactory = $contentHandlerFactory;
91  $this->searchEngineFactory = $searchEngineFactory;
92  $this->namespaceInfo = $namespaceInfo;
93  $this->titleFactory = $titleFactory;
94  $this->linksMigration = $linksMigration;
95  }
96 
97  public function execute( $par ) {
98  $out = $this->getOutput();
99 
100  $this->setHeaders();
101  $this->outputHeader();
102  $this->addHelpLink( 'Help:What links here' );
103  $out->addModuleStyles( 'mediawiki.special' );
104 
105  $opts = new FormOptions();
106 
107  $opts->add( 'target', '' );
108  $opts->add( 'namespace', '', FormOptions::INTNULL );
109  $opts->add( 'limit', $this->getConfig()->get( MainConfigNames::QueryPageDefaultLimit ) );
110  $opts->add( 'offset', '' );
111  $opts->add( 'from', 0 );
112  $opts->add( 'dir', 'next' );
113  $opts->add( 'hideredirs', false );
114  $opts->add( 'hidetrans', false );
115  $opts->add( 'hidelinks', false );
116  $opts->add( 'hideimages', false );
117  $opts->add( 'invert', false );
118 
120  $opts->validateIntBounds( 'limit', 0, 5000 );
121 
122  // Give precedence to subpage syntax
123  if ( $par !== null ) {
124  $opts->setValue( 'target', $par );
125  }
126 
127  // Bind to member variable
128  $this->opts = $opts;
129 
130  $this->target = Title::newFromText( $opts->getValue( 'target' ) );
131  if ( !$this->target ) {
132  if ( !$this->including() ) {
133  $out->addHTML( $this->whatlinkshereForm() );
134  }
135 
136  return;
137  }
138 
139  $this->getSkin()->setRelevantTitle( $this->target );
140 
141  $this->selfTitle = $this->getPageTitle( $this->target->getPrefixedDBkey() );
142 
143  $out->setPageTitle( $this->msg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
144  $out->addBacklinkSubtitle( $this->target );
145 
146  [ $offsetNamespace, $offsetPageID, $dir ] = $this->parseOffsetAndDir( $opts );
147 
148  $this->showIndirectLinks(
149  0,
150  $this->target,
151  $opts->getValue( 'limit' ),
152  $offsetNamespace,
153  $offsetPageID,
154  $dir
155  );
156  }
157 
169  private function parseOffsetAndDir( FormOptions $opts ): array {
170  $from = $opts->getValue( 'from' );
171  $opts->reset( 'from' );
172 
173  if ( $from ) {
174  $dir = 'next';
175  $offsetNamespace = null;
176  $offsetPageID = $from - 1;
177  } else {
178  $dir = $opts->getValue( 'dir' );
179  [ $offsetNamespaceString, $offsetPageIDString ] = explode(
180  '|',
181  $opts->getValue( 'offset' ) . '|'
182  );
183  if ( !$offsetPageIDString ) {
184  $offsetPageIDString = $offsetNamespaceString;
185  $offsetNamespaceString = '';
186  }
187  if ( is_numeric( $offsetNamespaceString ) ) {
188  $offsetNamespace = (int)$offsetNamespaceString;
189  } else {
190  $offsetNamespace = null;
191  }
192  $offsetPageID = (int)$offsetPageIDString;
193  }
194 
195  if ( $offsetNamespace === null ) {
196  $offsetTitle = $this->titleFactory->newFromID( $offsetPageID );
197  $offsetNamespace = $offsetTitle ? $offsetTitle->getNamespace() : NS_MAIN;
198  }
199 
200  return [ $offsetNamespace, $offsetPageID, $dir ];
201  }
202 
211  private function showIndirectLinks(
212  $level, $target, $limit, $offsetNamespace = 0, $offsetPageID = 0, $dir = 'next'
213  ) {
214  $out = $this->getOutput();
215  $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
216 
217  $hidelinks = $this->opts->getValue( 'hidelinks' );
218  $hideredirs = $this->opts->getValue( 'hideredirs' );
219  $hidetrans = $this->opts->getValue( 'hidetrans' );
220  $hideimages = $target->getNamespace() !== NS_FILE || $this->opts->getValue( 'hideimages' );
221 
222  // For historical reasons `pagelinks` always contains an entry for the redirect target.
223  // So we only need to query `redirect` if `pagelinks` isn't being queried.
224  $fetchredirs = $hidelinks && !$hideredirs;
225 
226  // Build query conds in concert for all four tables...
227  $conds = [];
228  $conds['redirect'] = [
229  'rd_namespace' => $target->getNamespace(),
230  'rd_title' => $target->getDBkey(),
231  ];
232  $conds['pagelinks'] = [
233  'pl_namespace' => $target->getNamespace(),
234  'pl_title' => $target->getDBkey(),
235  ];
236  $conds['templatelinks'] = $this->linksMigration->getLinksConditions( 'templatelinks', $target );
237  $conds['imagelinks'] = [
238  'il_to' => $target->getDBkey(),
239  ];
240 
241  $namespace = $this->opts->getValue( 'namespace' );
242  if ( is_int( $namespace ) ) {
243  $invert = $this->opts->getValue( 'invert' );
244  if ( $invert ) {
245  // Select all namespaces except for the specified one.
246  // This allows the database to use the *_from_namespace index. (T241837)
247  $namespaces = array_diff(
248  $this->namespaceInfo->getValidNamespaces(), [ $namespace ] );
249  } else {
250  $namespaces = $namespace;
251  }
252  } else {
253  // Select all namespaces.
254  // This allows the database to use the *_from_namespace index. (T297754)
255  $namespaces = $this->namespaceInfo->getValidNamespaces();
256  }
257  $conds['redirect']['page_namespace'] = $namespaces;
258  $conds['pagelinks']['pl_from_namespace'] = $namespaces;
259  $conds['templatelinks']['tl_from_namespace'] = $namespaces;
260  $conds['imagelinks']['il_from_namespace'] = $namespaces;
261 
262  if ( $offsetPageID ) {
263  $rel = $dir === 'prev' ? '<' : '>';
264  $conds['redirect'][] = "rd_from $rel $offsetPageID";
265  $conds['templatelinks'][] = "(tl_from_namespace = $offsetNamespace AND tl_from $rel $offsetPageID " .
266  "OR tl_from_namespace $rel $offsetNamespace)";
267  $conds['pagelinks'][] = "(pl_from_namespace = $offsetNamespace AND pl_from $rel $offsetPageID " .
268  "OR pl_from_namespace $rel $offsetNamespace)";
269  $conds['imagelinks'][] = "(il_from_namespace = $offsetNamespace AND il_from $rel $offsetPageID " .
270  "OR il_from_namespace $rel $offsetNamespace)";
271  }
272 
273  if ( $hideredirs ) {
274  // For historical reasons `pagelinks` always contains an entry for the redirect target.
275  // So we hide that link when $hideredirs is set. There's unfortunately no way to tell when a
276  // redirect's content also links to the target.
277  $conds['pagelinks']['rd_from'] = null;
278  }
279 
280  $sortDirection = $dir === 'prev' ? SelectQueryBuilder::SORT_DESC : SelectQueryBuilder::SORT_ASC;
281 
282  $fname = __METHOD__;
283  $queryFunc = static function ( IDatabase $dbr, $table, $fromCol ) use (
284  $conds, $target, $limit, $sortDirection, $fname
285  ) {
286  // Read an extra row as an at-end check
287  $queryLimit = $limit + 1;
288  $on = [
289  "rd_from = $fromCol",
290  'rd_title' => $target->getDBkey(),
291  'rd_namespace' => $target->getNamespace(),
292  'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'
293  ];
294  // Inner LIMIT is 2X in case of stale backlinks with wrong namespaces
295  $subQuery = $dbr->newSelectQueryBuilder()
296  ->table( $table )
297  ->fields( [ $fromCol, 'rd_from', 'rd_fragment' ] )
298  ->conds( $conds[$table] )
299  ->orderBy( [ $fromCol . '_namespace', $fromCol ], $sortDirection )
300  ->limit( 2 * $queryLimit )
301  ->leftJoin( 'redirect', 'redirect', $on );
302 
303  return $dbr->newSelectQueryBuilder()
304  ->table( $subQuery, 'temp_backlink_range' )
305  ->join( 'page', 'page', "$fromCol = page_id" )
306  ->fields( [ 'page_id', 'page_namespace', 'page_title',
307  'rd_from', 'rd_fragment', 'page_is_redirect' ] )
308  ->orderBy( [ 'page_namespace', 'page_id' ], $sortDirection )
309  ->limit( $queryLimit )
310  ->caller( $fname )
311  ->fetchResultSet();
312  };
313 
314  if ( $fetchredirs ) {
315  $rdRes = $dbr->newSelectQueryBuilder()
316  ->table( 'redirect' )
317  ->fields( [ 'page_id', 'page_namespace', 'page_title', 'rd_from', 'rd_fragment', 'page_is_redirect' ] )
318  ->conds( $conds['redirect'] )
319  ->orderBy( 'rd_from', $sortDirection )
320  ->limit( $limit + 1 )
321  ->join( 'page', 'page', 'rd_from = page_id' )
322  ->caller( __METHOD__ )
323  ->fetchResultSet();
324  }
325 
326  if ( !$hidelinks ) {
327  $plRes = $queryFunc( $dbr, 'pagelinks', 'pl_from' );
328  }
329 
330  if ( !$hidetrans ) {
331  $tlRes = $queryFunc( $dbr, 'templatelinks', 'tl_from' );
332  }
333 
334  if ( !$hideimages ) {
335  $ilRes = $queryFunc( $dbr, 'imagelinks', 'il_from' );
336  }
337 
338  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $rdRes is declared when fetching redirs
339  if ( ( !$fetchredirs || !$rdRes->numRows() )
340  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $plRes is declared when fetching links
341  && ( $hidelinks || !$plRes->numRows() )
342  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $tlRes is declared when fetching trans
343  && ( $hidetrans || !$tlRes->numRows() )
344  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $ilRes is declared when fetching images
345  && ( $hideimages || !$ilRes->numRows() )
346  ) {
347  if ( $level == 0 && !$this->including() ) {
348  $out->addHTML( $this->whatlinkshereForm() );
349 
350  $msgKey = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
351  $link = $this->getLinkRenderer()->makeLink(
352  $this->target,
353  null,
354  [],
355  $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
356  );
357 
358  $errMsg = $this->msg( $msgKey )
359  ->params( $this->target->getPrefixedText() )
360  ->rawParams( $link )
361  ->parseAsBlock();
362  $out->addHTML( $errMsg );
363  $out->setStatusCode( 404 );
364  }
365 
366  return;
367  }
368 
369  // Read the rows into an array and remove duplicates
370  // templatelinks comes third so that the templatelinks row overwrites the
371  // pagelinks/redirect row, so we get (inclusion) rather than nothing
372  $rows = [];
373  if ( $fetchredirs ) {
374  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $rdRes is declared when fetching redirs
375  foreach ( $rdRes as $row ) {
376  $row->is_template = 0;
377  $row->is_image = 0;
378  $rows[$row->page_id] = $row;
379  }
380  }
381  if ( !$hidelinks ) {
382  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $plRes is declared when fetching links
383  foreach ( $plRes as $row ) {
384  $row->is_template = 0;
385  $row->is_image = 0;
386  $rows[$row->page_id] = $row;
387  }
388  }
389  if ( !$hidetrans ) {
390  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $tlRes is declared when fetching trans
391  foreach ( $tlRes as $row ) {
392  $row->is_template = 1;
393  $row->is_image = 0;
394  $rows[$row->page_id] = $row;
395  }
396  }
397  if ( !$hideimages ) {
398  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $ilRes is declared when fetching images
399  foreach ( $ilRes as $row ) {
400  $row->is_template = 0;
401  $row->is_image = 1;
402  $rows[$row->page_id] = $row;
403  }
404  }
405 
406  // Sort by namespace + page ID, changing the keys to 0-based indices
407  usort( $rows, static function ( $rowA, $rowB ) {
408  if ( $rowA->page_namespace !== $rowB->page_namespace ) {
409  return $rowA->page_namespace < $rowB->page_namespace ? -1 : 1;
410  }
411  if ( $rowA->page_id !== $rowB->page_id ) {
412  return $rowA->page_id < $rowB->page_id ? -1 : 1;
413  }
414  return 0;
415  } );
416 
417  $numRows = count( $rows );
418 
419  // Work out the start and end IDs, for prev/next links
420  if ( !$limit ) { // T289351
421  $nextNamespace = $nextPageId = $prevNamespace = $prevPageId = false;
422  $rows = [];
423  } elseif ( $dir === 'prev' ) {
424  if ( $numRows > $limit ) {
425  // More rows available after these ones
426  // Get the next row from the last row in the result set
427  $nextNamespace = $rows[$limit]->page_namespace;
428  $nextPageId = $rows[$limit]->page_id;
429  // Remove undisplayed rows, for dir='prev' we need to discard first record after sorting
430  $rows = array_slice( $rows, 1, $limit );
431  // Get the prev row from the first displayed row
432  $prevNamespace = $rows[0]->page_namespace;
433  $prevPageId = $rows[0]->page_id;
434  } else {
435  // Get the next row from the last displayed row
436  $nextNamespace = $rows[$numRows - 1]->page_namespace;
437  $nextPageId = $rows[$numRows - 1]->page_id;
438  $prevNamespace = false;
439  $prevPageId = false;
440  }
441  } else {
442  // If offset is not set disable prev link
443  $prevNamespace = $offsetPageID ? $rows[0]->page_namespace : false;
444  $prevPageId = $offsetPageID ? $rows[0]->page_id : false;
445  if ( $numRows > $limit ) {
446  // Get the next row from the last displayed row
447  $nextNamespace = $rows[$limit - 1]->page_namespace;
448  $nextPageId = $rows[$limit - 1]->page_id;
449  // Remove undisplayed rows
450  $rows = array_slice( $rows, 0, $limit );
451  } else {
452  $nextNamespace = false;
453  $nextPageId = false;
454  }
455  }
456 
457  // use LinkBatch to make sure, that all required data (associated with Titles)
458  // is loaded in one query
459  $lb = $this->linkBatchFactory->newLinkBatch();
460  foreach ( $rows as $row ) {
461  $lb->add( $row->page_namespace, $row->page_title );
462  }
463  $lb->execute();
464 
465  if ( $level == 0 && !$this->including() ) {
466  $out->addHTML( $this->whatlinkshereForm() );
467 
468  $link = $this->getLinkRenderer()->makeLink(
469  $this->target,
470  null,
471  [],
472  $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
473  );
474 
475  $msg = $this->msg( 'linkshere' )
476  ->params( $this->target->getPrefixedText() )
477  ->rawParams( $link )
478  ->parseAsBlock();
479  $out->addHTML( $msg );
480 
481  $out->addWikiMsg( 'whatlinkshere-count', Message::numParam( count( $rows ) ) );
482 
483  $prevnext = $this->getPrevNext( $prevNamespace, $prevPageId, $nextNamespace, $nextPageId );
484  $out->addHTML( $prevnext );
485  }
486  $out->addHTML( $this->listStart( $level ) );
487  foreach ( $rows as $row ) {
488  $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
489 
490  if ( $row->rd_from && $level < 2 ) {
491  $out->addHTML( $this->listItem( $row, $nt, $target, true ) );
492  $this->showIndirectLinks(
493  $level + 1,
494  $nt,
495  $this->getConfig()->get( MainConfigNames::MaxRedirectLinksRetrieved )
496  );
497  $out->addHTML( Xml::closeElement( 'li' ) );
498  } else {
499  $out->addHTML( $this->listItem( $row, $nt, $target ) );
500  }
501  }
502 
503  $out->addHTML( $this->listEnd() );
504 
505  if ( $level == 0 && !$this->including() ) {
506  // @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable $prevnext is defined with $level is 0
507  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable prevnext is set when used
508  $out->addHTML( $prevnext );
509  }
510  }
511 
512  protected function listStart( $level ) {
513  return Xml::openElement( 'ul', ( $level ? [] : [ 'id' => 'mw-whatlinkshere-list' ] ) );
514  }
515 
516  protected function listItem( $row, $nt, $target, $notClose = false ) {
517  $dirmark = $this->getLanguage()->getDirMark();
518 
519  if ( $row->rd_from ) {
520  $query = [ 'redirect' => 'no' ];
521  } else {
522  $query = [];
523  }
524 
525  $link = $this->getLinkRenderer()->makeKnownLink(
526  $nt,
527  null,
528  $row->page_is_redirect ? [ 'class' => 'mw-redirect' ] : [],
529  $query
530  );
531 
532  // Display properties (redirect or template)
533  $propsText = '';
534  $props = [];
535  if ( (string)$row->rd_fragment !== '' ) {
536  $props[] = $this->msg( 'whatlinkshere-sectionredir' )
537  ->rawParams( $this->getLinkRenderer()->makeLink(
538  $target->createFragmentTarget( $row->rd_fragment ),
539  $row->rd_fragment
540  ) )->escaped();
541  } elseif ( $row->rd_from ) {
542  $props[] = $this->msg( 'isredirect' )->escaped();
543  }
544  if ( $row->is_template ) {
545  $props[] = $this->msg( 'istemplate' )->escaped();
546  }
547  if ( $row->is_image ) {
548  $props[] = $this->msg( 'isimage' )->escaped();
549  }
550 
551  $this->getHookRunner()->onWhatLinksHereProps( $row, $nt, $target, $props );
552 
553  if ( count( $props ) ) {
554  $propsText = $this->msg( 'parentheses' )
555  ->rawParams( $this->getLanguage()->semicolonList( $props ) )->escaped();
556  }
557 
558  # Space for utilities links, with a what-links-here link provided
559  $wlhLink = $this->wlhLink(
560  $nt,
561  $this->msg( 'whatlinkshere-links' )->text(),
562  $this->msg( 'editlink' )->text()
563  );
564  $wlh = Xml::wrapClass(
565  $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(),
566  'mw-whatlinkshere-tools'
567  );
568 
569  return $notClose ?
570  Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" :
571  Xml::tags( 'li', null, "$link $propsText $dirmark $wlh" ) . "\n";
572  }
573 
574  protected function listEnd() {
575  return Xml::closeElement( 'ul' );
576  }
577 
578  protected function wlhLink( Title $target, $text, $editText ) {
579  static $title = null;
580  if ( $title === null ) {
581  $title = $this->getPageTitle();
582  }
583 
584  $linkRenderer = $this->getLinkRenderer();
585 
586  // always show a "<- Links" link
587  $links = [
588  'links' => $linkRenderer->makeKnownLink(
589  $title,
590  $text,
591  [],
592  [ 'target' => $target->getPrefixedText() ]
593  ),
594  ];
595 
596  // if the page is editable, add an edit link
597  if (
598  // check user permissions
599  $this->getAuthority()->isAllowed( 'edit' ) &&
600  // check, if the content model is editable through action=edit
601  $this->contentHandlerFactory->getContentHandler( $target->getContentModel() )
602  ->supportsDirectEditing()
603  ) {
604  $links['edit'] = $linkRenderer->makeKnownLink(
605  $target,
606  $editText,
607  [],
608  [ 'action' => 'edit' ]
609  );
610  }
611 
612  // build the links html
613  return $this->getLanguage()->pipeList( $links );
614  }
615 
616  private function makeSelfLink( $text, $query ) {
617  return $this->getLinkRenderer()->makeKnownLink(
618  $this->selfTitle,
619  $text,
620  [],
621  $query
622  );
623  }
624 
625  private function getPrevNext( $prevNamespace, $prevPageId, $nextNamespace, $nextPageId ) {
626  $currentLimit = $this->opts->getValue( 'limit' );
627  $prev = $this->msg( 'whatlinkshere-prev' )->numParams( $currentLimit )->text();
628  $next = $this->msg( 'whatlinkshere-next' )->numParams( $currentLimit )->text();
629 
630  $changed = $this->opts->getChangedValues();
631  unset( $changed['target'] ); // Already in the request title
632 
633  if ( $prevPageId != 0 ) {
634  $overrides = [ 'dir' => 'prev', 'offset' => "$prevNamespace|$prevPageId", ];
635  $prev = Message::rawParam( $this->makeSelfLink( $prev, array_merge( $changed, $overrides ) ) );
636  }
637  if ( $nextPageId != 0 ) {
638  $overrides = [ 'dir' => 'next', 'offset' => "$nextNamespace|$nextPageId", ];
639  $next = Message::rawParam( $this->makeSelfLink( $next, array_merge( $changed, $overrides ) ) );
640  }
641 
642  $limitLinks = [];
643  $lang = $this->getLanguage();
644  foreach ( $this->limits as $limit ) {
645  $prettyLimit = $lang->formatNum( $limit );
646  $overrides = [ 'limit' => $limit ];
647  $limitLinks[] = $this->makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) );
648  }
649 
650  $nums = $lang->pipeList( $limitLinks );
651 
652  return $this->msg( 'viewprevnext' )->params( $prev, $next )->rawParams( $nums )->escaped();
653  }
654 
655  private function whatlinkshereForm() {
656  // We get nicer value from the title object
657  $this->opts->consumeValue( 'target' );
658  $target = $this->target ? $this->target->getPrefixedText() : '';
659  $this->opts->consumeValue( 'namespace' );
660  $this->opts->consumeValue( 'invert' );
661 
662  $fields = [
663  'target' => [
664  'type' => 'title',
665  'name' => 'target',
666  'default' => $target,
667  'id' => 'mw-whatlinkshere-target',
668  'label-message' => 'whatlinkshere-page',
669  'section' => 'whatlinkshere-target',
670  ],
671  'namespace' => [
672  'type' => 'namespaceselect',
673  'name' => 'namespace',
674  'id' => 'namespace',
675  'label-message' => 'namespace',
676  'all' => '',
677  'in-user-lang' => true,
678  'section' => 'whatlinkshere-ns',
679  ],
680  'invert' => [
681  'type' => 'check',
682  'name' => 'invert',
683  'id' => 'nsinvert',
684  'hide-if' => [ '===', 'namespace', '' ],
685  'label-message' => 'invert',
686  'help-message' => 'tooltip-whatlinkshere-invert',
687  'help-inline' => false,
688  'section' => 'whatlinkshere-ns',
689  ],
690  ];
691 
692  $filters = [ 'hidetrans', 'hidelinks', 'hideredirs' ];
693  if ( $this->target instanceof Title &&
694  $this->target->getNamespace() == NS_FILE ) {
695  $filters[] = 'hideimages';
696  }
697 
698  // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans',
699  // 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
700  // To be sure they will be found by grep
701  foreach ( $filters as $filter ) {
702  // Parameter only provided for backwards-compatibility with old translations
703  $hide = $this->msg( 'hide' )->text();
704  $msg = $this->msg( "whatlinkshere-{$filter}", $hide )->text();
705  $fields[$filter] = [
706  'type' => 'check',
707  'name' => $filter,
708  'label' => $msg,
709  'section' => 'whatlinkshere-filter',
710  ];
711  }
712 
713  $form = HTMLForm::factory( 'ooui', $fields, $this->getContext() )
714  ->setMethod( 'GET' )
715  ->setTitle( $this->getPageTitle() )
716  ->setWrapperLegendMsg( 'whatlinkshere' )
717  ->setSubmitTextMsg( 'whatlinkshere-submit' );
718 
719  return $form->prepareForm()->getHTML( false );
720  }
721 
730  public function prefixSearchSubpages( $search, $limit, $offset ) {
731  return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
732  }
733 
734  protected function getGroupName() {
735  return 'pagetools';
736  }
737 }
getAuthority()
const NS_FILE
Definition: Defines.php:70
const NS_MAIN
Definition: Defines.php:64
getContext()
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition: WebStart.php:82
Helper class to keep track of options when mixing links and form elements.
Definition: FormOptions.php:35
add( $name, $default, $type=self::AUTO)
Add an option to be handled by this FormOptions instance.
Definition: FormOptions.php:83
setValue( $name, $value, $force=false)
Use to set the value of an option.
reset( $name)
Delete the option value.
fetchValuesFromRequest(WebRequest $r, $optionKeys=null)
Fetch values for all options (or selected options) from the given WebRequest, making them available f...
validateIntBounds( $name, $min, $max)
const INTNULL
Integer type or null, maps to WebRequest::getIntOrNull() This is useful for the namespace selector.
Definition: FormOptions.php:55
getValue( $name)
Get the value for the given option name.
static factory( $displayFormat, $descriptor, IContextSource $context, $messagePrefix='')
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:344
Shortcut to construct an includable special page.
Service for compat reading of links tables.
A class containing constants representing the names of configuration variables.
static rawParam( $raw)
Definition: Message.php:1155
static numParam( $num)
Definition: Message.php:1166
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Factory class for SearchEngine.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getSkin()
Shortcut to get the skin being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
including( $x=null)
Whether the special page is being evaluated via transclusion.
Implements Special:Whatlinkshere.
execute( $par)
Default execute method Checks user permissions.
wlhLink(Title $target, $text, $editText)
listItem( $row, $nt, $target, $notClose=false)
__construct(ILoadBalancer $loadBalancer, LinkBatchFactory $linkBatchFactory, IContentHandlerFactory $contentHandlerFactory, SearchEngineFactory $searchEngineFactory, NamespaceInfo $namespaceInfo, TitleFactory $titleFactory, LinksMigration $linksMigration)
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Creates Title objects.
Represents a title within MediaWiki.
Definition: Title.php:49
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1066
getDBkey()
Get the main part with underscores.
Definition: Title.php:1057
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:370
createFragmentTarget(string $fragment)
Creates a new Title for a different fragment of the same page.
Definition: Title.php:1821
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:638
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:1088
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1888
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:121
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:112
static wrapClass( $text, $class, $tag='span', $attribs=[])
Shortcut to make a specific element with a class attribute.
Definition: Xml.php:271
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:134
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:39
Create and track the database connections and transactions for a given database cluster.
const DB_REPLICA
Definition: defines.php:26
if(!isset( $args[0])) $lang