41 protected $limits = [ 20, 50, 100, 250, 500 ];
44 parent::__construct(
'Whatlinkshere' );
71 if ( $par !==
null ) {
78 $this->target = Title::newFromText(
$opts->
getValue(
'target' ) );
79 if ( !$this->target ) {
87 $this->
getSkin()->setRelevantTitle( $this->target );
89 $this->selfTitle = $this->
getPageTitle( $this->target->getPrefixedDBkey() );
91 $out->setPageTitle( $this->
msg(
'whatlinkshere-title', $this->target->getPrefixedText() ) );
92 $out->addBacklinkSubtitle( $this->target );
113 $hidelinks = $this->opts->getValue(
'hidelinks' );
114 $hideredirs = $this->opts->getValue(
'hideredirs' );
115 $hidetrans = $this->opts->getValue(
'hidetrans' );
118 $fetchlinks = ( !$hidelinks || !$hideredirs );
122 $conds[
'pagelinks'] = [
126 $conds[
'templatelinks'] = [
130 $conds[
'imagelinks'] = [
134 $namespace = $this->opts->getValue(
'namespace' );
135 $invert = $this->opts->getValue(
'invert' );
136 $nsComparison = ( $invert ?
'!= ' :
'= ' ) .
$dbr->addQuotes( $namespace );
137 if ( is_int( $namespace ) ) {
138 $conds[
'pagelinks'][] =
"pl_from_namespace $nsComparison";
139 $conds[
'templatelinks'][] =
"tl_from_namespace $nsComparison";
140 $conds[
'imagelinks'][] =
"il_from_namespace $nsComparison";
144 $conds[
'templatelinks'][] =
"tl_from >= $from";
145 $conds[
'pagelinks'][] =
"pl_from >= $from";
146 $conds[
'imagelinks'][] =
"il_from >= $from";
150 $conds[
'pagelinks'][
'rd_from'] =
null;
151 } elseif ( $hidelinks ) {
152 $conds[
'pagelinks'][] =
'rd_from is NOT NULL';
155 $queryFunc =
function (
IDatabase $dbr, $table, $fromCol ) use (
159 $queryLimit = $limit + 1;
161 "rd_from = $fromCol",
163 'rd_interwiki = ' .
$dbr->addQuotes(
'' ) .
' OR rd_interwiki IS NULL'
167 $subQuery =
$dbr->buildSelectSubquery(
168 [ $table,
'redirect',
'page' ],
169 [ $fromCol,
'rd_from' ],
171 __CLASS__ .
'::showIndirectLinks',
173 [
'ORDER BY' => $fromCol,
'LIMIT' => 2 * $queryLimit,
'STRAIGHT_JOIN' ],
175 'page' => [
'JOIN',
"$fromCol = page_id" ],
176 'redirect' => [
'LEFT JOIN', $on ]
180 [
'page',
'temp_backlink_range' => $subQuery ],
181 [
'page_id',
'page_namespace',
'page_title',
'rd_from',
'page_is_redirect' ],
183 __CLASS__ .
'::showIndirectLinks',
184 [
'ORDER BY' =>
'page_id',
'LIMIT' => $queryLimit ],
185 [
'page' => [
'JOIN',
"$fromCol = page_id" ] ]
190 $plRes = $queryFunc(
$dbr,
'pagelinks',
'pl_from' );
194 $tlRes = $queryFunc(
$dbr,
'templatelinks',
'tl_from' );
197 if ( !$hideimages ) {
198 $ilRes = $queryFunc(
$dbr,
'imagelinks',
'il_from' );
201 if ( ( !$fetchlinks || !$plRes->numRows() )
202 && ( $hidetrans || !$tlRes->numRows() )
203 && ( $hideimages || !$ilRes->numRows() )
205 if ( $level == 0 && !$this->
including() ) {
209 if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
212 $msgKey = is_int( $namespace ) ?
'nolinkshere-ns' :
'nolinkshere';
217 $this->target->isRedirect() ? [
'redirect' =>
'no' ] : []
220 $errMsg = $this->
msg( $msgKey )
221 ->params( $this->target->getPrefixedText() )
224 $out->addHTML( $errMsg );
225 $out->setStatusCode( 404 );
236 foreach ( $plRes as $row ) {
237 $row->is_template = 0;
239 $rows[$row->page_id] = $row;
243 foreach ( $tlRes as $row ) {
244 $row->is_template = 1;
246 $rows[$row->page_id] = $row;
249 if ( !$hideimages ) {
250 foreach ( $ilRes as $row ) {
251 $row->is_template = 0;
253 $rows[$row->page_id] = $row;
259 $rows = array_values( $rows );
261 $numRows = count( $rows );
264 if ( $numRows > $limit ) {
267 $nextId = $rows[$limit]->page_id;
269 $rows = array_slice( $rows, 0, $limit );
279 foreach ( $rows as $row ) {
280 $lb->add( $row->page_namespace, $row->page_title );
284 if ( $level == 0 && !$this->
including() ) {
292 $this->target->isRedirect() ? [
'redirect' =>
'no' ] : []
295 $msg = $this->
msg(
'linkshere' )
296 ->params( $this->target->getPrefixedText() )
299 $out->addHTML( $msg );
301 $prevnext = $this->
getPrevNext( $prevId, $nextId );
302 $out->addHTML( $prevnext );
304 $out->addHTML( $this->
listStart( $level ) );
305 foreach ( $rows as $row ) {
306 $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
308 if ( $row->rd_from && $level < 2 ) {
313 $this->
getConfig()->
get(
'MaxRedirectLinksRetrieved' )
315 $out->addHTML( Xml::closeElement(
'li' ) );
321 $out->addHTML( $this->
listEnd() );
323 if ( $level == 0 && !$this->
including() ) {
324 $out->addHTML( $prevnext );
329 return Xml::openElement(
'ul', ( $level ? [] : [
'id' =>
'mw-whatlinkshere-list' ] ) );
335 # local message cache
336 static $msgcache =
null;
337 if ( $msgcache ===
null ) {
338 static $msgs = [
'isredirect',
'istemplate',
'semicolon-separator',
339 'whatlinkshere-links',
'isimage',
'editlink' ];
341 foreach ( $msgs as $msg ) {
342 $msgcache[$msg] = $this->
msg( $msg )->escaped();
346 if ( $row->rd_from ) {
347 $query = [
'redirect' =>
'no' ];
355 $row->page_is_redirect ? [
'class' =>
'mw-redirect' ] : [],
362 if ( $row->rd_from ) {
363 $props[] = $msgcache[
'isredirect'];
365 if ( $row->is_template ) {
366 $props[] = $msgcache[
'istemplate'];
368 if ( $row->is_image ) {
369 $props[] = $msgcache[
'isimage'];
372 Hooks::run(
'WhatLinksHereProps', [ $row, $nt,
$target, &$props ] );
374 if ( count( $props ) ) {
375 $propsText = $this->
msg(
'parentheses' )
376 ->rawParams( implode( $msgcache[
'semicolon-separator'], $props ) )->escaped();
379 # Space for utilities links, with a what-links-here link provided
380 $wlhLink = $this->
wlhLink( $nt, $msgcache[
'whatlinkshere-links'], $msgcache[
'editlink'] );
381 $wlh = Xml::wrapClass(
382 $this->
msg(
'parentheses' )->rawParams( $wlhLink )->escaped(),
383 'mw-whatlinkshere-tools'
387 Xml::openElement(
'li' ) .
"$link $propsText $dirmark $wlh\n" :
388 Xml::tags(
'li',
null,
"$link $propsText $dirmark $wlh" ) .
"\n";
392 return Xml::closeElement(
'ul' );
403 if ( $text !==
null ) {
420 MediaWikiServices::getInstance()
422 ->userHasRight( $this->
getUser(),
'edit' ) &&
424 ContentHandler::getForTitle(
$target )->supportsDirectEditing()
426 if ( $editText !==
null ) {
434 [
'action' =>
'edit' ]
443 if ( $text !==
null ) {
456 $currentLimit = $this->opts->getValue(
'limit' );
457 $prev = $this->
msg(
'whatlinkshere-prev' )->numParams( $currentLimit )->escaped();
458 $next = $this->
msg(
'whatlinkshere-next' )->numParams( $currentLimit )->escaped();
460 $changed = $this->opts->getChangedValues();
461 unset( $changed[
'target'] );
463 if ( $prevId != 0 ) {
464 $overrides = [
'from' => $this->opts->getValue(
'back' ) ];
465 $prev = $this->
makeSelfLink( $prev, array_merge( $changed, $overrides ) );
467 if ( $nextId != 0 ) {
468 $overrides = [
'from' => $nextId,
'back' => $prevId ];
469 $next = $this->
makeSelfLink( $next, array_merge( $changed, $overrides ) );
474 foreach ( $this->limits as $limit ) {
475 $prettyLimit = htmlspecialchars(
$lang->formatNum( $limit ) );
476 $overrides = [
'limit' => $limit ];
477 $limitLinks[] = $this->
makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) );
480 $nums =
$lang->pipeList( $limitLinks );
482 return $this->
msg(
'viewprevnext' )->rawParams( $prev, $next, $nums )->escaped();
487 $this->opts->consumeValue(
'target' );
489 $this->opts->consumeValues( [
'back',
'from' ] );
492 $namespace = $this->opts->consumeValue(
'namespace' );
493 $nsinvert = $this->opts->consumeValue(
'invert' );
496 $f = Xml::openElement(
'form', [
'action' =>
wfScript() ] );
498 # Values that should not be forgotten
499 $f .= Html::hidden(
'title', $this->
getPageTitle()->getPrefixedText() );
500 foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
501 $f .= Html::hidden( $name, $value );
504 $f .= Xml::fieldset( $this->
msg(
'whatlinkshere' )->text() );
506 # Target input (.mw-searchInput enables suggestions)
507 $f .= Xml::inputLabel( $this->
msg(
'whatlinkshere-page' )->text(),
'target',
508 'mw-whatlinkshere-target', 40,
$target, [
'class' =>
'mw-searchInput' ] );
513 $f .= Html::namespaceSelector(
515 'selected' => $namespace,
517 'label' => $this->
msg(
'namespace' )->text(),
518 'in-user-lang' =>
true,
520 'name' =>
'namespace',
522 'class' =>
'namespaceselector',
528 $this->
msg(
'invert' )->text(),
532 [
'title' => $this->
msg(
'tooltip-whatlinkshere-invert' )->text() ]
538 $f .= Xml::submitButton( $this->
msg(
'whatlinkshere-submit' )->text() );
541 $f .= Xml::closeElement(
'fieldset' ) . Xml::closeElement(
'form' ) .
"\n";
552 $show = $this->
msg(
'show' )->escaped();
553 $hide = $this->
msg(
'hide' )->escaped();
555 $changed = $this->opts->getChangedValues();
556 unset( $changed[
'target'] );
559 $types = [
'hidetrans',
'hidelinks',
'hideredirs' ];
560 if ( $this->target->getNamespace() ==
NS_FILE ) {
561 $types[] =
'hideimages';
567 foreach ( $types as
$type ) {
568 $chosen = $this->opts->getValue(
$type );
569 $msg = $chosen ? $show : $hide;
570 $overrides = [
$type => !$chosen ];
571 $links[] = $this->
msg(
"whatlinkshere-{$type}" )->rawParams(
572 $this->
makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped();
575 return Xml::fieldset(
576 $this->
msg(
'whatlinkshere-filters' )->text(),
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Marks HTML that shouldn't be escaped.
Shortcut to construct an includable special page.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
getSkin()
Shortcut to get the skin being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
including( $x=null)
Whether the special page is being evaluated via transclusion.
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
MediaWiki Linker LinkRenderer null $linkRenderer
Implements Special:Whatlinkshere.
execute( $par)
Default execute method Checks user permissions.
wlhLink(Title $target, $text, $editText)
listItem( $row, $nt, $target, $notClose=false)
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
getFilterPanel()
Create filter panel.
showIndirectLinks( $level, $target, $limit, $from=0, $back=0)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
makeSelfLink( $text, $query)
getPrevNext( $prevId, $nextId)
Represents a title within MediaWiki.
getNamespace()
Get the namespace index, i.e.
getDBkey()
Get the main part with underscores.
getPrefixedText()
Get the prefixed title with spaces.
if(!isset( $args[0])) $lang