41 protected $limits = [ 20, 50, 100, 250, 500 ];
44 parent::__construct(
'Whatlinkshere' );
71 if ( $par !==
null ) {
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 ) {
308 if ( $row->rd_from && $level < 2 ) {
313 $this->
getConfig()->
get(
'MaxRedirectLinksRetrieved' )
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'];
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'] );
382 $this->
msg(
'parentheses' )->rawParams( $wlhLink )->escaped(),
383 'mw-whatlinkshere-tools'
388 Xml::tags(
'li',
null,
"$link $propsText $dirmark $wlh" ) .
"\n";
403 if ( $text !==
null ) {
420 MediaWikiServices::getInstance()
421 ->getPermissionManager()
422 ->userHasRight( $this->
getUser(),
'edit' ) &&
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' );
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 );
506 # Target input (.mw-searchInput enables suggestions)
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() ]
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();
576 $this->
msg(
'whatlinkshere-filters' )->text(),