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