MediaWiki  REL1_23
SpecialWhatlinkshere.php
Go to the documentation of this file.
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 }