[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/specials/ -> SpecialWhatlinkshere.php (source)

   1  <?php
   2  /**
   3   * Implements Special:Whatlinkshere
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @todo Use some variant of Pager or something; the pagination here is lousy.
  22   */
  23  
  24  /**
  25   * Implements Special:Whatlinkshere
  26   *
  27   * @ingroup SpecialPage
  28   */
  29  class SpecialWhatLinksHere extends IncludableSpecialPage {
  30      /** @var FormOptions */
  31      protected $opts;
  32  
  33      protected $selfTitle;
  34  
  35      /** @var Title */
  36      protected $target;
  37  
  38      protected $limits = array( 20, 50, 100, 250, 500 );
  39  
  40  	public function __construct() {
  41          parent::__construct( 'Whatlinkshere' );
  42      }
  43  
  44  	function execute( $par ) {
  45          $out = $this->getOutput();
  46  
  47          $this->setHeaders();
  48          $this->outputHeader();
  49  
  50          $opts = new FormOptions();
  51  
  52          $opts->add( 'target', '' );
  53          $opts->add( 'namespace', '', FormOptions::INTNULL );
  54          $opts->add( 'limit', $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
  55          $opts->add( 'from', 0 );
  56          $opts->add( 'back', 0 );
  57          $opts->add( 'hideredirs', false );
  58          $opts->add( 'hidetrans', false );
  59          $opts->add( 'hidelinks', false );
  60          $opts->add( 'hideimages', false );
  61  
  62          $opts->fetchValuesFromRequest( $this->getRequest() );
  63          $opts->validateIntBounds( 'limit', 0, 5000 );
  64  
  65          // Give precedence to subpage syntax
  66          if ( $par !== null ) {
  67              $opts->setValue( 'target', $par );
  68          }
  69  
  70          // Bind to member variable
  71          $this->opts = $opts;
  72  
  73          $this->target = Title::newFromURL( $opts->getValue( 'target' ) );
  74          if ( !$this->target ) {
  75              $out->addHTML( $this->whatlinkshereForm() );
  76  
  77              return;
  78          }
  79  
  80          $this->getSkin()->setRelevantTitle( $this->target );
  81  
  82          $this->selfTitle = $this->getPageTitle( $this->target->getPrefixedDBkey() );
  83  
  84          $out->setPageTitle( $this->msg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
  85          $out->addBacklinkSubtitle( $this->target );
  86          $this->showIndirectLinks(
  87              0,
  88              $this->target,
  89              $opts->getValue( 'limit' ),
  90              $opts->getValue( 'from' ),
  91              $opts->getValue( 'back' )
  92          );
  93      }
  94  
  95      /**
  96       * @param int $level Recursion level
  97       * @param Title $target Target title
  98       * @param int $limit Number of entries to display
  99       * @param int $from Display from this article ID (default: 0)
 100       * @param int $back Display from this article ID at backwards scrolling (default: 0)
 101       */
 102  	function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) {
 103          $out = $this->getOutput();
 104          $dbr = wfGetDB( DB_SLAVE );
 105  
 106          $hidelinks = $this->opts->getValue( 'hidelinks' );
 107          $hideredirs = $this->opts->getValue( 'hideredirs' );
 108          $hidetrans = $this->opts->getValue( 'hidetrans' );
 109          $hideimages = $target->getNamespace() != NS_FILE || $this->opts->getValue( 'hideimages' );
 110  
 111          $fetchlinks = ( !$hidelinks || !$hideredirs );
 112  
 113          // Build query conds in concert for all three tables...
 114          $conds['pagelinks'] = array(
 115              'pl_namespace' => $target->getNamespace(),
 116              'pl_title' => $target->getDBkey(),
 117          );
 118          $conds['templatelinks'] = array(
 119              'tl_namespace' => $target->getNamespace(),
 120              'tl_title' => $target->getDBkey(),
 121          );
 122          $conds['imagelinks'] = array(
 123              'il_to' => $target->getDBkey(),
 124          );
 125  
 126          $useLinkNamespaceDBFields = $this->getConfig()->get( 'UseLinkNamespaceDBFields' );
 127          $namespace = $this->opts->getValue( 'namespace' );
 128          if ( is_int( $namespace ) ) {
 129              if ( $useLinkNamespaceDBFields ) {
 130                  $conds['pagelinks']['pl_from_namespace'] = $namespace;
 131                  $conds['templatelinks']['tl_from_namespace'] = $namespace;
 132                  $conds['imagelinks']['il_from_namespace'] = $namespace;
 133              } else {
 134                  $conds['pagelinks']['page_namespace'] = $namespace;
 135                  $conds['templatelinks']['page_namespace'] = $namespace;
 136                  $conds['imagelinks']['page_namespace'] = $namespace;
 137              }
 138          }
 139  
 140          if ( $from ) {
 141              $conds['templatelinks'][] = "tl_from >= $from";
 142              $conds['pagelinks'][] = "pl_from >= $from";
 143              $conds['imagelinks'][] = "il_from >= $from";
 144          }
 145  
 146          if ( $hideredirs ) {
 147              $conds['pagelinks']['rd_from'] = null;
 148          } elseif ( $hidelinks ) {
 149              $conds['pagelinks'][] = 'rd_from is NOT NULL';
 150          }
 151  
 152          $queryFunc = function ( $dbr, $table, $fromCol ) use ( $conds, $target, $limit, $useLinkNamespaceDBFields ) {
 153              // Read an extra row as an at-end check
 154              $queryLimit = $limit + 1;
 155              $on = array(
 156                  "rd_from = $fromCol",
 157                  'rd_title' => $target->getDBkey(),
 158                  'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'
 159              );
 160              if ( $useLinkNamespaceDBFields ) { // migration check
 161                  $on['rd_namespace'] = $target->getNamespace();
 162              }
 163              // Inner LIMIT is 2X in case of stale backlinks with wrong namespaces
 164              $subQuery = $dbr->selectSqlText(
 165                  array( $table, 'page', 'redirect' ),
 166                  array( $fromCol, 'rd_from' ),
 167                  $conds[$table],
 168                  __CLASS__ . '::showIndirectLinks',
 169                  array( 'ORDER BY' => $fromCol, 'LIMIT' => 2 * $queryLimit ),
 170                  array(
 171                      'page' => array( 'INNER JOIN', "$fromCol = page_id" ),
 172                      'redirect' => array( 'LEFT JOIN', $on )
 173                  )
 174              );
 175              return $dbr->select(
 176                  array( 'page', 'temp_backlink_range' => "($subQuery)" ),
 177                  array( 'page_id', 'page_namespace', 'page_title', 'rd_from' ),
 178                  array(),
 179                  __CLASS__ . '::showIndirectLinks',
 180                  array( 'ORDER BY' => 'page_id', 'LIMIT' => $queryLimit ),
 181                  array( 'page' => array( 'INNER JOIN', "$fromCol = page_id" ) )
 182              );
 183          };
 184  
 185          if ( $fetchlinks ) {
 186              $plRes = $queryFunc( $dbr, 'pagelinks', 'pl_from' );
 187          }
 188  
 189          if ( !$hidetrans ) {
 190              $tlRes = $queryFunc( $dbr, 'templatelinks', 'tl_from' );
 191          }
 192  
 193          if ( !$hideimages ) {
 194              $ilRes = $queryFunc( $dbr, 'imagelinks', 'il_from' );
 195          }
 196  
 197          if ( ( !$fetchlinks || !$plRes->numRows() )
 198              && ( $hidetrans || !$tlRes->numRows() )
 199              && ( $hideimages || !$ilRes->numRows() )
 200          ) {
 201              if ( 0 == $level ) {
 202                  if ( !$this->including() ) {
 203                      $out->addHTML( $this->whatlinkshereForm() );
 204  
 205                      // Show filters only if there are links
 206                      if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
 207                          $out->addHTML( $this->getFilterPanel() );
 208                      }
 209                      $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
 210                      $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
 211                      $out->setStatusCode( 404 );
 212                  }
 213              }
 214  
 215              return;
 216          }
 217  
 218          // Read the rows into an array and remove duplicates
 219          // templatelinks comes second so that the templatelinks row overwrites the
 220          // pagelinks row, so we get (inclusion) rather than nothing
 221          if ( $fetchlinks ) {
 222              foreach ( $plRes as $row ) {
 223                  $row->is_template = 0;
 224                  $row->is_image = 0;
 225                  $rows[$row->page_id] = $row;
 226              }
 227          }
 228          if ( !$hidetrans ) {
 229              foreach ( $tlRes as $row ) {
 230                  $row->is_template = 1;
 231                  $row->is_image = 0;
 232                  $rows[$row->page_id] = $row;
 233              }
 234          }
 235          if ( !$hideimages ) {
 236              foreach ( $ilRes as $row ) {
 237                  $row->is_template = 0;
 238                  $row->is_image = 1;
 239                  $rows[$row->page_id] = $row;
 240              }
 241          }
 242  
 243          // Sort by key and then change the keys to 0-based indices
 244          ksort( $rows );
 245          $rows = array_values( $rows );
 246  
 247          $numRows = count( $rows );
 248  
 249          // Work out the start and end IDs, for prev/next links
 250          if ( $numRows > $limit ) {
 251              // More rows available after these ones
 252              // Get the ID from the last row in the result set
 253              $nextId = $rows[$limit]->page_id;
 254              // Remove undisplayed rows
 255              $rows = array_slice( $rows, 0, $limit );
 256          } else {
 257              // No more rows after
 258              $nextId = false;
 259          }
 260          $prevId = $from;
 261  
 262          if ( $level == 0 ) {
 263              if ( !$this->including() ) {
 264                  $out->addHTML( $this->whatlinkshereForm() );
 265                  $out->addHTML( $this->getFilterPanel() );
 266                  $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
 267  
 268                  $prevnext = $this->getPrevNext( $prevId, $nextId );
 269                  $out->addHTML( $prevnext );
 270              }
 271          }
 272          $out->addHTML( $this->listStart( $level ) );
 273          foreach ( $rows as $row ) {
 274              $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
 275  
 276              if ( $row->rd_from && $level < 2 ) {
 277                  $out->addHTML( $this->listItem( $row, $nt, $target, true ) );
 278                  $this->showIndirectLinks( $level + 1, $nt, $this->getConfig()->get( 'MaxRedirectLinksRetrieved' ) );
 279                  $out->addHTML( Xml::closeElement( 'li' ) );
 280              } else {
 281                  $out->addHTML( $this->listItem( $row, $nt, $target ) );
 282              }
 283          }
 284  
 285          $out->addHTML( $this->listEnd() );
 286  
 287          if ( $level == 0 ) {
 288              if ( !$this->including() ) {
 289                  $out->addHTML( $prevnext );
 290              }
 291          }
 292      }
 293  
 294  	protected function listStart( $level ) {
 295          return Xml::openElement( 'ul', ( $level ? array() : array( 'id' => 'mw-whatlinkshere-list' ) ) );
 296      }
 297  
 298  	protected function listItem( $row, $nt, $target, $notClose = false ) {
 299          $dirmark = $this->getLanguage()->getDirMark();
 300  
 301          # local message cache
 302          static $msgcache = null;
 303          if ( $msgcache === null ) {
 304              static $msgs = array( 'isredirect', 'istemplate', 'semicolon-separator',
 305                  'whatlinkshere-links', 'isimage' );
 306              $msgcache = array();
 307              foreach ( $msgs as $msg ) {
 308                  $msgcache[$msg] = $this->msg( $msg )->escaped();
 309              }
 310          }
 311  
 312          if ( $row->rd_from ) {
 313              $query = array( 'redirect' => 'no' );
 314          } else {
 315              $query = array();
 316          }
 317  
 318          $link = Linker::linkKnown(
 319              $nt,
 320              null,
 321              array(),
 322              $query
 323          );
 324  
 325          // Display properties (redirect or template)
 326          $propsText = '';
 327          $props = array();
 328          if ( $row->rd_from ) {
 329              $props[] = $msgcache['isredirect'];
 330          }
 331          if ( $row->is_template ) {
 332              $props[] = $msgcache['istemplate'];
 333          }
 334          if ( $row->is_image ) {
 335              $props[] = $msgcache['isimage'];
 336          }
 337  
 338          wfRunHooks( 'WhatLinksHereProps', array( $row, $nt, $target, &$props ) );
 339  
 340          if ( count( $props ) ) {
 341              $propsText = $this->msg( 'parentheses' )
 342                  ->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped();
 343          }
 344  
 345          # Space for utilities links, with a what-links-here link provided
 346          $wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'] );
 347          $wlh = Xml::wrapClass(
 348              $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(),
 349              'mw-whatlinkshere-tools'
 350          );
 351  
 352          return $notClose ?
 353              Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" :
 354              Xml::tags( 'li', null, "$link $propsText $dirmark $wlh" ) . "\n";
 355      }
 356  
 357  	protected function listEnd() {
 358          return Xml::closeElement( 'ul' );
 359      }
 360  
 361  	protected function wlhLink( Title $target, $text ) {
 362          static $title = null;
 363          if ( $title === null ) {
 364              $title = $this->getPageTitle();
 365          }
 366  
 367          return Linker::linkKnown(
 368              $title,
 369              $text,
 370              array(),
 371              array( 'target' => $target->getPrefixedText() )
 372          );
 373      }
 374  
 375  	function makeSelfLink( $text, $query ) {
 376          return Linker::linkKnown(
 377              $this->selfTitle,
 378              $text,
 379              array(),
 380              $query
 381          );
 382      }
 383  
 384  	function getPrevNext( $prevId, $nextId ) {
 385          $currentLimit = $this->opts->getValue( 'limit' );
 386          $prev = $this->msg( 'whatlinkshere-prev' )->numParams( $currentLimit )->escaped();
 387          $next = $this->msg( 'whatlinkshere-next' )->numParams( $currentLimit )->escaped();
 388  
 389          $changed = $this->opts->getChangedValues();
 390          unset( $changed['target'] ); // Already in the request title
 391  
 392          if ( 0 != $prevId ) {
 393              $overrides = array( 'from' => $this->opts->getValue( 'back' ) );
 394              $prev = $this->makeSelfLink( $prev, array_merge( $changed, $overrides ) );
 395          }
 396          if ( 0 != $nextId ) {
 397              $overrides = array( 'from' => $nextId, 'back' => $prevId );
 398              $next = $this->makeSelfLink( $next, array_merge( $changed, $overrides ) );
 399          }
 400  
 401          $limitLinks = array();
 402          $lang = $this->getLanguage();
 403          foreach ( $this->limits as $limit ) {
 404              $prettyLimit = htmlspecialchars( $lang->formatNum( $limit ) );
 405              $overrides = array( 'limit' => $limit );
 406              $limitLinks[] = $this->makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) );
 407          }
 408  
 409          $nums = $lang->pipeList( $limitLinks );
 410  
 411          return $this->msg( 'viewprevnext' )->rawParams( $prev, $next, $nums )->escaped();
 412      }
 413  
 414  	function whatlinkshereForm() {
 415          // We get nicer value from the title object
 416          $this->opts->consumeValue( 'target' );
 417          // Reset these for new requests
 418          $this->opts->consumeValues( array( 'back', 'from' ) );
 419  
 420          $target = $this->target ? $this->target->getPrefixedText() : '';
 421          $namespace = $this->opts->consumeValue( 'namespace' );
 422  
 423          # Build up the form
 424          $f = Xml::openElement( 'form', array( 'action' => wfScript() ) );
 425  
 426          # Values that should not be forgotten
 427          $f .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
 428          foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
 429              $f .= Html::hidden( $name, $value );
 430          }
 431  
 432          $f .= Xml::fieldset( $this->msg( 'whatlinkshere' )->text() );
 433  
 434          # Target input
 435          $f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target',
 436              'mw-whatlinkshere-target', 40, $target );
 437  
 438          $f .= ' ';
 439  
 440          # Namespace selector
 441          $f .= Html::namespaceSelector(
 442              array(
 443                  'selected' => $namespace,
 444                  'all' => '',
 445                  'label' => $this->msg( 'namespace' )->text()
 446              ), array(
 447                  'name' => 'namespace',
 448                  'id' => 'namespace',
 449                  'class' => 'namespaceselector',
 450              )
 451          );
 452  
 453          $f .= ' ';
 454  
 455          # Submit
 456          $f .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() );
 457  
 458          # Close
 459          $f .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n";
 460  
 461          return $f;
 462      }
 463  
 464      /**
 465       * Create filter panel
 466       *
 467       * @return string HTML fieldset and filter panel with the show/hide links
 468       */
 469  	function getFilterPanel() {
 470          $show = $this->msg( 'show' )->escaped();
 471          $hide = $this->msg( 'hide' )->escaped();
 472  
 473          $changed = $this->opts->getChangedValues();
 474          unset( $changed['target'] ); // Already in the request title
 475  
 476          $links = array();
 477          $types = array( 'hidetrans', 'hidelinks', 'hideredirs' );
 478          if ( $this->target->getNamespace() == NS_FILE ) {
 479              $types[] = 'hideimages';
 480          }
 481  
 482          // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans',
 483          // 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
 484          // To be sure they will be found by grep
 485          foreach ( $types as $type ) {
 486              $chosen = $this->opts->getValue( $type );
 487              $msg = $chosen ? $show : $hide;
 488              $overrides = array( $type => !$chosen );
 489              $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams(
 490                  $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped();
 491          }
 492  
 493          return Xml::fieldset(
 494              $this->msg( 'whatlinkshere-filters' )->text(),
 495              $this->getLanguage()->pipeList( $links )
 496          );
 497      }
 498  
 499  	protected function getGroupName() {
 500          return 'pagetools';
 501      }
 502  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1