MediaWiki  master
SpecialWhatLinksHere.php
Go to the documentation of this file.
1 <?php
32 
40  protected $opts;
41 
43  protected $target;
44 
46  private $loadBalancer;
47 
49  private $linkBatchFactory;
50 
52  private $contentHandlerFactory;
53 
55  private $searchEngineFactory;
56 
58  private $namespaceInfo;
59 
61  private $titleFactory;
62 
64  private $linksMigration;
65 
66  protected $limits = [ 20, 50, 100, 250, 500 ];
67 
77  public function __construct(
78  ILoadBalancer $loadBalancer,
79  LinkBatchFactory $linkBatchFactory,
80  IContentHandlerFactory $contentHandlerFactory,
81  SearchEngineFactory $searchEngineFactory,
82  NamespaceInfo $namespaceInfo,
83  TitleFactory $titleFactory,
84  LinksMigration $linksMigration
85  ) {
86  parent::__construct( 'Whatlinkshere' );
87  $this->mIncludable = true;
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 
101  protected function setParameter( $par ) {
102  if ( $par ) {
103  // The only difference that subpage syntax can have is the underscore.
104  $par = str_replace( '_', ' ', $par );
105  }
106  parent::setParameter( $par );
107  }
108 
112  public function onSuccess() {
113  $opts = new FormOptions();
114 
115  $opts->add( 'namespace', '', FormOptions::INTNULL );
116  $opts->add( 'limit', $this->getConfig()->get( MainConfigNames::QueryPageDefaultLimit ) );
117  $opts->add( 'offset', '' );
118  $opts->add( 'from', 0 );
119  $opts->add( 'dir', 'next' );
120  $opts->add( 'hideredirs', false );
121  $opts->add( 'hidetrans', false );
122  $opts->add( 'hidelinks', false );
123  $opts->add( 'hideimages', false );
124  $opts->add( 'invert', false );
125 
127  $opts->validateIntBounds( 'limit', 0, 5000 );
128 
129  // Bind to member variable
130  $this->opts = $opts;
131 
132  $this->getSkin()->setRelevantTitle( $this->target );
133 
134  $out = $this->getOutput();
135  $out->setPageTitle( $this->msg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
136  $out->addBacklinkSubtitle( $this->target );
137 
138  [ $offsetNamespace, $offsetPageID, $dir ] = $this->parseOffsetAndDir( $opts );
139 
140  $this->showIndirectLinks(
141  0,
142  $this->target,
143  $opts->getValue( 'limit' ),
144  $offsetNamespace,
145  $offsetPageID,
146  $dir
147  );
148  }
149 
161  private function parseOffsetAndDir( FormOptions $opts ): array {
162  $from = $opts->getValue( 'from' );
163  $opts->reset( 'from' );
164 
165  if ( $from ) {
166  $dir = 'next';
167  $offsetNamespace = null;
168  $offsetPageID = $from - 1;
169  } else {
170  $dir = $opts->getValue( 'dir' );
171  [ $offsetNamespaceString, $offsetPageIDString ] = explode(
172  '|',
173  $opts->getValue( 'offset' ) . '|'
174  );
175  if ( !$offsetPageIDString ) {
176  $offsetPageIDString = $offsetNamespaceString;
177  $offsetNamespaceString = '';
178  }
179  if ( is_numeric( $offsetNamespaceString ) ) {
180  $offsetNamespace = (int)$offsetNamespaceString;
181  } else {
182  $offsetNamespace = null;
183  }
184  $offsetPageID = (int)$offsetPageIDString;
185  }
186 
187  if ( $offsetNamespace === null ) {
188  $offsetTitle = $this->titleFactory->newFromID( $offsetPageID );
189  $offsetNamespace = $offsetTitle ? $offsetTitle->getNamespace() : NS_MAIN;
190  }
191 
192  return [ $offsetNamespace, $offsetPageID, $dir ];
193  }
194 
203  private function showIndirectLinks(
204  $level, $target, $limit, $offsetNamespace = 0, $offsetPageID = 0, $dir = 'next'
205  ) {
206  $out = $this->getOutput();
207  $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
208 
209  $hidelinks = $this->opts->getValue( 'hidelinks' );
210  $hideredirs = $this->opts->getValue( 'hideredirs' );
211  $hidetrans = $this->opts->getValue( 'hidetrans' );
212  $hideimages = $target->getNamespace() !== NS_FILE || $this->opts->getValue( 'hideimages' );
213 
214  // For historical reasons `pagelinks` always contains an entry for the redirect target.
215  // So we only need to query `redirect` if `pagelinks` isn't being queried.
216  $fetchredirs = $hidelinks && !$hideredirs;
217 
218  // Build query conds in concert for all four tables...
219  $conds = [];
220  $conds['redirect'] = [
221  'rd_namespace' => $target->getNamespace(),
222  'rd_title' => $target->getDBkey(),
223  ];
224  $conds['pagelinks'] = [
225  'pl_namespace' => $target->getNamespace(),
226  'pl_title' => $target->getDBkey(),
227  ];
228  $conds['templatelinks'] = $this->linksMigration->getLinksConditions( 'templatelinks', $target );
229  $conds['imagelinks'] = [
230  'il_to' => $target->getDBkey(),
231  ];
232 
233  $namespace = $this->opts->getValue( 'namespace' );
234  if ( is_int( $namespace ) ) {
235  $invert = $this->opts->getValue( 'invert' );
236  if ( $invert ) {
237  // Select all namespaces except for the specified one.
238  // This allows the database to use the *_from_namespace index. (T241837)
239  $namespaces = array_diff(
240  $this->namespaceInfo->getValidNamespaces(), [ $namespace ] );
241  } else {
242  $namespaces = $namespace;
243  }
244  } else {
245  // Select all namespaces.
246  // This allows the database to use the *_from_namespace index. (T297754)
247  $namespaces = $this->namespaceInfo->getValidNamespaces();
248  }
249  $conds['redirect']['page_namespace'] = $namespaces;
250  $conds['pagelinks']['pl_from_namespace'] = $namespaces;
251  $conds['templatelinks']['tl_from_namespace'] = $namespaces;
252  $conds['imagelinks']['il_from_namespace'] = $namespaces;
253 
254  if ( $offsetPageID ) {
255  $op = $dir === 'prev' ? '<' : '>';
256  $conds['redirect'][] = $dbr->buildComparison( $op, [
257  'rd_from' => $offsetPageID,
258  ] );
259  $conds['templatelinks'][] = $dbr->buildComparison( $op, [
260  'tl_from_namespace' => $offsetNamespace,
261  'tl_from' => $offsetPageID,
262  ] );
263  $conds['pagelinks'][] = $dbr->buildComparison( $op, [
264  'pl_from_namespace' => $offsetNamespace,
265  'pl_from' => $offsetPageID,
266  ] );
267  $conds['imagelinks'][] = $dbr->buildComparison( $op, [
268  'il_from_namespace' => $offsetNamespace,
269  'il_from' => $offsetPageID,
270  ] );
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  if ( $hidelinks || $hidetrans || $hideredirs ) {
349  $msgKey = 'nolinkshere-filter';
350  } elseif ( is_int( $namespace ) ) {
351  $msgKey = 'nolinkshere-ns';
352  } else {
353  $msgKey = 'nolinkshere';
354  }
355  $link = $this->getLinkRenderer()->makeLink(
356  $this->target,
357  null,
358  [],
359  $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
360  );
361 
362  $errMsg = $this->msg( $msgKey )
363  ->params( $this->target->getPrefixedText() )
364  ->rawParams( $link )
365  ->parseAsBlock();
366  $out->addHTML( $errMsg );
367  $out->setStatusCode( 404 );
368  }
369 
370  return;
371  }
372 
373  // Read the rows into an array and remove duplicates
374  // templatelinks comes third so that the templatelinks row overwrites the
375  // pagelinks/redirect row, so we get (inclusion) rather than nothing
376  $rows = [];
377  if ( $fetchredirs ) {
378  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $rdRes is declared when fetching redirs
379  foreach ( $rdRes as $row ) {
380  $row->is_template = 0;
381  $row->is_image = 0;
382  $rows[$row->page_id] = $row;
383  }
384  }
385  if ( !$hidelinks ) {
386  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $plRes is declared when fetching links
387  foreach ( $plRes as $row ) {
388  $row->is_template = 0;
389  $row->is_image = 0;
390  $rows[$row->page_id] = $row;
391  }
392  }
393  if ( !$hidetrans ) {
394  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $tlRes is declared when fetching trans
395  foreach ( $tlRes as $row ) {
396  $row->is_template = 1;
397  $row->is_image = 0;
398  $rows[$row->page_id] = $row;
399  }
400  }
401  if ( !$hideimages ) {
402  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $ilRes is declared when fetching images
403  foreach ( $ilRes as $row ) {
404  $row->is_template = 0;
405  $row->is_image = 1;
406  $rows[$row->page_id] = $row;
407  }
408  }
409 
410  // Sort by namespace + page ID, changing the keys to 0-based indices
411  usort( $rows, static function ( $rowA, $rowB ) {
412  if ( $rowA->page_namespace !== $rowB->page_namespace ) {
413  return $rowA->page_namespace < $rowB->page_namespace ? -1 : 1;
414  }
415  if ( $rowA->page_id !== $rowB->page_id ) {
416  return $rowA->page_id < $rowB->page_id ? -1 : 1;
417  }
418  return 0;
419  } );
420 
421  $numRows = count( $rows );
422 
423  // Work out the start and end IDs, for prev/next links
424  if ( !$limit ) { // T289351
425  $nextNamespace = $nextPageId = $prevNamespace = $prevPageId = false;
426  $rows = [];
427  } elseif ( $dir === 'prev' ) {
428  if ( $numRows > $limit ) {
429  // More rows available after these ones
430  // Get the next row from the last row in the result set
431  $nextNamespace = $rows[$limit]->page_namespace;
432  $nextPageId = $rows[$limit]->page_id;
433  // Remove undisplayed rows, for dir='prev' we need to discard first record after sorting
434  $rows = array_slice( $rows, 1, $limit );
435  // Get the prev row from the first displayed row
436  $prevNamespace = $rows[0]->page_namespace;
437  $prevPageId = $rows[0]->page_id;
438  } else {
439  // Get the next row from the last displayed row
440  $nextNamespace = $rows[$numRows - 1]->page_namespace;
441  $nextPageId = $rows[$numRows - 1]->page_id;
442  $prevNamespace = false;
443  $prevPageId = false;
444  }
445  } else {
446  // If offset is not set disable prev link
447  $prevNamespace = $offsetPageID ? $rows[0]->page_namespace : false;
448  $prevPageId = $offsetPageID ? $rows[0]->page_id : false;
449  if ( $numRows > $limit ) {
450  // Get the next row from the last displayed row
451  $nextNamespace = $rows[$limit - 1]->page_namespace;
452  $nextPageId = $rows[$limit - 1]->page_id;
453  // Remove undisplayed rows
454  $rows = array_slice( $rows, 0, $limit );
455  } else {
456  $nextNamespace = false;
457  $nextPageId = false;
458  }
459  }
460 
461  // use LinkBatch to make sure, that all required data (associated with Titles)
462  // is loaded in one query
463  $lb = $this->linkBatchFactory->newLinkBatch();
464  foreach ( $rows as $row ) {
465  $lb->add( $row->page_namespace, $row->page_title );
466  }
467  $lb->execute();
468 
469  if ( $level == 0 && !$this->including() ) {
470  $link = $this->getLinkRenderer()->makeLink(
471  $this->target,
472  null,
473  [],
474  $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
475  );
476 
477  $msg = $this->msg( 'linkshere' )
478  ->params( $this->target->getPrefixedText() )
479  ->rawParams( $link )
480  ->parseAsBlock();
481  $out->addHTML( $msg );
482 
483  $out->addWikiMsg( 'whatlinkshere-count', Message::numParam( count( $rows ) ) );
484 
485  $prevnext = $this->getPrevNext( $prevNamespace, $prevPageId, $nextNamespace, $nextPageId );
486  $out->addHTML( $prevnext );
487  }
488  $out->addHTML( $this->listStart( $level ) );
489  foreach ( $rows as $row ) {
490  $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
491 
492  if ( $row->rd_from && $level < 2 ) {
493  $out->addHTML( $this->listItem( $row, $nt, $target, true ) );
494  $this->showIndirectLinks(
495  $level + 1,
496  $nt,
497  $this->getConfig()->get( MainConfigNames::MaxRedirectLinksRetrieved )
498  );
499  $out->addHTML( Xml::closeElement( 'li' ) );
500  } else {
501  $out->addHTML( $this->listItem( $row, $nt, $target ) );
502  }
503  }
504 
505  $out->addHTML( $this->listEnd() );
506 
507  if ( $level == 0 && !$this->including() ) {
508  // @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable $prevnext is defined with $level is 0
509  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable prevnext is set when used
510  $out->addHTML( $prevnext );
511  }
512  }
513 
514  protected function listStart( $level ) {
515  return Xml::openElement( 'ul', ( $level ? [] : [ 'id' => 'mw-whatlinkshere-list' ] ) );
516  }
517 
518  protected function listItem( $row, $nt, $target, $notClose = false ) {
519  $dirmark = $this->getLanguage()->getDirMark();
520 
521  if ( $row->rd_from ) {
522  $query = [ 'redirect' => 'no' ];
523  } else {
524  $query = [];
525  }
526 
527  $link = $this->getLinkRenderer()->makeKnownLink(
528  $nt,
529  null,
530  $row->page_is_redirect ? [ 'class' => 'mw-redirect' ] : [],
531  $query
532  );
533 
534  // Display properties (redirect or template)
535  $propsText = '';
536  $props = [];
537  if ( (string)$row->rd_fragment !== '' ) {
538  $props[] = $this->msg( 'whatlinkshere-sectionredir' )
539  ->rawParams( $this->getLinkRenderer()->makeLink(
540  $target->createFragmentTarget( $row->rd_fragment ),
541  $row->rd_fragment
542  ) )->escaped();
543  } elseif ( $row->rd_from ) {
544  $props[] = $this->msg( 'isredirect' )->escaped();
545  }
546  if ( $row->is_template ) {
547  $props[] = $this->msg( 'istemplate' )->escaped();
548  }
549  if ( $row->is_image ) {
550  $props[] = $this->msg( 'isimage' )->escaped();
551  }
552 
553  $this->getHookRunner()->onWhatLinksHereProps( $row, $nt, $target, $props );
554 
555  if ( count( $props ) ) {
556  $propsText = $this->msg( 'parentheses' )
557  ->rawParams( $this->getLanguage()->semicolonList( $props ) )->escaped();
558  }
559 
560  # Space for utilities links, with a what-links-here link provided
561  $wlhLink = $this->wlhLink(
562  $nt,
563  $this->msg( 'whatlinkshere-links' )->text(),
564  $this->msg( 'editlink' )->text()
565  );
566  $wlh = Xml::wrapClass(
567  $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(),
568  'mw-whatlinkshere-tools'
569  );
570 
571  return $notClose ?
572  Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" :
573  Xml::tags( 'li', null, "$link $propsText $dirmark $wlh" ) . "\n";
574  }
575 
576  protected function listEnd() {
577  return Xml::closeElement( 'ul' );
578  }
579 
580  protected function wlhLink( Title $target, $text, $editText ) {
581  static $title = null;
582  if ( $title === null ) {
583  $title = $this->getPageTitle();
584  }
585 
586  $linkRenderer = $this->getLinkRenderer();
587 
588  // always show a "<- Links" link
589  $links = [
590  'links' => $linkRenderer->makeKnownLink(
591  $title,
592  $text,
593  [],
594  [ 'target' => $target->getPrefixedText() ]
595  ),
596  ];
597 
598  // if the page is editable, add an edit link
599  if (
600  // check user permissions
601  $this->getAuthority()->isAllowed( 'edit' ) &&
602  // check, if the content model is editable through action=edit
603  $this->contentHandlerFactory->getContentHandler( $target->getContentModel() )
604  ->supportsDirectEditing()
605  ) {
606  $links['edit'] = $linkRenderer->makeKnownLink(
607  $target,
608  $editText,
609  [],
610  [ 'action' => 'edit' ]
611  );
612  }
613 
614  // build the links html
615  return $this->getLanguage()->pipeList( $links );
616  }
617 
618  private function getPrevNext( $prevNamespace, $prevPageId, $nextNamespace, $nextPageId ) {
619  $navBuilder = new PagerNavigationBuilder( $this->getContext() );
620 
621  $navBuilder
622  ->setPage( $this->getPageTitle( $this->target->getPrefixedDBkey() ) )
623  // Remove 'target', already included in the request title
624  ->setLinkQuery( array_diff_key( $this->opts->getChangedValues(), [ 'target' => null ] ) )
625  ->setLimits( $this->limits )
626  ->setLimitLinkQueryParam( 'limit' )
627  ->setCurrentLimit( $this->opts->getValue( 'limit' ) )
628  ->setPrevMsg( 'whatlinkshere-prev' )
629  ->setNextMsg( 'whatlinkshere-next' );
630 
631  if ( $prevPageId != 0 ) {
632  $navBuilder->setPrevLinkQuery( [ 'dir' => 'prev', 'offset' => "$prevNamespace|$prevPageId" ] );
633  }
634  if ( $nextPageId != 0 ) {
635  $navBuilder->setNextLinkQuery( [ 'dir' => 'next', 'offset' => "$nextNamespace|$nextPageId" ] );
636  }
637 
638  return $navBuilder->getHtml();
639  }
640 
641  protected function getFormFields() {
642  $this->addHelpLink( 'Help:What links here' );
643  $this->getOutput()->addModuleStyles( 'mediawiki.special' );
644 
645  $fields = [
646  'target' => [
647  'type' => 'title',
648  'name' => 'target',
649  'id' => 'mw-whatlinkshere-target',
650  'label-message' => 'whatlinkshere-page',
651  'section' => 'whatlinkshere-target',
652  'creatable' => true,
653  ],
654  'namespace' => [
655  'type' => 'namespaceselect',
656  'name' => 'namespace',
657  'id' => 'namespace',
658  'label-message' => 'namespace',
659  'all' => '',
660  'in-user-lang' => true,
661  'section' => 'whatlinkshere-ns',
662  ],
663  'invert' => [
664  'type' => 'check',
665  'name' => 'invert',
666  'id' => 'nsinvert',
667  'hide-if' => [ '===', 'namespace', '' ],
668  'label-message' => 'invert',
669  'help-message' => 'tooltip-whatlinkshere-invert',
670  'help-inline' => false,
671  'section' => 'whatlinkshere-ns',
672  ],
673  ];
674 
675  $filters = [ 'hidetrans', 'hidelinks', 'hideredirs' ];
676 
677  // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans',
678  // 'whatlinkshere-hidelinks'
679  // To be sure they will be found by grep
680  foreach ( $filters as $filter ) {
681  // Parameter only provided for backwards-compatibility with old translations
682  $hide = $this->msg( 'hide' )->text();
683  $msg = $this->msg( "whatlinkshere-{$filter}", $hide )->text();
684  $fields[$filter] = [
685  'type' => 'check',
686  'name' => $filter,
687  'label' => $msg,
688  'section' => 'whatlinkshere-filter',
689  ];
690  }
691 
692  return $fields;
693  }
694 
695  protected function alterForm( HTMLForm $form ) {
696  // This parameter from the subpage syntax is only added after constructing the form,
697  // so we should add the dynamic field that depends on the user input here.
698 
699  // TODO: This looks not good. Ideally we can initialize it in onSubmit().
700  // Maybe extend the hide-if feature to match prefixes on the client side.
701  $this->target = Title::newFromText( $this->getRequest()->getText( 'target' ) );
702  if ( $this->target && $this->target->getNamespace() == NS_FILE ) {
703  $hide = $this->msg( 'hide' )->text();
704  $msg = $this->msg( 'whatlinkshere-hideimages', $hide )->text();
705  $form->addFields( [
706  'hideimages' => [
707  'type' => 'check',
708  'name' => 'hideimages',
709  'label' => $msg,
710  'section' => 'whatlinkshere-filter',
711  ]
712  ] );
713  }
714 
715  $form->setWrapperLegendMsg( 'whatlinkshere' )
716  ->setSubmitTextMsg( 'whatlinkshere-submit' );
717  }
718 
719  protected function getShowAlways() {
720  return true;
721  }
722 
723  protected function getSubpageField() {
724  return 'target';
725  }
726 
727  public function onSubmit( array $data ) {
728  return true;
729  }
730 
731  public function requiresPost() {
732  return false;
733  }
734 
735  protected function getDisplayFormat() {
736  return 'ooui';
737  }
738 
747  public function prefixSearchSubpages( $search, $limit, $offset ) {
748  return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
749  }
750 
751  protected function getGroupName() {
752  return 'pagetools';
753  }
754 }
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
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.
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:151
setWrapperLegendMsg( $msg)
Prompt the whole form to be wrapped in a "<fieldset>", with this message as its "<legend>" element.
Definition: HTMLForm.php:1830
addFields( $descriptor)
Add fields to the form.
Definition: HTMLForm.php:402
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.
static numParam( $num)
Definition: Message.php:1145
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(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.
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.
Represents a title within MediaWiki.
Definition: Title.php:52
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1069
getDBkey()
Get the main part with underscores.
Definition: Title.php:1060
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:373
createFragmentTarget(string $fragment)
Creates a new Title for a different fragment of the same page.
Definition: Title.php:1824
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:641
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:1091
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1891
A query builder for SELECT queries with a fluent interface.
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:268
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:40
This class is a delegate to ILBFactory for a given database cluster.
const DB_REPLICA
Definition: defines.php:26