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