MediaWiki master
SpecialWhatLinksHere.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Specials;
22
40use stdClass;
44
52 protected $opts;
53
55 protected $target;
56
57 private IConnectionProvider $dbProvider;
58 private LinkBatchFactory $linkBatchFactory;
59 private IContentHandlerFactory $contentHandlerFactory;
60 private SearchEngineFactory $searchEngineFactory;
61 private NamespaceInfo $namespaceInfo;
62 private TitleFactory $titleFactory;
63 private LinksMigration $linksMigration;
64
65 protected $limits = [ 20, 50, 100, 250, 500 ];
66
76 public function __construct(
77 IConnectionProvider $dbProvider,
78 LinkBatchFactory $linkBatchFactory,
79 IContentHandlerFactory $contentHandlerFactory,
80 SearchEngineFactory $searchEngineFactory,
81 NamespaceInfo $namespaceInfo,
82 TitleFactory $titleFactory,
83 LinksMigration $linksMigration
84 ) {
85 parent::__construct( 'Whatlinkshere' );
86 $this->mIncludable = true;
87 $this->dbProvider = $dbProvider;
88 $this->linkBatchFactory = $linkBatchFactory;
89 $this->contentHandlerFactory = $contentHandlerFactory;
90 $this->searchEngineFactory = $searchEngineFactory;
91 $this->namespaceInfo = $namespaceInfo;
92 $this->titleFactory = $titleFactory;
93 $this->linksMigration = $linksMigration;
94 }
95
100 protected function setParameter( $par ) {
101 if ( $par ) {
102 // The only difference that subpage syntax can have is the underscore.
103 $par = str_replace( '_', ' ', $par );
104 }
105 parent::setParameter( $par );
106 }
107
111 public function onSuccess() {
112 $opts = new FormOptions();
113
114 $opts->add( 'namespace', null, FormOptions::INTNULL );
115 $opts->add( 'limit', null, FormOptions::INTNULL );
116 $opts->add( 'offset', '' );
117 $opts->add( 'from', 0 );
118 $opts->add( 'dir', 'next' );
119 $opts->add( 'hideredirs', false );
120 $opts->add( 'hidetrans', false );
121 $opts->add( 'hidelinks', false );
122 $opts->add( 'hideimages', false );
123 $opts->add( 'invert', false );
124
126
127 if ( $opts->getValue( 'limit' ) === null ) {
129 }
130 $opts->validateIntBounds( 'limit', 0, 5000 );
131
132 // Bind to member variable
133 $this->opts = $opts;
134
135 $this->getSkin()->setRelevantTitle( $this->target );
136
137 $out = $this->getOutput();
138 $out->setPageTitleMsg(
139 $this->msg( 'whatlinkshere-title' )->plaintextParams( $this->target->getPrefixedText() )
140 );
141 $out->addBacklinkSubtitle( $this->target );
142
143 [ $offsetNamespace, $offsetPageID, $dir ] = $this->parseOffsetAndDir( $opts );
144
145 $this->showIndirectLinks(
146 0,
147 $this->target,
148 $opts->getValue( 'limit' ),
149 $offsetNamespace,
150 $offsetPageID,
151 $dir
152 );
153 }
154
166 private function parseOffsetAndDir( FormOptions $opts ): array {
167 $from = $opts->getValue( 'from' );
168 $opts->reset( 'from' );
169
170 if ( $from ) {
171 $dir = 'next';
172 $offsetNamespace = null;
173 $offsetPageID = $from - 1;
174 } else {
175 $dir = $opts->getValue( 'dir' );
176 [ $offsetNamespaceString, $offsetPageIDString ] = explode(
177 '|',
178 $opts->getValue( 'offset' ) . '|'
179 );
180 if ( !$offsetPageIDString ) {
181 $offsetPageIDString = $offsetNamespaceString;
182 $offsetNamespaceString = '';
183 }
184 if ( is_numeric( $offsetNamespaceString ) ) {
185 $offsetNamespace = (int)$offsetNamespaceString;
186 } else {
187 $offsetNamespace = null;
188 }
189 $offsetPageID = (int)$offsetPageIDString;
190 }
191
192 if ( $offsetNamespace === null ) {
193 $offsetTitle = $this->titleFactory->newFromID( $offsetPageID );
194 $offsetNamespace = $offsetTitle ? $offsetTitle->getNamespace() : NS_MAIN;
195 }
196
197 return [ $offsetNamespace, $offsetPageID, $dir ];
198 }
199
208 private function showIndirectLinks(
209 $level, LinkTarget $target, $limit, $offsetNamespace = 0, $offsetPageID = 0, $dir = 'next'
210 ) {
211 $out = $this->getOutput();
212 $dbr = $this->dbProvider->getReplicaDatabase();
213
214 $hidelinks = $this->opts->getValue( 'hidelinks' );
215 $hideredirs = $this->opts->getValue( 'hideredirs' );
216 $hidetrans = $this->opts->getValue( 'hidetrans' );
217 $hideimages = $target->getNamespace() !== NS_FILE || $this->opts->getValue( 'hideimages' );
218
219 // For historical reasons `pagelinks` always contains an entry for the redirect target.
220 // So we only need to query `redirect` if `pagelinks` isn't being queried.
221 $fetchredirs = $hidelinks && !$hideredirs;
222
223 // Build query conds in concert for all four tables...
224 $conds = [];
225 $conds['redirect'] = [
226 'rd_namespace' => $target->getNamespace(),
227 'rd_title' => $target->getDBkey(),
228 'rd_interwiki' => '',
229 ];
230 $conds['pagelinks'] = $this->linksMigration->getLinksConditions( 'pagelinks', $target );
231 $conds['templatelinks'] = $this->linksMigration->getLinksConditions( 'templatelinks', $target );
232 $conds['imagelinks'] = [
233 'il_to' => $target->getDBkey(),
234 ];
235
236 $namespace = $this->opts->getValue( 'namespace' );
237 if ( is_int( $namespace ) ) {
238 $invert = $this->opts->getValue( 'invert' );
239 if ( $invert ) {
240 // Select all namespaces except for the specified one.
241 // This allows the database to use the *_from_namespace index. (T241837)
242 $namespaces = array_diff(
243 $this->namespaceInfo->getValidNamespaces(), [ $namespace ] );
244 } else {
245 $namespaces = $namespace;
246 }
247 } else {
248 // Select all namespaces.
249 // This allows the database to use the *_from_namespace index. (T297754)
250 $namespaces = $this->namespaceInfo->getValidNamespaces();
251 }
252 $conds['redirect']['page_namespace'] = $namespaces;
253 $conds['pagelinks']['pl_from_namespace'] = $namespaces;
254 $conds['templatelinks']['tl_from_namespace'] = $namespaces;
255 $conds['imagelinks']['il_from_namespace'] = $namespaces;
256
257 if ( $offsetPageID ) {
258 $op = $dir === 'prev' ? '<' : '>';
259 $conds['redirect'][] = $dbr->buildComparison( $op, [
260 'rd_from' => $offsetPageID,
261 ] );
262 $conds['templatelinks'][] = $dbr->buildComparison( $op, [
263 'tl_from_namespace' => $offsetNamespace,
264 'tl_from' => $offsetPageID,
265 ] );
266 $conds['pagelinks'][] = $dbr->buildComparison( $op, [
267 'pl_from_namespace' => $offsetNamespace,
268 'pl_from' => $offsetPageID,
269 ] );
270 $conds['imagelinks'][] = $dbr->buildComparison( $op, [
271 'il_from_namespace' => $offsetNamespace,
272 'il_from' => $offsetPageID,
273 ] );
274 }
275
276 if ( $hideredirs ) {
277 // For historical reasons `pagelinks` always contains an entry for the redirect target.
278 // So we hide that link when $hideredirs is set. There's unfortunately no way to tell when a
279 // redirect's content also links to the target.
280 $conds['pagelinks']['rd_from'] = null;
281 }
282
283 $sortDirection = $dir === 'prev' ? SelectQueryBuilder::SORT_DESC : SelectQueryBuilder::SORT_ASC;
284
285 $fname = __METHOD__;
286 $queryFunc = static function ( IReadableDatabase $dbr, $table, $fromCol ) use (
287 $conds, $target, $limit, $sortDirection, $fname
288 ) {
289 // Read an extra row as an at-end check
290 $queryLimit = $limit + 1;
291 $on = [
292 "rd_from = $fromCol",
293 'rd_title' => $target->getDBkey(),
294 'rd_namespace' => $target->getNamespace(),
295 'rd_interwiki' => '',
296 ];
297 // Inner LIMIT is 2X in case of stale backlinks with wrong namespaces
298 $subQuery = $dbr->newSelectQueryBuilder()
299 ->table( $table )
300 ->fields( [ $fromCol, 'rd_from', 'rd_fragment' ] )
301 ->conds( $conds[$table] )
302 ->orderBy( [ $fromCol . '_namespace', $fromCol ], $sortDirection )
303 ->limit( 2 * $queryLimit )
304 ->leftJoin( 'redirect', 'redirect', $on );
305
306 return $dbr->newSelectQueryBuilder()
307 ->table( $subQuery, 'temp_backlink_range' )
308 ->join( 'page', 'page', "$fromCol = page_id" )
309 ->fields( [ 'page_id', 'page_namespace', 'page_title',
310 'rd_from', 'rd_fragment', 'page_is_redirect' ] )
311 ->orderBy( [ 'page_namespace', 'page_id' ], $sortDirection )
312 ->limit( $queryLimit )
313 ->caller( $fname )
314 ->fetchResultSet();
315 };
316
317 if ( $fetchredirs ) {
318 $rdRes = $dbr->newSelectQueryBuilder()
319 ->table( 'redirect' )
320 ->fields( [ 'page_id', 'page_namespace', 'page_title', 'rd_from', 'rd_fragment', 'page_is_redirect' ] )
321 ->conds( $conds['redirect'] )
322 ->orderBy( 'rd_from', $sortDirection )
323 ->limit( $limit + 1 )
324 ->join( 'page', 'page', 'rd_from = page_id' )
325 ->caller( __METHOD__ )
326 ->fetchResultSet();
327 }
328
329 if ( !$hidelinks ) {
330 $plRes = $queryFunc( $dbr, 'pagelinks', 'pl_from' );
331 }
332
333 if ( !$hidetrans ) {
334 $tlRes = $queryFunc( $dbr, 'templatelinks', 'tl_from' );
335 }
336
337 if ( !$hideimages ) {
338 $ilRes = $queryFunc( $dbr, 'imagelinks', 'il_from' );
339 }
340
341 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $rdRes is declared when fetching redirs
342 if ( ( !$fetchredirs || !$rdRes->numRows() )
343 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $plRes is declared when fetching links
344 && ( $hidelinks || !$plRes->numRows() )
345 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $tlRes is declared when fetching trans
346 && ( $hidetrans || !$tlRes->numRows() )
347 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $ilRes is declared when fetching images
348 && ( $hideimages || !$ilRes->numRows() )
349 ) {
350 if ( $level == 0 && !$this->including() ) {
351 if ( $hidelinks || $hidetrans || $hideredirs ) {
352 $msgKey = 'nolinkshere-filter';
353 } elseif ( is_int( $namespace ) ) {
354 $msgKey = 'nolinkshere-ns';
355 } else {
356 $msgKey = 'nolinkshere';
357 }
358 $link = $this->getLinkRenderer()->makeLink(
359 $this->target,
360 null,
361 [],
362 $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
363 );
364
365 $errMsg = $this->msg( $msgKey )
366 ->params( $this->target->getPrefixedText() )
367 ->rawParams( $link )
368 ->parseAsBlock();
369 $out->addHTML( $errMsg );
370 $out->setStatusCode( 404 );
371 }
372
373 return;
374 }
375
376 // Read the rows into an array and remove duplicates
377 // templatelinks comes third so that the templatelinks row overwrites the
378 // pagelinks/redirect row, so we get (inclusion) rather than nothing
379 $rows = [];
380 if ( $fetchredirs ) {
381 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $rdRes is declared when fetching redirs
382 foreach ( $rdRes as $row ) {
383 $row->is_template = 0;
384 $row->is_image = 0;
385 $rows[$row->page_id] = $row;
386 }
387 }
388 if ( !$hidelinks ) {
389 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $plRes is declared when fetching links
390 foreach ( $plRes as $row ) {
391 $row->is_template = 0;
392 $row->is_image = 0;
393 $rows[$row->page_id] = $row;
394 }
395 }
396 if ( !$hidetrans ) {
397 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $tlRes is declared when fetching trans
398 foreach ( $tlRes as $row ) {
399 $row->is_template = 1;
400 $row->is_image = 0;
401 $rows[$row->page_id] = $row;
402 }
403 }
404 if ( !$hideimages ) {
405 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $ilRes is declared when fetching images
406 foreach ( $ilRes as $row ) {
407 $row->is_template = 0;
408 $row->is_image = 1;
409 $rows[$row->page_id] = $row;
410 }
411 }
412
413 // Sort by namespace + page ID, changing the keys to 0-based indices
414 usort( $rows, static function ( $rowA, $rowB ) {
415 if ( $rowA->page_namespace !== $rowB->page_namespace ) {
416 return $rowA->page_namespace < $rowB->page_namespace ? -1 : 1;
417 }
418 if ( $rowA->page_id !== $rowB->page_id ) {
419 return $rowA->page_id < $rowB->page_id ? -1 : 1;
420 }
421 return 0;
422 } );
423
424 $numRows = count( $rows );
425
426 // Work out the start and end IDs, for prev/next links
427 if ( !$limit ) { // T289351
428 $nextNamespace = $nextPageId = $prevNamespace = $prevPageId = false;
429 $rows = [];
430 } elseif ( $dir === 'prev' ) {
431 if ( $numRows > $limit ) {
432 // More rows available after these ones
433 // Get the next row from the last row in the result set
434 $nextNamespace = $rows[$limit]->page_namespace;
435 $nextPageId = $rows[$limit]->page_id;
436 // Remove undisplayed rows, for dir='prev' we need to discard first record after sorting
437 $rows = array_slice( $rows, 1, $limit );
438 // Get the prev row from the first displayed row
439 $prevNamespace = $rows[0]->page_namespace;
440 $prevPageId = $rows[0]->page_id;
441 } else {
442 // Get the next row from the last displayed row
443 $nextNamespace = $rows[$numRows - 1]->page_namespace;
444 $nextPageId = $rows[$numRows - 1]->page_id;
445 $prevNamespace = false;
446 $prevPageId = false;
447 }
448 } else {
449 // If offset is not set disable prev link
450 $prevNamespace = $offsetPageID ? $rows[0]->page_namespace : false;
451 $prevPageId = $offsetPageID ? $rows[0]->page_id : false;
452 if ( $numRows > $limit ) {
453 // Get the next row from the last displayed row
454 $nextNamespace = $rows[$limit - 1]->page_namespace ?? false;
455 $nextPageId = $rows[$limit - 1]->page_id ?? false;
456 // Remove undisplayed rows
457 $rows = array_slice( $rows, 0, $limit );
458 } else {
459 $nextNamespace = false;
460 $nextPageId = false;
461 }
462 }
463
464 // use LinkBatch to make sure, that all required data (associated with Titles)
465 // is loaded in one query
466 $lb = $this->linkBatchFactory->newLinkBatch();
467 foreach ( $rows as $row ) {
468 $lb->add( $row->page_namespace, $row->page_title );
469 }
470 $lb->execute();
471
472 if ( $level == 0 && !$this->including() ) {
473 $link = $this->getLinkRenderer()->makeLink(
474 $this->target,
475 null,
476 [],
477 $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
478 );
479
480 $msg = $this->msg( 'linkshere' )
481 ->params( $this->target->getPrefixedText() )
482 ->rawParams( $link )
483 ->parseAsBlock();
484 $out->addHTML( $msg );
485
486 $out->addWikiMsg( 'whatlinkshere-count', Message::numParam( count( $rows ) ) );
487
488 $prevnext = $this->getPrevNext( $prevNamespace, $prevPageId, $nextNamespace, $nextPageId );
489 $out->addHTML( $prevnext );
490 }
491 $out->addHTML( $this->listStart( $level ) );
492 foreach ( $rows as $row ) {
493 $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
494
495 if ( $row->rd_from && $level < 2 ) {
496 $out->addHTML( $this->listItem( $row, $nt, $target, true ) );
497 $this->showIndirectLinks(
498 $level + 1,
499 $nt,
500 $this->getConfig()->get( MainConfigNames::MaxRedirectLinksRetrieved )
501 );
502 $out->addHTML( Xml::closeElement( 'li' ) );
503 } else {
504 $out->addHTML( $this->listItem( $row, $nt, $target ) );
505 }
506 }
507
508 $out->addHTML( $this->listEnd() );
509
510 if ( $level == 0 && !$this->including() ) {
511 // @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable $prevnext is defined with $level is 0
512 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable prevnext is set when used
513 $out->addHTML( $prevnext );
514 }
515 }
516
517 protected function listStart( $level ) {
518 return Xml::openElement( 'ul', ( $level ? [] : [ 'id' => 'mw-whatlinkshere-list' ] ) );
519 }
520
521 private function listItem( stdClass $row, PageIdentity $nt, LinkTarget $target, bool $notClose = false ) {
522 $legacyTitle = $this->titleFactory->newFromPageIdentity( $nt );
523 $dirmark = $this->getLanguage()->getDirMark();
524
525 if ( $row->rd_from ) {
526 $query = [ 'redirect' => 'no' ];
527 } else {
528 $query = [];
529 }
530
531 $link = $this->getLinkRenderer()->makeKnownLink(
532 $nt,
533 null,
534 $row->page_is_redirect ? [ 'class' => 'mw-redirect' ] : [],
535 $query
536 );
537
538 // Display properties (redirect or template)
539 $propsText = '';
540 $props = [];
541 if ( (string)$row->rd_fragment !== '' ) {
542 $props[] = $this->msg( 'whatlinkshere-sectionredir' )
543 ->rawParams( $this->getLinkRenderer()->makeLink(
544 $target->createFragmentTarget( $row->rd_fragment ),
545 $row->rd_fragment
546 ) )->escaped();
547 } elseif ( $row->rd_from ) {
548 $props[] = $this->msg( 'isredirect' )->escaped();
549 }
550 if ( $row->is_template ) {
551 $props[] = $this->msg( 'istemplate' )->escaped();
552 }
553 if ( $row->is_image ) {
554 $props[] = $this->msg( 'isimage' )->escaped();
555 }
556
557 $legacyTarget = $this->titleFactory->newFromLinkTarget( $target );
558 $this->getHookRunner()->onWhatLinksHereProps( $row, $legacyTitle, $legacyTarget, $props );
559
560 if ( count( $props ) ) {
561 $propsText = $this->msg( 'parentheses' )
562 ->rawParams( $this->getLanguage()->semicolonList( $props ) )->escaped();
563 }
564
565 # Space for utilities links, with a what-links-here link provided
566 $wlhLink = $this->wlhLink(
567 $legacyTitle,
568 $this->msg( 'whatlinkshere-links' )->text(),
569 $this->msg( 'editlink' )->text()
570 );
571 $wlh = Html::rawElement(
572 'span',
573 [ 'class' => 'mw-whatlinkshere-tools' ],
574 $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped()
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}
761
766class_alias( SpecialWhatLinksHere::class, 'SpecialWhatLinksHere' );
getRequest()
getAuthority()
const NS_FILE
Definition Defines.php:71
const NS_MAIN
Definition Defines.php:65
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:207
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:467
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
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition Html.php:240
static closeElement( $element)
Returns "</$element>".
Definition Html.php:304
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()
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:79
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:1067
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1862
Module of static functions for generating XML.
Definition Xml.php:37
Factory class for SearchEngine.
Build SELECT queries with a fluent interface.
Represents the target of a wiki link.
createFragmentTarget(string $fragment)
Create a new LinkTarget with a different fragment on the same page.
Interface for objects (potentially) representing an editable wiki page.
Provide primary and replica IDatabase connections.
A database connection without write operations.