41 protected $limits = [ 20, 50, 100, 250, 500 ];
44 parent::__construct(
'Whatlinkshere' );
57 $opts->
add(
'namespace',
'', FormOptions::INTNULL );
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' );
120 $fetchredirs = $hidelinks && !$hideredirs;
124 $conds[
'redirect'] = [
128 $conds[
'pagelinks'] = [
132 $conds[
'templatelinks'] = [
136 $conds[
'imagelinks'] = [
140 $namespace = $this->opts->getValue(
'namespace' );
141 $invert = $this->opts->getValue(
'invert' );
142 $nsComparison = ( $invert ?
'!= ' :
'= ' ) .
$dbr->addQuotes( $namespace );
143 if ( is_int( $namespace ) ) {
144 $conds[
'redirect'][] =
"page_namespace $nsComparison";
145 $conds[
'pagelinks'][] =
"pl_from_namespace $nsComparison";
146 $conds[
'templatelinks'][] =
"tl_from_namespace $nsComparison";
147 $conds[
'imagelinks'][] =
"il_from_namespace $nsComparison";
151 $conds[
'redirect'][] =
"rd_from >= $from";
152 $conds[
'templatelinks'][] =
"tl_from >= $from";
153 $conds[
'pagelinks'][] =
"pl_from >= $from";
154 $conds[
'imagelinks'][] =
"il_from >= $from";
161 $conds[
'pagelinks'][
'rd_from'] =
null;
164 $queryFunc =
function (
IDatabase $dbr, $table, $fromCol ) use (
168 $queryLimit = $limit + 1;
170 "rd_from = $fromCol",
172 'rd_interwiki = ' .
$dbr->addQuotes(
'' ) .
' OR rd_interwiki IS NULL'
176 $subQuery =
$dbr->newSelectQueryBuilder()
178 ->fields( [ $fromCol,
'rd_from',
'rd_fragment' ] )
179 ->conds( $conds[$table] )
180 ->orderBy( $fromCol )
181 ->limit( 2 * $queryLimit )
182 ->leftJoin(
'redirect',
'redirect', $on );
184 return $dbr->newSelectQueryBuilder()
185 ->table( $subQuery,
'temp_backlink_range' )
186 ->join(
'page',
'page',
"$fromCol = page_id" )
187 ->fields( [
'page_id',
'page_namespace',
'page_title',
188 'rd_from',
'rd_fragment',
'page_is_redirect' ] )
189 ->orderBy(
'page_id' )
190 ->limit( $queryLimit )
191 ->caller( __CLASS__ .
'::showIndirectLinks' )
195 if ( $fetchredirs ) {
196 $rdRes =
$dbr->select(
197 [
'redirect',
'page' ],
198 [
'page_id',
'page_namespace',
'page_title',
'rd_from',
'rd_fragment',
'page_is_redirect' ],
201 [
'ORDER BY' =>
'rd_from',
'LIMIT' => $limit + 1 ],
202 [
'page' => [
'JOIN',
'rd_from = page_id' ] ]
207 $plRes = $queryFunc(
$dbr,
'pagelinks',
'pl_from' );
211 $tlRes = $queryFunc(
$dbr,
'templatelinks',
'tl_from' );
214 if ( !$hideimages ) {
215 $ilRes = $queryFunc(
$dbr,
'imagelinks',
'il_from' );
218 if ( ( !$fetchredirs || !$rdRes->numRows() )
219 && ( $hidelinks || !$plRes->numRows() )
220 && ( $hidetrans || !$tlRes->numRows() )
221 && ( $hideimages || !$ilRes->numRows() )
223 if ( $level == 0 && !$this->
including() ) {
227 if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
230 $msgKey = is_int( $namespace ) ?
'nolinkshere-ns' :
'nolinkshere';
235 $this->target->isRedirect() ? [
'redirect' =>
'no' ] : []
238 $errMsg = $this->
msg( $msgKey )
239 ->params( $this->target->getPrefixedText() )
242 $out->addHTML( $errMsg );
243 $out->setStatusCode( 404 );
253 if ( $fetchredirs ) {
254 foreach ( $rdRes as $row ) {
255 $row->is_template = 0;
257 $rows[$row->page_id] = $row;
261 foreach ( $plRes as $row ) {
262 $row->is_template = 0;
264 $rows[$row->page_id] = $row;
268 foreach ( $tlRes as $row ) {
269 $row->is_template = 1;
271 $rows[$row->page_id] = $row;
274 if ( !$hideimages ) {
275 foreach ( $ilRes as $row ) {
276 $row->is_template = 0;
278 $rows[$row->page_id] = $row;
284 $rows = array_values( $rows );
286 $numRows = count( $rows );
289 if ( $numRows > $limit ) {
292 $nextId = $rows[$limit]->page_id;
294 $rows = array_slice( $rows, 0, $limit );
304 foreach ( $rows as $row ) {
305 $lb->add( $row->page_namespace, $row->page_title );
309 if ( $level == 0 && !$this->
including() ) {
317 $this->target->isRedirect() ? [
'redirect' =>
'no' ] : []
320 $msg = $this->
msg(
'linkshere' )
321 ->params( $this->target->getPrefixedText() )
324 $out->addHTML( $msg );
328 $prevnext = $this->
getPrevNext( $prevId, $nextId );
329 $out->addHTML( $prevnext );
331 $out->addHTML( $this->
listStart( $level ) );
332 foreach ( $rows as $row ) {
333 $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
335 if ( $row->rd_from && $level < 2 ) {
340 $this->
getConfig()->
get(
'MaxRedirectLinksRetrieved' )
342 $out->addHTML( Xml::closeElement(
'li' ) );
348 $out->addHTML( $this->
listEnd() );
350 if ( $level == 0 && !$this->
including() ) {
351 $out->addHTML( $prevnext );
356 return Xml::openElement(
'ul', ( $level ? [] : [
'id' =>
'mw-whatlinkshere-list' ] ) );
362 # local message cache
363 static $msgcache =
null;
364 if ( $msgcache ===
null ) {
365 static $msgs = [
'isredirect',
'istemplate',
'semicolon-separator',
366 'whatlinkshere-links',
'isimage',
'editlink' ];
368 foreach ( $msgs as $msg ) {
369 $msgcache[$msg] = $this->
msg( $msg )->escaped();
373 if ( $row->rd_from ) {
374 $query = [
'redirect' =>
'no' ];
382 $row->page_is_redirect ? [
'class' =>
'mw-redirect' ] : [],
389 if ( (
string)$row->rd_fragment !==
'' ) {
390 $props[] = $this->
msg(
'whatlinkshere-sectionredir' )
395 } elseif ( $row->rd_from ) {
396 $props[] = $msgcache[
'isredirect'];
398 if ( $row->is_template ) {
399 $props[] = $msgcache[
'istemplate'];
401 if ( $row->is_image ) {
402 $props[] = $msgcache[
'isimage'];
407 if ( count( $props ) ) {
408 $propsText = $this->
msg(
'parentheses' )
409 ->rawParams( implode( $msgcache[
'semicolon-separator'], $props ) )->escaped();
412 # Space for utilities links, with a what-links-here link provided
413 $wlhLink = $this->
wlhLink( $nt, $msgcache[
'whatlinkshere-links'], $msgcache[
'editlink'] );
414 $wlh = Xml::wrapClass(
415 $this->
msg(
'parentheses' )->rawParams( $wlhLink )->escaped(),
416 'mw-whatlinkshere-tools'
420 Xml::openElement(
'li' ) .
"$link $propsText $dirmark $wlh\n" :
421 Xml::tags(
'li',
null,
"$link $propsText $dirmark $wlh" ) .
"\n";
425 return Xml::closeElement(
'ul' );
436 if ( $text !==
null ) {
453 MediaWikiServices::getInstance()
455 ->userHasRight( $this->
getUser(),
'edit' ) &&
457 MediaWikiServices::getInstance()
458 ->getContentHandlerFactory()
460 ->supportsDirectEditing()
462 if ( $editText !==
null ) {
470 [
'action' =>
'edit' ]
479 if ( $text !==
null ) {
492 $currentLimit = $this->opts->getValue(
'limit' );
493 $prev = $this->
msg(
'whatlinkshere-prev' )->numParams( $currentLimit )->escaped();
494 $next = $this->
msg(
'whatlinkshere-next' )->numParams( $currentLimit )->escaped();
496 $changed = $this->opts->getChangedValues();
497 unset( $changed[
'target'] );
499 if ( $prevId != 0 ) {
500 $overrides = [
'from' => $this->opts->getValue(
'back' ) ];
501 $prev = $this->
makeSelfLink( $prev, array_merge( $changed, $overrides ) );
503 if ( $nextId != 0 ) {
504 $overrides = [
'from' => $nextId,
'back' => $prevId ];
505 $next = $this->
makeSelfLink( $next, array_merge( $changed, $overrides ) );
510 foreach ( $this->limits as $limit ) {
511 $prettyLimit = htmlspecialchars(
$lang->formatNum( $limit ) );
512 $overrides = [
'limit' => $limit ];
513 $limitLinks[] = $this->
makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) );
516 $nums =
$lang->pipeList( $limitLinks );
518 return $this->
msg(
'viewprevnext' )->rawParams( $prev, $next, $nums )->escaped();
523 $this->opts->consumeValue(
'target' );
525 $this->opts->consumeValues( [
'back',
'from' ] );
528 $namespace = $this->opts->consumeValue(
'namespace' );
529 $nsinvert = $this->opts->consumeValue(
'invert' );
532 $f = Xml::openElement(
'form', [
'action' =>
wfScript() ] );
534 # Values that should not be forgotten
535 $f .= Html::hidden(
'title', $this->
getPageTitle()->getPrefixedText() );
536 foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
537 $f .= Html::hidden( $name, $value );
540 $f .= Xml::fieldset( $this->
msg(
'whatlinkshere' )->text() );
542 # Target input (.mw-searchInput enables suggestions)
543 $f .= Xml::inputLabel( $this->
msg(
'whatlinkshere-page' )->text(),
'target',
544 'mw-whatlinkshere-target', 40,
$target, [
'class' =>
'mw-searchInput' ] );
549 $f .= Html::namespaceSelector(
551 'selected' => $namespace,
553 'label' => $this->
msg(
'namespace' )->text(),
554 'in-user-lang' =>
true,
556 'name' =>
'namespace',
558 'class' =>
'namespaceselector',
564 $this->
msg(
'invert' )->text(),
568 [
'title' => $this->
msg(
'tooltip-whatlinkshere-invert' )->text() ]
574 $f .= Xml::submitButton( $this->
msg(
'whatlinkshere-submit' )->text() );
577 $f .= Xml::closeElement(
'fieldset' ) . Xml::closeElement(
'form' ) .
"\n";
588 $show = $this->
msg(
'show' )->escaped();
589 $hide = $this->
msg(
'hide' )->escaped();
591 $changed = $this->opts->getChangedValues();
592 unset( $changed[
'target'] );
595 $types = [
'hidetrans',
'hidelinks',
'hideredirs' ];
596 if ( $this->target->getNamespace() ==
NS_FILE ) {
597 $types[] =
'hideimages';
603 foreach ( $types as
$type ) {
604 $chosen = $this->opts->getValue(
$type );
605 $msg = $chosen ? $show : $hide;
606 $overrides = [
$type => !$chosen ];
607 $links[] = $this->
msg(
"whatlinkshere-{$type}" )->rawParams(
608 $this->
makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped();
611 return Xml::fieldset(
612 $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.
createFragmentTarget( $fragment)
Creates a new Title for a different fragment of the same page.
getDBkey()
Get the main part with underscores.
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
getPrefixedText()
Get the prefixed title with spaces.
if(!isset( $args[0])) $lang