MediaWiki
REL1_24
|
00001 <?php 00029 class SpecialWhatLinksHere extends IncludableSpecialPage { 00031 protected $opts; 00032 00033 protected $selfTitle; 00034 00036 protected $target; 00037 00038 protected $limits = array( 20, 50, 100, 250, 500 ); 00039 00040 public function __construct() { 00041 parent::__construct( 'Whatlinkshere' ); 00042 } 00043 00044 function execute( $par ) { 00045 $out = $this->getOutput(); 00046 00047 $this->setHeaders(); 00048 $this->outputHeader(); 00049 00050 $opts = new FormOptions(); 00051 00052 $opts->add( 'target', '' ); 00053 $opts->add( 'namespace', '', FormOptions::INTNULL ); 00054 $opts->add( 'limit', $this->getConfig()->get( 'QueryPageDefaultLimit' ) ); 00055 $opts->add( 'from', 0 ); 00056 $opts->add( 'back', 0 ); 00057 $opts->add( 'hideredirs', false ); 00058 $opts->add( 'hidetrans', false ); 00059 $opts->add( 'hidelinks', false ); 00060 $opts->add( 'hideimages', false ); 00061 00062 $opts->fetchValuesFromRequest( $this->getRequest() ); 00063 $opts->validateIntBounds( 'limit', 0, 5000 ); 00064 00065 // Give precedence to subpage syntax 00066 if ( $par !== null ) { 00067 $opts->setValue( 'target', $par ); 00068 } 00069 00070 // Bind to member variable 00071 $this->opts = $opts; 00072 00073 $this->target = Title::newFromURL( $opts->getValue( 'target' ) ); 00074 if ( !$this->target ) { 00075 $out->addHTML( $this->whatlinkshereForm() ); 00076 00077 return; 00078 } 00079 00080 $this->getSkin()->setRelevantTitle( $this->target ); 00081 00082 $this->selfTitle = $this->getPageTitle( $this->target->getPrefixedDBkey() ); 00083 00084 $out->setPageTitle( $this->msg( 'whatlinkshere-title', $this->target->getPrefixedText() ) ); 00085 $out->addBacklinkSubtitle( $this->target ); 00086 $this->showIndirectLinks( 00087 0, 00088 $this->target, 00089 $opts->getValue( 'limit' ), 00090 $opts->getValue( 'from' ), 00091 $opts->getValue( 'back' ) 00092 ); 00093 } 00094 00102 function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) { 00103 $out = $this->getOutput(); 00104 $dbr = wfGetDB( DB_SLAVE ); 00105 00106 $hidelinks = $this->opts->getValue( 'hidelinks' ); 00107 $hideredirs = $this->opts->getValue( 'hideredirs' ); 00108 $hidetrans = $this->opts->getValue( 'hidetrans' ); 00109 $hideimages = $target->getNamespace() != NS_FILE || $this->opts->getValue( 'hideimages' ); 00110 00111 $fetchlinks = ( !$hidelinks || !$hideredirs ); 00112 00113 // Build query conds in concert for all three tables... 00114 $conds['pagelinks'] = array( 00115 'pl_namespace' => $target->getNamespace(), 00116 'pl_title' => $target->getDBkey(), 00117 ); 00118 $conds['templatelinks'] = array( 00119 'tl_namespace' => $target->getNamespace(), 00120 'tl_title' => $target->getDBkey(), 00121 ); 00122 $conds['imagelinks'] = array( 00123 'il_to' => $target->getDBkey(), 00124 ); 00125 00126 $useLinkNamespaceDBFields = $this->getConfig()->get( 'UseLinkNamespaceDBFields' ); 00127 $namespace = $this->opts->getValue( 'namespace' ); 00128 if ( is_int( $namespace ) ) { 00129 if ( $useLinkNamespaceDBFields ) { 00130 $conds['pagelinks']['pl_from_namespace'] = $namespace; 00131 $conds['templatelinks']['tl_from_namespace'] = $namespace; 00132 $conds['imagelinks']['il_from_namespace'] = $namespace; 00133 } else { 00134 $conds['pagelinks']['page_namespace'] = $namespace; 00135 $conds['templatelinks']['page_namespace'] = $namespace; 00136 $conds['imagelinks']['page_namespace'] = $namespace; 00137 } 00138 } 00139 00140 if ( $from ) { 00141 $conds['templatelinks'][] = "tl_from >= $from"; 00142 $conds['pagelinks'][] = "pl_from >= $from"; 00143 $conds['imagelinks'][] = "il_from >= $from"; 00144 } 00145 00146 if ( $hideredirs ) { 00147 $conds['pagelinks']['rd_from'] = null; 00148 } elseif ( $hidelinks ) { 00149 $conds['pagelinks'][] = 'rd_from is NOT NULL'; 00150 } 00151 00152 $queryFunc = function ( $dbr, $table, $fromCol ) use ( $conds, $target, $limit, $useLinkNamespaceDBFields ) { 00153 // Read an extra row as an at-end check 00154 $queryLimit = $limit + 1; 00155 $on = array( 00156 "rd_from = $fromCol", 00157 'rd_title' => $target->getDBkey(), 00158 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL' 00159 ); 00160 if ( $useLinkNamespaceDBFields ) { // migration check 00161 $on['rd_namespace'] = $target->getNamespace(); 00162 } 00163 // Inner LIMIT is 2X in case of stale backlinks with wrong namespaces 00164 $subQuery = $dbr->selectSqlText( 00165 array( $table, 'page', 'redirect' ), 00166 array( $fromCol, 'rd_from' ), 00167 $conds[$table], 00168 __CLASS__ . '::showIndirectLinks', 00169 array( 'ORDER BY' => $fromCol, 'LIMIT' => 2 * $queryLimit ), 00170 array( 00171 'page' => array( 'INNER JOIN', "$fromCol = page_id" ), 00172 'redirect' => array( 'LEFT JOIN', $on ) 00173 ) 00174 ); 00175 return $dbr->select( 00176 array( 'page', 'temp_backlink_range' => "($subQuery)" ), 00177 array( 'page_id', 'page_namespace', 'page_title', 'rd_from' ), 00178 array(), 00179 __CLASS__ . '::showIndirectLinks', 00180 array( 'ORDER BY' => 'page_id', 'LIMIT' => $queryLimit ), 00181 array( 'page' => array( 'INNER JOIN', "$fromCol = page_id" ) ) 00182 ); 00183 }; 00184 00185 if ( $fetchlinks ) { 00186 $plRes = $queryFunc( $dbr, 'pagelinks', 'pl_from' ); 00187 } 00188 00189 if ( !$hidetrans ) { 00190 $tlRes = $queryFunc( $dbr, 'templatelinks', 'tl_from' ); 00191 } 00192 00193 if ( !$hideimages ) { 00194 $ilRes = $queryFunc( $dbr, 'imagelinks', 'il_from' ); 00195 } 00196 00197 if ( ( !$fetchlinks || !$plRes->numRows() ) 00198 && ( $hidetrans || !$tlRes->numRows() ) 00199 && ( $hideimages || !$ilRes->numRows() ) 00200 ) { 00201 if ( 0 == $level ) { 00202 if ( !$this->including() ) { 00203 $out->addHTML( $this->whatlinkshereForm() ); 00204 00205 // Show filters only if there are links 00206 if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) { 00207 $out->addHTML( $this->getFilterPanel() ); 00208 } 00209 $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere'; 00210 $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() ); 00211 $out->setStatusCode( 404 ); 00212 } 00213 } 00214 00215 return; 00216 } 00217 00218 // Read the rows into an array and remove duplicates 00219 // templatelinks comes second so that the templatelinks row overwrites the 00220 // pagelinks row, so we get (inclusion) rather than nothing 00221 if ( $fetchlinks ) { 00222 foreach ( $plRes as $row ) { 00223 $row->is_template = 0; 00224 $row->is_image = 0; 00225 $rows[$row->page_id] = $row; 00226 } 00227 } 00228 if ( !$hidetrans ) { 00229 foreach ( $tlRes as $row ) { 00230 $row->is_template = 1; 00231 $row->is_image = 0; 00232 $rows[$row->page_id] = $row; 00233 } 00234 } 00235 if ( !$hideimages ) { 00236 foreach ( $ilRes as $row ) { 00237 $row->is_template = 0; 00238 $row->is_image = 1; 00239 $rows[$row->page_id] = $row; 00240 } 00241 } 00242 00243 // Sort by key and then change the keys to 0-based indices 00244 ksort( $rows ); 00245 $rows = array_values( $rows ); 00246 00247 $numRows = count( $rows ); 00248 00249 // Work out the start and end IDs, for prev/next links 00250 if ( $numRows > $limit ) { 00251 // More rows available after these ones 00252 // Get the ID from the last row in the result set 00253 $nextId = $rows[$limit]->page_id; 00254 // Remove undisplayed rows 00255 $rows = array_slice( $rows, 0, $limit ); 00256 } else { 00257 // No more rows after 00258 $nextId = false; 00259 } 00260 $prevId = $from; 00261 00262 if ( $level == 0 ) { 00263 if ( !$this->including() ) { 00264 $out->addHTML( $this->whatlinkshereForm() ); 00265 $out->addHTML( $this->getFilterPanel() ); 00266 $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() ); 00267 00268 $prevnext = $this->getPrevNext( $prevId, $nextId ); 00269 $out->addHTML( $prevnext ); 00270 } 00271 } 00272 $out->addHTML( $this->listStart( $level ) ); 00273 foreach ( $rows as $row ) { 00274 $nt = Title::makeTitle( $row->page_namespace, $row->page_title ); 00275 00276 if ( $row->rd_from && $level < 2 ) { 00277 $out->addHTML( $this->listItem( $row, $nt, $target, true ) ); 00278 $this->showIndirectLinks( $level + 1, $nt, $this->getConfig()->get( 'MaxRedirectLinksRetrieved' ) ); 00279 $out->addHTML( Xml::closeElement( 'li' ) ); 00280 } else { 00281 $out->addHTML( $this->listItem( $row, $nt, $target ) ); 00282 } 00283 } 00284 00285 $out->addHTML( $this->listEnd() ); 00286 00287 if ( $level == 0 ) { 00288 if ( !$this->including() ) { 00289 $out->addHTML( $prevnext ); 00290 } 00291 } 00292 } 00293 00294 protected function listStart( $level ) { 00295 return Xml::openElement( 'ul', ( $level ? array() : array( 'id' => 'mw-whatlinkshere-list' ) ) ); 00296 } 00297 00298 protected function listItem( $row, $nt, $target, $notClose = false ) { 00299 $dirmark = $this->getLanguage()->getDirMark(); 00300 00301 # local message cache 00302 static $msgcache = null; 00303 if ( $msgcache === null ) { 00304 static $msgs = array( 'isredirect', 'istemplate', 'semicolon-separator', 00305 'whatlinkshere-links', 'isimage' ); 00306 $msgcache = array(); 00307 foreach ( $msgs as $msg ) { 00308 $msgcache[$msg] = $this->msg( $msg )->escaped(); 00309 } 00310 } 00311 00312 if ( $row->rd_from ) { 00313 $query = array( 'redirect' => 'no' ); 00314 } else { 00315 $query = array(); 00316 } 00317 00318 $link = Linker::linkKnown( 00319 $nt, 00320 null, 00321 array(), 00322 $query 00323 ); 00324 00325 // Display properties (redirect or template) 00326 $propsText = ''; 00327 $props = array(); 00328 if ( $row->rd_from ) { 00329 $props[] = $msgcache['isredirect']; 00330 } 00331 if ( $row->is_template ) { 00332 $props[] = $msgcache['istemplate']; 00333 } 00334 if ( $row->is_image ) { 00335 $props[] = $msgcache['isimage']; 00336 } 00337 00338 wfRunHooks( 'WhatLinksHereProps', array( $row, $nt, $target, &$props ) ); 00339 00340 if ( count( $props ) ) { 00341 $propsText = $this->msg( 'parentheses' ) 00342 ->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped(); 00343 } 00344 00345 # Space for utilities links, with a what-links-here link provided 00346 $wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'] ); 00347 $wlh = Xml::wrapClass( 00348 $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(), 00349 'mw-whatlinkshere-tools' 00350 ); 00351 00352 return $notClose ? 00353 Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" : 00354 Xml::tags( 'li', null, "$link $propsText $dirmark $wlh" ) . "\n"; 00355 } 00356 00357 protected function listEnd() { 00358 return Xml::closeElement( 'ul' ); 00359 } 00360 00361 protected function wlhLink( Title $target, $text ) { 00362 static $title = null; 00363 if ( $title === null ) { 00364 $title = $this->getPageTitle(); 00365 } 00366 00367 return Linker::linkKnown( 00368 $title, 00369 $text, 00370 array(), 00371 array( 'target' => $target->getPrefixedText() ) 00372 ); 00373 } 00374 00375 function makeSelfLink( $text, $query ) { 00376 return Linker::linkKnown( 00377 $this->selfTitle, 00378 $text, 00379 array(), 00380 $query 00381 ); 00382 } 00383 00384 function getPrevNext( $prevId, $nextId ) { 00385 $currentLimit = $this->opts->getValue( 'limit' ); 00386 $prev = $this->msg( 'whatlinkshere-prev' )->numParams( $currentLimit )->escaped(); 00387 $next = $this->msg( 'whatlinkshere-next' )->numParams( $currentLimit )->escaped(); 00388 00389 $changed = $this->opts->getChangedValues(); 00390 unset( $changed['target'] ); // Already in the request title 00391 00392 if ( 0 != $prevId ) { 00393 $overrides = array( 'from' => $this->opts->getValue( 'back' ) ); 00394 $prev = $this->makeSelfLink( $prev, array_merge( $changed, $overrides ) ); 00395 } 00396 if ( 0 != $nextId ) { 00397 $overrides = array( 'from' => $nextId, 'back' => $prevId ); 00398 $next = $this->makeSelfLink( $next, array_merge( $changed, $overrides ) ); 00399 } 00400 00401 $limitLinks = array(); 00402 $lang = $this->getLanguage(); 00403 foreach ( $this->limits as $limit ) { 00404 $prettyLimit = htmlspecialchars( $lang->formatNum( $limit ) ); 00405 $overrides = array( 'limit' => $limit ); 00406 $limitLinks[] = $this->makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) ); 00407 } 00408 00409 $nums = $lang->pipeList( $limitLinks ); 00410 00411 return $this->msg( 'viewprevnext' )->rawParams( $prev, $next, $nums )->escaped(); 00412 } 00413 00414 function whatlinkshereForm() { 00415 // We get nicer value from the title object 00416 $this->opts->consumeValue( 'target' ); 00417 // Reset these for new requests 00418 $this->opts->consumeValues( array( 'back', 'from' ) ); 00419 00420 $target = $this->target ? $this->target->getPrefixedText() : ''; 00421 $namespace = $this->opts->consumeValue( 'namespace' ); 00422 00423 # Build up the form 00424 $f = Xml::openElement( 'form', array( 'action' => wfScript() ) ); 00425 00426 # Values that should not be forgotten 00427 $f .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ); 00428 foreach ( $this->opts->getUnconsumedValues() as $name => $value ) { 00429 $f .= Html::hidden( $name, $value ); 00430 } 00431 00432 $f .= Xml::fieldset( $this->msg( 'whatlinkshere' )->text() ); 00433 00434 # Target input 00435 $f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target', 00436 'mw-whatlinkshere-target', 40, $target ); 00437 00438 $f .= ' '; 00439 00440 # Namespace selector 00441 $f .= Html::namespaceSelector( 00442 array( 00443 'selected' => $namespace, 00444 'all' => '', 00445 'label' => $this->msg( 'namespace' )->text() 00446 ), array( 00447 'name' => 'namespace', 00448 'id' => 'namespace', 00449 'class' => 'namespaceselector', 00450 ) 00451 ); 00452 00453 $f .= ' '; 00454 00455 # Submit 00456 $f .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ); 00457 00458 # Close 00459 $f .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n"; 00460 00461 return $f; 00462 } 00463 00469 function getFilterPanel() { 00470 $show = $this->msg( 'show' )->escaped(); 00471 $hide = $this->msg( 'hide' )->escaped(); 00472 00473 $changed = $this->opts->getChangedValues(); 00474 unset( $changed['target'] ); // Already in the request title 00475 00476 $links = array(); 00477 $types = array( 'hidetrans', 'hidelinks', 'hideredirs' ); 00478 if ( $this->target->getNamespace() == NS_FILE ) { 00479 $types[] = 'hideimages'; 00480 } 00481 00482 // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans', 00483 // 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages' 00484 // To be sure they will be found by grep 00485 foreach ( $types as $type ) { 00486 $chosen = $this->opts->getValue( $type ); 00487 $msg = $chosen ? $show : $hide; 00488 $overrides = array( $type => !$chosen ); 00489 $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams( 00490 $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped(); 00491 } 00492 00493 return Xml::fieldset( 00494 $this->msg( 'whatlinkshere-filters' )->text(), 00495 $this->getLanguage()->pipeList( $links ) 00496 ); 00497 } 00498 00499 protected function getGroupName() { 00500 return 'pagetools'; 00501 } 00502 }