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