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