[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/actions/ -> HistoryAction.php (source)

   1  <?php
   2  /**
   3   * Page history
   4   *
   5   * Split off from Article.php and Skin.php, 2003-12-22
   6   *
   7   * This program is free software; you can redistribute it and/or modify
   8   * it under the terms of the GNU General Public License as published by
   9   * the Free Software Foundation; either version 2 of the License, or
  10   * (at your option) any later version.
  11   *
  12   * This program is distributed in the hope that it will be useful,
  13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15   * GNU General Public License for more details.
  16   *
  17   * You should have received a copy of the GNU General Public License along
  18   * with this program; if not, write to the Free Software Foundation, Inc.,
  19   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20   * http://www.gnu.org/copyleft/gpl.html
  21   *
  22   * @file
  23   * @ingroup Actions
  24   */
  25  
  26  /**
  27   * This class handles printing the history page for an article. In order to
  28   * be efficient, it uses timestamps rather than offsets for paging, to avoid
  29   * costly LIMIT,offset queries.
  30   *
  31   * Construct it by passing in an Article, and call $h->history() to print the
  32   * history.
  33   *
  34   * @ingroup Actions
  35   */
  36  class HistoryAction extends FormlessAction {
  37      const DIR_PREV = 0;
  38      const DIR_NEXT = 1;
  39  
  40      /** @var array Array of message keys and strings */
  41      public $message;
  42  
  43  	public function getName() {
  44          return 'history';
  45      }
  46  
  47  	public function requiresWrite() {
  48          return false;
  49      }
  50  
  51  	public function requiresUnblock() {
  52          return false;
  53      }
  54  
  55  	protected function getPageTitle() {
  56          return $this->msg( 'history-title', $this->getTitle()->getPrefixedText() )->text();
  57      }
  58  
  59  	protected function getDescription() {
  60          // Creation of a subtitle link pointing to [[Special:Log]]
  61          return Linker::linkKnown(
  62              SpecialPage::getTitleFor( 'Log' ),
  63              $this->msg( 'viewpagelogs' )->escaped(),
  64              array(),
  65              array( 'page' => $this->getTitle()->getPrefixedText() )
  66          );
  67      }
  68  
  69      /**
  70       * Get the Article object we are working on.
  71       * @return Page
  72       */
  73  	public function getArticle() {
  74          return $this->page;
  75      }
  76  
  77      /**
  78       * As we use the same small set of messages in various methods and that
  79       * they are called often, we call them once and save them in $this->message
  80       */
  81  	private function preCacheMessages() {
  82          // Precache various messages
  83          if ( !isset( $this->message ) ) {
  84              $msgs = array( 'cur', 'last', 'pipe-separator' );
  85              foreach ( $msgs as $msg ) {
  86                  $this->message[$msg] = $this->msg( $msg )->escaped();
  87              }
  88          }
  89      }
  90  
  91      /**
  92       * Print the history page for an article.
  93       */
  94  	function onView() {
  95          $out = $this->getOutput();
  96          $request = $this->getRequest();
  97  
  98          /**
  99           * Allow client caching.
 100           */
 101          if ( $out->checkLastModified( $this->page->getTouched() ) ) {
 102              return; // Client cache fresh and headers sent, nothing more to do.
 103          }
 104  
 105          wfProfileIn( __METHOD__ );
 106  
 107          $this->preCacheMessages();
 108          $config = $this->context->getConfig();
 109  
 110          # Fill in the file cache if not set already
 111          $useFileCache = $config->get( 'UseFileCache' );
 112          if ( $useFileCache && HTMLFileCache::useFileCache( $this->getContext() ) ) {
 113              $cache = new HTMLFileCache( $this->getTitle(), 'history' );
 114              if ( !$cache->isCacheGood( /* Assume up to date */ ) ) {
 115                  ob_start( array( &$cache, 'saveToFileCache' ) );
 116              }
 117          }
 118  
 119          // Setup page variables.
 120          $out->setFeedAppendQuery( 'action=history' );
 121          $out->addModules( 'mediawiki.action.history' );
 122          if ( $config->get( 'UseMediaWikiUIEverywhere' ) ) {
 123              $out = $this->getOutput();
 124              $out->addModuleStyles( array(
 125                  'mediawiki.ui.input',
 126                  'mediawiki.ui.checkbox',
 127              ) );
 128          }
 129  
 130          // Handle atom/RSS feeds.
 131          $feedType = $request->getVal( 'feed' );
 132          if ( $feedType ) {
 133              $this->feed( $feedType );
 134              wfProfileOut( __METHOD__ );
 135  
 136              return;
 137          }
 138  
 139          // Fail nicely if article doesn't exist.
 140          if ( !$this->page->exists() ) {
 141              $out->addWikiMsg( 'nohistory' );
 142              # show deletion/move log if there is an entry
 143              LogEventsList::showLogExtract(
 144                  $out,
 145                  array( 'delete', 'move' ),
 146                  $this->getTitle(),
 147                  '',
 148                  array( 'lim' => 10,
 149                      'conds' => array( "log_action != 'revision'" ),
 150                      'showIfEmpty' => false,
 151                      'msgKey' => array( 'moveddeleted-notice' )
 152                  )
 153              );
 154              wfProfileOut( __METHOD__ );
 155  
 156              return;
 157          }
 158  
 159          /**
 160           * Add date selector to quickly get to a certain time
 161           */
 162          $year = $request->getInt( 'year' );
 163          $month = $request->getInt( 'month' );
 164          $tagFilter = $request->getVal( 'tagfilter' );
 165          $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
 166  
 167          /**
 168           * Option to show only revisions that have been (partially) hidden via RevisionDelete
 169           */
 170          if ( $request->getBool( 'deleted' ) ) {
 171              $conds = array( 'rev_deleted != 0' );
 172          } else {
 173              $conds = array();
 174          }
 175          if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
 176              $checkDeleted = Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(),
 177                  'deleted', 'mw-show-deleted-only', $request->getBool( 'deleted' ) ) . "\n";
 178          } else {
 179              $checkDeleted = '';
 180          }
 181  
 182          // Add the general form
 183          $action = htmlspecialchars( wfScript() );
 184          $out->addHTML(
 185              "<form action=\"$action\" method=\"get\" id=\"mw-history-searchform\">" .
 186              Xml::fieldset(
 187                  $this->msg( 'history-fieldset-title' )->text(),
 188                  false,
 189                  array( 'id' => 'mw-history-search' )
 190              ) .
 191              Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
 192              Html::hidden( 'action', 'history' ) . "\n" .
 193              Xml::dateMenu(
 194                  ( $year == null ? MWTimestamp::getLocalInstance()->format( 'Y' ) : $year ),
 195                  $month
 196              ) . '&#160;' .
 197              ( $tagSelector ? ( implode( '&#160;', $tagSelector ) . '&#160;' ) : '' ) .
 198              $checkDeleted .
 199              Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n" .
 200              '</fieldset></form>'
 201          );
 202  
 203          wfRunHooks( 'PageHistoryBeforeList', array( &$this->page, $this->getContext() ) );
 204  
 205          // Create and output the list.
 206          $pager = new HistoryPager( $this, $year, $month, $tagFilter, $conds );
 207          $out->addHTML(
 208              $pager->getNavigationBar() .
 209              $pager->getBody() .
 210              $pager->getNavigationBar()
 211          );
 212          $out->preventClickjacking( $pager->getPreventClickjacking() );
 213  
 214          wfProfileOut( __METHOD__ );
 215      }
 216  
 217      /**
 218       * Fetch an array of revisions, specified by a given limit, offset and
 219       * direction. This is now only used by the feeds. It was previously
 220       * used by the main UI but that's now handled by the pager.
 221       *
 222       * @param int $limit The limit number of revisions to get
 223       * @param int $offset
 224       * @param int $direction Either self::DIR_PREV or self::DIR_NEXT
 225       * @return ResultWrapper
 226       */
 227  	function fetchRevisions( $limit, $offset, $direction ) {
 228          // Fail if article doesn't exist.
 229          if ( !$this->getTitle()->exists() ) {
 230              return new FakeResultWrapper( array() );
 231          }
 232  
 233          $dbr = wfGetDB( DB_SLAVE );
 234  
 235          if ( $direction === self::DIR_PREV ) {
 236              list( $dirs, $oper ) = array( "ASC", ">=" );
 237          } else { /* $direction === self::DIR_NEXT */
 238              list( $dirs, $oper ) = array( "DESC", "<=" );
 239          }
 240  
 241          if ( $offset ) {
 242              $offsets = array( "rev_timestamp $oper " . $dbr->addQuotes( $dbr->timestamp( $offset ) ) );
 243          } else {
 244              $offsets = array();
 245          }
 246  
 247          $page_id = $this->page->getId();
 248  
 249          return $dbr->select( 'revision',
 250              Revision::selectFields(),
 251              array_merge( array( 'rev_page' => $page_id ), $offsets ),
 252              __METHOD__,
 253              array( 'ORDER BY' => "rev_timestamp $dirs",
 254                  'USE INDEX' => 'page_timestamp', 'LIMIT' => $limit )
 255          );
 256      }
 257  
 258      /**
 259       * Output a subscription feed listing recent edits to this page.
 260       *
 261       * @param string $type Feed type
 262       */
 263  	function feed( $type ) {
 264          if ( !FeedUtils::checkFeedOutput( $type ) ) {
 265              return;
 266          }
 267          $request = $this->getRequest();
 268  
 269          $feedClasses = $this->context->getConfig()->get( 'FeedClasses' );
 270          /** @var RSSFeed|AtomFeed $feed */
 271          $feed = new $feedClasses[$type](
 272              $this->getTitle()->getPrefixedText() . ' - ' .
 273              $this->msg( 'history-feed-title' )->inContentLanguage()->text(),
 274              $this->msg( 'history-feed-description' )->inContentLanguage()->text(),
 275              $this->getTitle()->getFullURL( 'action=history' )
 276          );
 277  
 278          // Get a limit on number of feed entries. Provide a sane default
 279          // of 10 if none is defined (but limit to $wgFeedLimit max)
 280          $limit = $request->getInt( 'limit', 10 );
 281          $limit = min(
 282              max( $limit, 1 ),
 283              $this->context->getConfig()->get( 'FeedLimit' )
 284          );
 285  
 286          $items = $this->fetchRevisions( $limit, 0, self::DIR_NEXT );
 287  
 288          // Generate feed elements enclosed between header and footer.
 289          $feed->outHeader();
 290          if ( $items->numRows() ) {
 291              foreach ( $items as $row ) {
 292                  $feed->outItem( $this->feedItem( $row ) );
 293              }
 294          } else {
 295              $feed->outItem( $this->feedEmpty() );
 296          }
 297          $feed->outFooter();
 298      }
 299  
 300  	function feedEmpty() {
 301          return new FeedItem(
 302              $this->msg( 'nohistory' )->inContentLanguage()->text(),
 303              $this->msg( 'history-feed-empty' )->inContentLanguage()->parseAsBlock(),
 304              $this->getTitle()->getFullURL(),
 305              wfTimestamp( TS_MW ),
 306              '',
 307              $this->getTitle()->getTalkPage()->getFullURL()
 308          );
 309      }
 310  
 311      /**
 312       * Generate a FeedItem object from a given revision table row
 313       * Borrows Recent Changes' feed generation functions for formatting;
 314       * includes a diff to the previous revision (if any).
 315       *
 316       * @param stdClass|array $row Database row
 317       * @return FeedItem
 318       */
 319  	function feedItem( $row ) {
 320          $rev = new Revision( $row );
 321          $rev->setTitle( $this->getTitle() );
 322          $text = FeedUtils::formatDiffRow(
 323              $this->getTitle(),
 324              $this->getTitle()->getPreviousRevisionID( $rev->getId() ),
 325              $rev->getId(),
 326              $rev->getTimestamp(),
 327              $rev->getComment()
 328          );
 329          if ( $rev->getComment() == '' ) {
 330              global $wgContLang;
 331              $title = $this->msg( 'history-feed-item-nocomment',
 332                  $rev->getUserText(),
 333                  $wgContLang->timeanddate( $rev->getTimestamp() ),
 334                  $wgContLang->date( $rev->getTimestamp() ),
 335                  $wgContLang->time( $rev->getTimestamp() ) )->inContentLanguage()->text();
 336          } else {
 337              $title = $rev->getUserText() .
 338                  $this->msg( 'colon-separator' )->inContentLanguage()->text() .
 339                  FeedItem::stripComment( $rev->getComment() );
 340          }
 341  
 342          return new FeedItem(
 343              $title,
 344              $text,
 345              $this->getTitle()->getFullURL( 'diff=' . $rev->getId() . '&oldid=prev' ),
 346              $rev->getTimestamp(),
 347              $rev->getUserText(),
 348              $this->getTitle()->getTalkPage()->getFullURL()
 349          );
 350      }
 351  }
 352  
 353  /**
 354   * @ingroup Pager
 355   * @ingroup Actions
 356   */
 357  class HistoryPager extends ReverseChronologicalPager {
 358      /**
 359       * @var bool|stdClass
 360       */
 361      public $lastRow = false;
 362  
 363      public $counter, $historyPage, $buttons, $conds;
 364  
 365      protected $oldIdChecked;
 366  
 367      protected $preventClickjacking = false;
 368      /**
 369       * @var array
 370       */
 371      protected $parentLens;
 372  
 373      /**
 374       * @param HistoryAction $historyPage
 375       * @param string $year
 376       * @param string $month
 377       * @param string $tagFilter
 378       * @param array $conds
 379       */
 380  	function __construct( $historyPage, $year = '', $month = '', $tagFilter = '', $conds = array() ) {
 381          parent::__construct( $historyPage->getContext() );
 382          $this->historyPage = $historyPage;
 383          $this->tagFilter = $tagFilter;
 384          $this->getDateCond( $year, $month );
 385          $this->conds = $conds;
 386      }
 387  
 388      // For hook compatibility...
 389  	function getArticle() {
 390          return $this->historyPage->getArticle();
 391      }
 392  
 393  	function getSqlComment() {
 394          if ( $this->conds ) {
 395              return 'history page filtered'; // potentially slow, see CR r58153
 396          } else {
 397              return 'history page unfiltered';
 398          }
 399      }
 400  
 401  	function getQueryInfo() {
 402          $queryInfo = array(
 403              'tables' => array( 'revision', 'user' ),
 404              'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ),
 405              'conds' => array_merge(
 406                  array( 'rev_page' => $this->getWikiPage()->getId() ),
 407                  $this->conds ),
 408              'options' => array( 'USE INDEX' => array( 'revision' => 'page_timestamp' ) ),
 409              'join_conds' => array( 'user' => Revision::userJoinCond() ),
 410          );
 411          ChangeTags::modifyDisplayQuery(
 412              $queryInfo['tables'],
 413              $queryInfo['fields'],
 414              $queryInfo['conds'],
 415              $queryInfo['join_conds'],
 416              $queryInfo['options'],
 417              $this->tagFilter
 418          );
 419          wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) );
 420  
 421          return $queryInfo;
 422      }
 423  
 424  	function getIndexField() {
 425          return 'rev_timestamp';
 426      }
 427  
 428      /**
 429       * @param stdClass $row
 430       * @return string
 431       */
 432  	function formatRow( $row ) {
 433          if ( $this->lastRow ) {
 434              $latest = ( $this->counter == 1 && $this->mIsFirst );
 435              $firstInList = $this->counter == 1;
 436              $this->counter++;
 437              $s = $this->historyLine( $this->lastRow, $row,
 438                  $this->getTitle()->getNotificationTimestamp( $this->getUser() ), $latest, $firstInList );
 439          } else {
 440              $s = '';
 441          }
 442          $this->lastRow = $row;
 443  
 444          return $s;
 445      }
 446  
 447  	function doBatchLookups() {
 448          # Do a link batch query
 449          $this->mResult->seek( 0 );
 450          $batch = new LinkBatch();
 451          $revIds = array();
 452          foreach ( $this->mResult as $row ) {
 453              if ( $row->rev_parent_id ) {
 454                  $revIds[] = $row->rev_parent_id;
 455              }
 456              if ( !is_null( $row->user_name ) ) {
 457                  $batch->add( NS_USER, $row->user_name );
 458                  $batch->add( NS_USER_TALK, $row->user_name );
 459              } else { # for anons or usernames of imported revisions
 460                  $batch->add( NS_USER, $row->rev_user_text );
 461                  $batch->add( NS_USER_TALK, $row->rev_user_text );
 462              }
 463          }
 464          $this->parentLens = Revision::getParentLengths( $this->mDb, $revIds );
 465          $batch->execute();
 466          $this->mResult->seek( 0 );
 467      }
 468  
 469      /**
 470       * Creates begin of history list with a submit button
 471       *
 472       * @return string HTML output
 473       */
 474  	function getStartBody() {
 475          $this->lastRow = false;
 476          $this->counter = 1;
 477          $this->oldIdChecked = 0;
 478  
 479          $this->getOutput()->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
 480          $s = Html::openElement( 'form', array( 'action' => wfScript(),
 481              'id' => 'mw-history-compare' ) ) . "\n";
 482          $s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n";
 483          $s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
 484  
 485          // Button container stored in $this->buttons for re-use in getEndBody()
 486          $this->buttons = '<div>';
 487          $className = 'historysubmit mw-history-compareselectedversions-button';
 488          if ( $this->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
 489              $className .= ' mw-ui-button mw-ui-constructive';
 490          }
 491          $this->buttons .= $this->submitButton( $this->msg( 'compareselectedversions' )->text(),
 492              array( 'class' => $className )
 493                  + Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' )
 494          ) . "\n";
 495  
 496          if ( $this->getUser()->isAllowed( 'deleterevision' ) ) {
 497              $this->buttons .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' );
 498          }
 499          $this->buttons .= '</div>';
 500  
 501          $s .= $this->buttons;
 502          $s .= '<ul id="pagehistory">' . "\n";
 503  
 504          return $s;
 505      }
 506  
 507  	private function getRevisionButton( $name, $msg ) {
 508          $this->preventClickjacking();
 509          # Note bug #20966, <button> is non-standard in IE<8
 510          $element = Html::element(
 511              'button',
 512              array(
 513                  'type' => 'submit',
 514                  'name' => $name,
 515                  'value' => '1',
 516                  'class' => "historysubmit mw-history-$name-button",
 517              ),
 518              $this->msg( $msg )->text()
 519          ) . "\n";
 520          return $element;
 521      }
 522  
 523  	function getEndBody() {
 524          if ( $this->lastRow ) {
 525              $latest = $this->counter == 1 && $this->mIsFirst;
 526              $firstInList = $this->counter == 1;
 527              if ( $this->mIsBackwards ) {
 528                  # Next row is unknown, but for UI reasons, probably exists if an offset has been specified
 529                  if ( $this->mOffset == '' ) {
 530                      $next = null;
 531                  } else {
 532                      $next = 'unknown';
 533                  }
 534              } else {
 535                  # The next row is the past-the-end row
 536                  $next = $this->mPastTheEndRow;
 537              }
 538              $this->counter++;
 539              $s = $this->historyLine( $this->lastRow, $next,
 540                  $this->getTitle()->getNotificationTimestamp( $this->getUser() ), $latest, $firstInList );
 541          } else {
 542              $s = '';
 543          }
 544          $s .= "</ul>\n";
 545          # Add second buttons only if there is more than one rev
 546          if ( $this->getNumRows() > 2 ) {
 547              $s .= $this->buttons;
 548          }
 549          $s .= '</form>';
 550  
 551          return $s;
 552      }
 553  
 554      /**
 555       * Creates a submit button
 556       *
 557       * @param string $message Text of the submit button, will be escaped
 558       * @param array $attributes Attributes
 559       * @return string HTML output for the submit button
 560       */
 561  	function submitButton( $message, $attributes = array() ) {
 562          # Disable submit button if history has 1 revision only
 563          if ( $this->getNumRows() > 1 ) {
 564              return Xml::submitButton( $message, $attributes );
 565          } else {
 566              return '';
 567          }
 568      }
 569  
 570      /**
 571       * Returns a row from the history printout.
 572       *
 573       * @todo document some more, and maybe clean up the code (some params redundant?)
 574       *
 575       * @param stdClass $row The database row corresponding to the previous line.
 576       * @param mixed $next The database row corresponding to the next line
 577       *   (chronologically previous)
 578       * @param bool|string $notificationtimestamp
 579       * @param bool $latest Whether this row corresponds to the page's latest revision.
 580       * @param bool $firstInList Whether this row corresponds to the first
 581       *   displayed on this history page.
 582       * @return string HTML output for the row
 583       */
 584  	function historyLine( $row, $next, $notificationtimestamp = false,
 585          $latest = false, $firstInList = false ) {
 586          $rev = new Revision( $row );
 587          $rev->setTitle( $this->getTitle() );
 588  
 589          if ( is_object( $next ) ) {
 590              $prevRev = new Revision( $next );
 591              $prevRev->setTitle( $this->getTitle() );
 592          } else {
 593              $prevRev = null;
 594          }
 595  
 596          $curlink = $this->curLink( $rev, $latest );
 597          $lastlink = $this->lastLink( $rev, $next );
 598          $curLastlinks = $curlink . $this->historyPage->message['pipe-separator'] . $lastlink;
 599          $histLinks = Html::rawElement(
 600              'span',
 601              array( 'class' => 'mw-history-histlinks' ),
 602              $this->msg( 'parentheses' )->rawParams( $curLastlinks )->escaped()
 603          );
 604  
 605          $diffButtons = $this->diffButtons( $rev, $firstInList );
 606          $s = $histLinks . $diffButtons;
 607  
 608          $link = $this->revLink( $rev );
 609          $classes = array();
 610  
 611          $del = '';
 612          $user = $this->getUser();
 613          // Show checkboxes for each revision
 614          if ( $user->isAllowed( 'deleterevision' ) ) {
 615              $this->preventClickjacking();
 616              // If revision was hidden from sysops, disable the checkbox
 617              if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
 618                  $del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) );
 619              // Otherwise, enable the checkbox...
 620              } else {
 621                  $del = Xml::check( 'showhiderevisions', false,
 622                      array( 'name' => 'ids[' . $rev->getId() . ']' ) );
 623              }
 624          // User can only view deleted revisions...
 625          } elseif ( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) {
 626              // If revision was hidden from sysops, disable the link
 627              if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
 628                  $del = Linker::revDeleteLinkDisabled( false );
 629              // Otherwise, show the link...
 630              } else {
 631                  $query = array( 'type' => 'revision',
 632                      'target' => $this->getTitle()->getPrefixedDBkey(), 'ids' => $rev->getId() );
 633                  $del .= Linker::revDeleteLink( $query,
 634                      $rev->isDeleted( Revision::DELETED_RESTRICTED ), false );
 635              }
 636          }
 637          if ( $del ) {
 638              $s .= " $del ";
 639          }
 640  
 641          $lang = $this->getLanguage();
 642          $dirmark = $lang->getDirMark();
 643  
 644          $s .= " $link";
 645          $s .= $dirmark;
 646          $s .= " <span class='history-user'>" .
 647              Linker::revUserTools( $rev, true ) . "</span>";
 648          $s .= $dirmark;
 649  
 650          if ( $rev->isMinor() ) {
 651              $s .= ' ' . ChangesList::flag( 'minor' );
 652          }
 653  
 654          # Sometimes rev_len isn't populated
 655          if ( $rev->getSize() !== null ) {
 656              # Size is always public data
 657              $prevSize = isset( $this->parentLens[$row->rev_parent_id] )
 658                  ? $this->parentLens[$row->rev_parent_id]
 659                  : 0;
 660              $sDiff = ChangesList::showCharacterDifference( $prevSize, $rev->getSize() );
 661              $fSize = Linker::formatRevisionSize( $rev->getSize() );
 662              $s .= ' <span class="mw-changeslist-separator">. .</span> ' . "$fSize $sDiff";
 663          }
 664  
 665          # Text following the character difference is added just before running hooks
 666          $s2 = Linker::revComment( $rev, false, true );
 667  
 668          if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) {
 669              $s2 .= ' <span class="updatedmarker">' . $this->msg( 'updatedmarker' )->escaped() . '</span>';
 670              $classes[] = 'mw-history-line-updated';
 671          }
 672  
 673          $tools = array();
 674  
 675          # Rollback and undo links
 676          if ( $prevRev && $this->getTitle()->quickUserCan( 'edit', $user ) ) {
 677              if ( $latest && $this->getTitle()->quickUserCan( 'rollback', $user ) ) {
 678                  // Get a rollback link without the brackets
 679                  $rollbackLink = Linker::generateRollback(
 680                      $rev,
 681                      $this->getContext(),
 682                      array( 'verify', 'noBrackets' )
 683                  );
 684                  if ( $rollbackLink ) {
 685                      $this->preventClickjacking();
 686                      $tools[] = $rollbackLink;
 687                  }
 688              }
 689  
 690              if ( !$rev->isDeleted( Revision::DELETED_TEXT )
 691                  && !$prevRev->isDeleted( Revision::DELETED_TEXT )
 692              ) {
 693                  # Create undo tooltip for the first (=latest) line only
 694                  $undoTooltip = $latest
 695                      ? array( 'title' => $this->msg( 'tooltip-undo' )->text() )
 696                      : array();
 697                  $undolink = Linker::linkKnown(
 698                      $this->getTitle(),
 699                      $this->msg( 'editundo' )->escaped(),
 700                      $undoTooltip,
 701                      array(
 702                          'action' => 'edit',
 703                          'undoafter' => $prevRev->getId(),
 704                          'undo' => $rev->getId()
 705                      )
 706                  );
 707                  $tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>";
 708              }
 709          }
 710          // Allow extension to add their own links here
 711          wfRunHooks( 'HistoryRevisionTools', array( $rev, &$tools ) );
 712  
 713          if ( $tools ) {
 714              $s2 .= ' ' . $this->msg( 'parentheses' )->rawParams( $lang->pipeList( $tools ) )->escaped();
 715          }
 716  
 717          # Tags
 718          list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' );
 719          $classes = array_merge( $classes, $newClasses );
 720          if ( $tagSummary !== '' ) {
 721              $s2 .= " $tagSummary";
 722          }
 723  
 724          # Include separator between character difference and following text
 725          if ( $s2 !== '' ) {
 726              $s .= ' <span class="mw-changeslist-separator">. .</span> ' . $s2;
 727          }
 728  
 729          wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row, &$s, &$classes ) );
 730  
 731          $attribs = array();
 732          if ( $classes ) {
 733              $attribs['class'] = implode( ' ', $classes );
 734          }
 735  
 736          return Xml::tags( 'li', $attribs, $s ) . "\n";
 737      }
 738  
 739      /**
 740       * Create a link to view this revision of the page
 741       *
 742       * @param Revision $rev
 743       * @return string
 744       */
 745  	function revLink( $rev ) {
 746          $date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $this->getUser() );
 747          $date = htmlspecialchars( $date );
 748          if ( $rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
 749              $link = Linker::linkKnown(
 750                  $this->getTitle(),
 751                  $date,
 752                  array( 'class' => 'mw-changeslist-date' ),
 753                  array( 'oldid' => $rev->getId() )
 754              );
 755          } else {
 756              $link = $date;
 757          }
 758          if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
 759              $link = "<span class=\"history-deleted\">$link</span>";
 760          }
 761  
 762          return $link;
 763      }
 764  
 765      /**
 766       * Create a diff-to-current link for this revision for this page
 767       *
 768       * @param Revision $rev
 769       * @param bool $latest This is the latest revision of the page?
 770       * @return string
 771       */
 772  	function curLink( $rev, $latest ) {
 773          $cur = $this->historyPage->message['cur'];
 774          if ( $latest || !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
 775              return $cur;
 776          } else {
 777              return Linker::linkKnown(
 778                  $this->getTitle(),
 779                  $cur,
 780                  array(),
 781                  array(
 782                      'diff' => $this->getWikiPage()->getLatest(),
 783                      'oldid' => $rev->getId()
 784                  )
 785              );
 786          }
 787      }
 788  
 789      /**
 790       * Create a diff-to-previous link for this revision for this page.
 791       *
 792       * @param Revision $prevRev The revision being displayed
 793       * @param stdClass|string|null $next The next revision in list (that is
 794       *        the previous one in chronological order).
 795       *        May either be a row, "unknown" or null.
 796       * @return string
 797       */
 798  	function lastLink( $prevRev, $next ) {
 799          $last = $this->historyPage->message['last'];
 800  
 801          if ( $next === null ) {
 802              # Probably no next row
 803              return $last;
 804          }
 805  
 806          if ( $next === 'unknown' ) {
 807              # Next row probably exists but is unknown, use an oldid=prev link
 808              return Linker::linkKnown(
 809                  $this->getTitle(),
 810                  $last,
 811                  array(),
 812                  array(
 813                      'diff' => $prevRev->getId(),
 814                      'oldid' => 'prev'
 815                  )
 816              );
 817          }
 818  
 819          $nextRev = new Revision( $next );
 820  
 821          if ( !$prevRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
 822              || !$nextRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
 823          ) {
 824              return $last;
 825          }
 826  
 827          return Linker::linkKnown(
 828              $this->getTitle(),
 829              $last,
 830              array(),
 831              array(
 832                  'diff' => $prevRev->getId(),
 833                  'oldid' => $next->rev_id
 834              )
 835          );
 836      }
 837  
 838      /**
 839       * Create radio buttons for page history
 840       *
 841       * @param Revision $rev
 842       * @param bool $firstInList Is this version the first one?
 843       *
 844       * @return string HTML output for the radio buttons
 845       */
 846  	function diffButtons( $rev, $firstInList ) {
 847          if ( $this->getNumRows() > 1 ) {
 848              $id = $rev->getId();
 849              $radio = array( 'type' => 'radio', 'value' => $id );
 850              /** @todo Move title texts to javascript */
 851              if ( $firstInList ) {
 852                  $first = Xml::element( 'input',
 853                      array_merge( $radio, array(
 854                          'style' => 'visibility:hidden',
 855                          'name' => 'oldid',
 856                          'id' => 'mw-oldid-null' ) )
 857                  );
 858                  $checkmark = array( 'checked' => 'checked' );
 859              } else {
 860                  # Check visibility of old revisions
 861                  if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
 862                      $radio['disabled'] = 'disabled';
 863                      $checkmark = array(); // We will check the next possible one
 864                  } elseif ( !$this->oldIdChecked ) {
 865                      $checkmark = array( 'checked' => 'checked' );
 866                      $this->oldIdChecked = $id;
 867                  } else {
 868                      $checkmark = array();
 869                  }
 870                  $first = Xml::element( 'input',
 871                      array_merge( $radio, $checkmark, array(
 872                          'name' => 'oldid',
 873                          'id' => "mw-oldid-$id" ) ) );
 874                  $checkmark = array();
 875              }
 876              $second = Xml::element( 'input',
 877                  array_merge( $radio, $checkmark, array(
 878                      'name' => 'diff',
 879                      'id' => "mw-diff-$id" ) ) );
 880  
 881              return $first . $second;
 882          } else {
 883              return '';
 884          }
 885      }
 886  
 887      /**
 888       * This is called if a write operation is possible from the generated HTML
 889       * @param bool $enable
 890       */
 891  	function preventClickjacking( $enable = true ) {
 892          $this->preventClickjacking = $enable;
 893      }
 894  
 895      /**
 896       * Get the "prevent clickjacking" flag
 897       * @return bool
 898       */
 899  	function getPreventClickjacking() {
 900          return $this->preventClickjacking;
 901      }
 902  }


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