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