MediaWiki
REL1_22
|
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 00177 if ( !$hidetrans ) { 00178 $options['ORDER BY'] = 'tl_from'; 00179 $tlRes = $dbr->select( array( 'templatelinks', 'page', 'redirect' ), $fields, 00180 $tlConds, __METHOD__, $options, 00181 $joinConds 00182 ); 00183 } 00184 00185 if ( !$hideimages ) { 00186 $options['ORDER BY'] = 'il_from'; 00187 $ilRes = $dbr->select( array( 'imagelinks', 'page', 'redirect' ), $fields, 00188 $ilConds, __METHOD__, $options, 00189 $joinConds 00190 ); 00191 } 00192 00193 if ( ( !$fetchlinks || !$plRes->numRows() ) && ( $hidetrans || !$tlRes->numRows() ) && ( $hideimages || !$ilRes->numRows() ) ) { 00194 if ( 0 == $level ) { 00195 $out->addHTML( $this->whatlinkshereForm() ); 00196 00197 // Show filters only if there are links 00198 if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) { 00199 $out->addHTML( $this->getFilterPanel() ); 00200 } 00201 00202 $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere'; 00203 $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() ); 00204 } 00205 return; 00206 } 00207 00208 // Read the rows into an array and remove duplicates 00209 // templatelinks comes second so that the templatelinks row overwrites the 00210 // pagelinks row, so we get (inclusion) rather than nothing 00211 if ( $fetchlinks ) { 00212 foreach ( $plRes as $row ) { 00213 $row->is_template = 0; 00214 $row->is_image = 0; 00215 $rows[$row->page_id] = $row; 00216 } 00217 } 00218 if ( !$hidetrans ) { 00219 foreach ( $tlRes as $row ) { 00220 $row->is_template = 1; 00221 $row->is_image = 0; 00222 $rows[$row->page_id] = $row; 00223 } 00224 } 00225 if ( !$hideimages ) { 00226 foreach ( $ilRes as $row ) { 00227 $row->is_template = 0; 00228 $row->is_image = 1; 00229 $rows[$row->page_id] = $row; 00230 } 00231 } 00232 00233 // Sort by key and then change the keys to 0-based indices 00234 ksort( $rows ); 00235 $rows = array_values( $rows ); 00236 00237 $numRows = count( $rows ); 00238 00239 // Work out the start and end IDs, for prev/next links 00240 if ( $numRows > $limit ) { 00241 // More rows available after these ones 00242 // Get the ID from the last row in the result set 00243 $nextId = $rows[$limit]->page_id; 00244 // Remove undisplayed rows 00245 $rows = array_slice( $rows, 0, $limit ); 00246 } else { 00247 // No more rows after 00248 $nextId = false; 00249 } 00250 $prevId = $from; 00251 00252 if ( $level == 0 ) { 00253 $out->addHTML( $this->whatlinkshereForm() ); 00254 $out->addHTML( $this->getFilterPanel() ); 00255 $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() ); 00256 00257 $prevnext = $this->getPrevNext( $prevId, $nextId ); 00258 $out->addHTML( $prevnext ); 00259 } 00260 00261 $out->addHTML( $this->listStart( $level ) ); 00262 foreach ( $rows as $row ) { 00263 $nt = Title::makeTitle( $row->page_namespace, $row->page_title ); 00264 00265 if ( $row->rd_from && $level < 2 ) { 00266 $out->addHTML( $this->listItem( $row, $nt, true ) ); 00267 $this->showIndirectLinks( $level + 1, $nt, $wgMaxRedirectLinksRetrieved ); 00268 $out->addHTML( Xml::closeElement( 'li' ) ); 00269 } else { 00270 $out->addHTML( $this->listItem( $row, $nt ) ); 00271 } 00272 } 00273 00274 $out->addHTML( $this->listEnd() ); 00275 00276 if ( $level == 0 ) { 00277 $out->addHTML( $prevnext ); 00278 } 00279 } 00280 00281 protected function listStart( $level ) { 00282 return Xml::openElement( 'ul', ( $level ? array() : array( 'id' => 'mw-whatlinkshere-list' ) ) ); 00283 } 00284 00285 protected function listItem( $row, $nt, $notClose = false ) { 00286 $dirmark = $this->getLanguage()->getDirMark(); 00287 00288 # local message cache 00289 static $msgcache = null; 00290 if ( $msgcache === null ) { 00291 static $msgs = array( 'isredirect', 'istemplate', 'semicolon-separator', 00292 'whatlinkshere-links', 'isimage' ); 00293 $msgcache = array(); 00294 foreach ( $msgs as $msg ) { 00295 $msgcache[$msg] = $this->msg( $msg )->escaped(); 00296 } 00297 } 00298 00299 if ( $row->rd_from ) { 00300 $query = array( 'redirect' => 'no' ); 00301 } else { 00302 $query = array(); 00303 } 00304 00305 $link = Linker::linkKnown( 00306 $nt, 00307 null, 00308 array(), 00309 $query 00310 ); 00311 00312 // Display properties (redirect or template) 00313 $propsText = ''; 00314 $props = array(); 00315 if ( $row->rd_from ) { 00316 $props[] = $msgcache['isredirect']; 00317 } 00318 if ( $row->is_template ) { 00319 $props[] = $msgcache['istemplate']; 00320 } 00321 if ( $row->is_image ) { 00322 $props[] = $msgcache['isimage']; 00323 } 00324 00325 if ( count( $props ) ) { 00326 $propsText = $this->msg( 'parentheses' )->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped(); 00327 } 00328 00329 # Space for utilities links, with a what-links-here link provided 00330 $wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'] ); 00331 $wlh = Xml::wrapClass( $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(), 'mw-whatlinkshere-tools' ); 00332 00333 return $notClose ? 00334 Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" : 00335 Xml::tags( 'li', null, "$link $propsText $dirmark $wlh" ) . "\n"; 00336 } 00337 00338 protected function listEnd() { 00339 return Xml::closeElement( 'ul' ); 00340 } 00341 00342 protected function wlhLink( Title $target, $text ) { 00343 static $title = null; 00344 if ( $title === null ) { 00345 $title = $this->getTitle(); 00346 } 00347 00348 return Linker::linkKnown( 00349 $title, 00350 $text, 00351 array(), 00352 array( 'target' => $target->getPrefixedText() ) 00353 ); 00354 } 00355 00356 function makeSelfLink( $text, $query ) { 00357 return Linker::linkKnown( 00358 $this->selfTitle, 00359 $text, 00360 array(), 00361 $query 00362 ); 00363 } 00364 00365 function getPrevNext( $prevId, $nextId ) { 00366 $currentLimit = $this->opts->getValue( 'limit' ); 00367 $prev = $this->msg( 'whatlinkshere-prev' )->numParams( $currentLimit )->escaped(); 00368 $next = $this->msg( 'whatlinkshere-next' )->numParams( $currentLimit )->escaped(); 00369 00370 $changed = $this->opts->getChangedValues(); 00371 unset( $changed['target'] ); // Already in the request title 00372 00373 if ( 0 != $prevId ) { 00374 $overrides = array( 'from' => $this->opts->getValue( 'back' ) ); 00375 $prev = $this->makeSelfLink( $prev, array_merge( $changed, $overrides ) ); 00376 } 00377 if ( 0 != $nextId ) { 00378 $overrides = array( 'from' => $nextId, 'back' => $prevId ); 00379 $next = $this->makeSelfLink( $next, array_merge( $changed, $overrides ) ); 00380 } 00381 00382 $limitLinks = array(); 00383 $lang = $this->getLanguage(); 00384 foreach ( $this->limits as $limit ) { 00385 $prettyLimit = htmlspecialchars( $lang->formatNum( $limit ) ); 00386 $overrides = array( 'limit' => $limit ); 00387 $limitLinks[] = $this->makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) ); 00388 } 00389 00390 $nums = $lang->pipeList( $limitLinks ); 00391 00392 return $this->msg( 'viewprevnext' )->rawParams( $prev, $next, $nums )->escaped(); 00393 } 00394 00395 function whatlinkshereForm() { 00396 global $wgScript; 00397 00398 // We get nicer value from the title object 00399 $this->opts->consumeValue( 'target' ); 00400 // Reset these for new requests 00401 $this->opts->consumeValues( array( 'back', 'from' ) ); 00402 00403 $target = $this->target ? $this->target->getPrefixedText() : ''; 00404 $namespace = $this->opts->consumeValue( 'namespace' ); 00405 00406 # Build up the form 00407 $f = Xml::openElement( 'form', array( 'action' => $wgScript ) ); 00408 00409 # Values that should not be forgotten 00410 $f .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ); 00411 foreach ( $this->opts->getUnconsumedValues() as $name => $value ) { 00412 $f .= Html::hidden( $name, $value ); 00413 } 00414 00415 $f .= Xml::fieldset( $this->msg( 'whatlinkshere' )->text() ); 00416 00417 # Target input 00418 $f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target', 00419 'mw-whatlinkshere-target', 40, $target ); 00420 00421 $f .= ' '; 00422 00423 # Namespace selector 00424 $f .= Html::namespaceSelector( 00425 array( 00426 'selected' => $namespace, 00427 'all' => '', 00428 'label' => $this->msg( 'namespace' )->text() 00429 ), array( 00430 'name' => 'namespace', 00431 'id' => 'namespace', 00432 'class' => 'namespaceselector', 00433 ) 00434 ); 00435 00436 $f .= ' '; 00437 00438 # Submit 00439 $f .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ); 00440 00441 # Close 00442 $f .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n"; 00443 00444 return $f; 00445 } 00446 00452 function getFilterPanel() { 00453 $show = $this->msg( 'show' )->escaped(); 00454 $hide = $this->msg( 'hide' )->escaped(); 00455 00456 $changed = $this->opts->getChangedValues(); 00457 unset( $changed['target'] ); // Already in the request title 00458 00459 $links = array(); 00460 $types = array( 'hidetrans', 'hidelinks', 'hideredirs' ); 00461 if ( $this->target->getNamespace() == NS_FILE ) { 00462 $types[] = 'hideimages'; 00463 } 00464 00465 // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans', 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages' 00466 // To be sure they will be found by grep 00467 foreach ( $types as $type ) { 00468 $chosen = $this->opts->getValue( $type ); 00469 $msg = $chosen ? $show : $hide; 00470 $overrides = array( $type => !$chosen ); 00471 $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams( 00472 $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped(); 00473 } 00474 return Xml::fieldset( $this->msg( 'whatlinkshere-filters' )->text(), $this->getLanguage()->pipeList( $links ) ); 00475 } 00476 00477 protected function getGroupName() { 00478 return 'pagetools'; 00479 } 00480 }