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