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