[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/diff/ -> DifferenceEngine.php (source)

   1  <?php
   2  /**
   3   * User interface for the difference engine.
   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   * @ingroup DifferenceEngine
  22   */
  23  
  24  /**
  25   * Constant to indicate diff cache compatibility.
  26   * Bump this when changing the diff formatting in a way that
  27   * fixes important bugs or such to force cached diff views to
  28   * clear.
  29   */
  30  define( 'MW_DIFF_VERSION', '1.11a' );
  31  
  32  /**
  33   * @todo document
  34   * @ingroup DifferenceEngine
  35   */
  36  class DifferenceEngine extends ContextSource {
  37  
  38      /** @var int */
  39      public $mOldid;
  40  
  41      /** @var int */
  42      public $mNewid;
  43  
  44      private $mOldTags;
  45      private $mNewTags;
  46  
  47      /** @var Content */
  48      public $mOldContent;
  49  
  50      /** @var Content */
  51      public $mNewContent;
  52  
  53      /** @var Language */
  54      protected $mDiffLang;
  55  
  56      /** @var Title */
  57      public $mOldPage;
  58  
  59      /** @var Title */
  60      public $mNewPage;
  61  
  62      /** @var Revision */
  63      public $mOldRev;
  64  
  65      /** @var Revision */
  66      public $mNewRev;
  67  
  68      /** @var bool Have the revisions IDs been loaded */
  69      private $mRevisionsIdsLoaded = false;
  70  
  71      /** @var bool Have the revisions been loaded */
  72      public $mRevisionsLoaded = false;
  73  
  74      /** @var int How many text blobs have been loaded, 0, 1 or 2? */
  75      public $mTextLoaded = 0;
  76  
  77      /** @var bool Was the diff fetched from cache? */
  78      public $mCacheHit = false;
  79  
  80      /**
  81       * Set this to true to add debug info to the HTML output.
  82       * Warning: this may cause RSS readers to spuriously mark articles as "new"
  83       * (bug 20601)
  84       */
  85      public $enableDebugComment = false;
  86  
  87      /** @var bool If true, line X is not displayed when X is 1, for example
  88       *    to increase readability and conserve space with many small diffs.
  89       */
  90      protected $mReducedLineNumbers = false;
  91  
  92      /** @var string Link to action=markpatrolled */
  93      protected $mMarkPatrolledLink = null;
  94  
  95      /** @var bool Show rev_deleted content if allowed */
  96      protected $unhide = false;
  97      /**#@-*/
  98  
  99      /**
 100       * Constructor
 101       * @param IContextSource $context Context to use, anything else will be ignored
 102       * @param int $old Old ID we want to show and diff with.
 103       * @param string|int $new Either revision ID or 'prev' or 'next'. Default: 0.
 104       * @param int $rcid Deprecated, no longer used!
 105       * @param bool $refreshCache If set, refreshes the diff cache
 106       * @param bool $unhide If set, allow viewing deleted revs
 107       */
 108  	public function __construct( $context = null, $old = 0, $new = 0, $rcid = 0,
 109          $refreshCache = false, $unhide = false
 110      ) {
 111          if ( $context instanceof IContextSource ) {
 112              $this->setContext( $context );
 113          }
 114  
 115          wfDebug( "DifferenceEngine old '$old' new '$new' rcid '$rcid'\n" );
 116  
 117          $this->mOldid = $old;
 118          $this->mNewid = $new;
 119          $this->mRefreshCache = $refreshCache;
 120          $this->unhide = $unhide;
 121      }
 122  
 123      /**
 124       * @param bool $value
 125       */
 126  	public function setReducedLineNumbers( $value = true ) {
 127          $this->mReducedLineNumbers = $value;
 128      }
 129  
 130      /**
 131       * @return Language
 132       */
 133  	public function getDiffLang() {
 134          if ( $this->mDiffLang === null ) {
 135              # Default language in which the diff text is written.
 136              $this->mDiffLang = $this->getTitle()->getPageLanguage();
 137          }
 138  
 139          return $this->mDiffLang;
 140      }
 141  
 142      /**
 143       * @return bool
 144       */
 145  	public function wasCacheHit() {
 146          return $this->mCacheHit;
 147      }
 148  
 149      /**
 150       * @return int
 151       */
 152  	public function getOldid() {
 153          $this->loadRevisionIds();
 154  
 155          return $this->mOldid;
 156      }
 157  
 158      /**
 159       * @return bool|int
 160       */
 161  	public function getNewid() {
 162          $this->loadRevisionIds();
 163  
 164          return $this->mNewid;
 165      }
 166  
 167      /**
 168       * Look up a special:Undelete link to the given deleted revision id,
 169       * as a workaround for being unable to load deleted diffs in currently.
 170       *
 171       * @param int $id Revision ID
 172       *
 173       * @return mixed URL or false
 174       */
 175  	public function deletedLink( $id ) {
 176          if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
 177              $dbr = wfGetDB( DB_SLAVE );
 178              $row = $dbr->selectRow( 'archive', '*',
 179                  array( 'ar_rev_id' => $id ),
 180                  __METHOD__ );
 181              if ( $row ) {
 182                  $rev = Revision::newFromArchiveRow( $row );
 183                  $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
 184  
 185                  return SpecialPage::getTitleFor( 'Undelete' )->getFullURL( array(
 186                      'target' => $title->getPrefixedText(),
 187                      'timestamp' => $rev->getTimestamp()
 188                  ) );
 189              }
 190          }
 191  
 192          return false;
 193      }
 194  
 195      /**
 196       * Build a wikitext link toward a deleted revision, if viewable.
 197       *
 198       * @param int $id Revision ID
 199       *
 200       * @return string Wikitext fragment
 201       */
 202  	public function deletedIdMarker( $id ) {
 203          $link = $this->deletedLink( $id );
 204          if ( $link ) {
 205              return "[$link $id]";
 206          } else {
 207              return $id;
 208          }
 209      }
 210  
 211  	private function showMissingRevision() {
 212          $out = $this->getOutput();
 213  
 214          $missing = array();
 215          if ( $this->mOldRev === null ||
 216              ( $this->mOldRev && $this->mOldContent === null )
 217          ) {
 218              $missing[] = $this->deletedIdMarker( $this->mOldid );
 219          }
 220          if ( $this->mNewRev === null ||
 221              ( $this->mNewRev && $this->mNewContent === null )
 222          ) {
 223              $missing[] = $this->deletedIdMarker( $this->mNewid );
 224          }
 225  
 226          $out->setPageTitle( $this->msg( 'errorpagetitle' ) );
 227          $out->addWikiMsg( 'difference-missing-revision',
 228              $this->getLanguage()->listToText( $missing ), count( $missing ) );
 229      }
 230  
 231  	public function showDiffPage( $diffOnly = false ) {
 232          wfProfileIn( __METHOD__ );
 233  
 234          # Allow frames except in certain special cases
 235          $out = $this->getOutput();
 236          $out->allowClickjacking();
 237          $out->setRobotPolicy( 'noindex,nofollow' );
 238  
 239          if ( !$this->loadRevisionData() ) {
 240              $this->showMissingRevision();
 241              wfProfileOut( __METHOD__ );
 242  
 243              return;
 244          }
 245  
 246          $user = $this->getUser();
 247          $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user );
 248          if ( $this->mOldPage ) { # mOldPage might not be set, see below.
 249              $permErrors = wfMergeErrorArrays( $permErrors,
 250                  $this->mOldPage->getUserPermissionsErrors( 'read', $user ) );
 251          }
 252          if ( count( $permErrors ) ) {
 253              wfProfileOut( __METHOD__ );
 254              throw new PermissionsError( 'read', $permErrors );
 255          }
 256  
 257          $rollback = '';
 258  
 259          $query = array();
 260          # Carry over 'diffonly' param via navigation links
 261          if ( $diffOnly != $user->getBoolOption( 'diffonly' ) ) {
 262              $query['diffonly'] = $diffOnly;
 263          }
 264          # Cascade unhide param in links for easy deletion browsing
 265          if ( $this->unhide ) {
 266              $query['unhide'] = 1;
 267          }
 268  
 269          # Check if one of the revisions is deleted/suppressed
 270          $deleted = $suppressed = false;
 271          $allowed = $this->mNewRev->userCan( Revision::DELETED_TEXT, $user );
 272  
 273          $revisionTools = array();
 274  
 275          # mOldRev is false if the difference engine is called with a "vague" query for
 276          # a diff between a version V and its previous version V' AND the version V
 277          # is the first version of that article. In that case, V' does not exist.
 278          if ( $this->mOldRev === false ) {
 279              $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
 280              $samePage = true;
 281              $oldHeader = '';
 282          } else {
 283              wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
 284  
 285              if ( $this->mNewPage->equals( $this->mOldPage ) ) {
 286                  $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
 287                  $samePage = true;
 288              } else {
 289                  $out->setPageTitle( $this->msg( 'difference-title-multipage',
 290                      $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
 291                  $out->addSubtitle( $this->msg( 'difference-multipage' ) );
 292                  $samePage = false;
 293              }
 294  
 295              if ( $samePage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
 296                  if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) {
 297                      $rollbackLink = Linker::generateRollback( $this->mNewRev, $this->getContext() );
 298                      if ( $rollbackLink ) {
 299                          $out->preventClickjacking();
 300                          $rollback = '&#160;&#160;&#160;' . $rollbackLink;
 301                      }
 302                  }
 303  
 304                  if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) &&
 305                      !$this->mNewRev->isDeleted( Revision::DELETED_TEXT )
 306                  ) {
 307                      $undoLink = Html::element( 'a', array(
 308                              'href' => $this->mNewPage->getLocalURL( array(
 309                                  'action' => 'edit',
 310                                  'undoafter' => $this->mOldid,
 311                                  'undo' => $this->mNewid
 312                              ) ),
 313                              'title' => Linker::titleAttrib( 'undo' ),
 314                          ),
 315                          $this->msg( 'editundo' )->text()
 316                      );
 317                      $revisionTools['mw-diff-undo'] = $undoLink;
 318                  }
 319              }
 320  
 321              # Make "previous revision link"
 322              if ( $samePage && $this->mOldRev->getPrevious() ) {
 323                  $prevlink = Linker::linkKnown(
 324                      $this->mOldPage,
 325                      $this->msg( 'previousdiff' )->escaped(),
 326                      array( 'id' => 'differences-prevlink' ),
 327                      array( 'diff' => 'prev', 'oldid' => $this->mOldid ) + $query
 328                  );
 329              } else {
 330                  $prevlink = '&#160;';
 331              }
 332  
 333              if ( $this->mOldRev->isMinor() ) {
 334                  $oldminor = ChangesList::flag( 'minor' );
 335              } else {
 336                  $oldminor = '';
 337              }
 338  
 339              $ldel = $this->revisionDeleteLink( $this->mOldRev );
 340              $oldRevisionHeader = $this->getRevisionHeader( $this->mOldRev, 'complete' );
 341              $oldChangeTags = ChangeTags::formatSummaryRow( $this->mOldTags, 'diff' );
 342  
 343              $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader . '</strong></div>' .
 344                  '<div id="mw-diff-otitle2">' .
 345                  Linker::revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
 346                  '<div id="mw-diff-otitle3">' . $oldminor .
 347                  Linker::revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
 348                  '<div id="mw-diff-otitle5">' . $oldChangeTags[0] . '</div>' .
 349                  '<div id="mw-diff-otitle4">' . $prevlink . '</div>';
 350  
 351              if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
 352                  $deleted = true; // old revisions text is hidden
 353                  if ( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
 354                      $suppressed = true; // also suppressed
 355                  }
 356              }
 357  
 358              # Check if this user can see the revisions
 359              if ( !$this->mOldRev->userCan( Revision::DELETED_TEXT, $user ) ) {
 360                  $allowed = false;
 361              }
 362          }
 363  
 364          # Make "next revision link"
 365          # Skip next link on the top revision
 366          if ( $samePage && !$this->mNewRev->isCurrent() ) {
 367              $nextlink = Linker::linkKnown(
 368                  $this->mNewPage,
 369                  $this->msg( 'nextdiff' )->escaped(),
 370                  array( 'id' => 'differences-nextlink' ),
 371                  array( 'diff' => 'next', 'oldid' => $this->mNewid ) + $query
 372              );
 373          } else {
 374              $nextlink = '&#160;';
 375          }
 376  
 377          if ( $this->mNewRev->isMinor() ) {
 378              $newminor = ChangesList::flag( 'minor' );
 379          } else {
 380              $newminor = '';
 381          }
 382  
 383          # Handle RevisionDelete links...
 384          $rdel = $this->revisionDeleteLink( $this->mNewRev );
 385  
 386          # Allow extensions to define their own revision tools
 387          wfRunHooks( 'DiffRevisionTools', array( $this->mNewRev, &$revisionTools, $this->mOldRev ) );
 388          $formattedRevisionTools = array();
 389          // Put each one in parentheses (poor man's button)
 390          foreach ( $revisionTools as $key => $tool ) {
 391              $toolClass = is_string( $key ) ? $key : 'mw-diff-tool';
 392              $element = Html::rawElement(
 393                  'span',
 394                  array( 'class' => $toolClass ),
 395                  $this->msg( 'parentheses' )->rawParams( $tool )->escaped()
 396              );
 397              $formattedRevisionTools[] = $element;
 398          }
 399          $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) .
 400              ' ' . implode( ' ', $formattedRevisionTools );
 401          $newChangeTags = ChangeTags::formatSummaryRow( $this->mNewTags, 'diff' );
 402  
 403          $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' .
 404              '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $this->mNewRev, !$this->unhide ) .
 405              " $rollback</div>" .
 406              '<div id="mw-diff-ntitle3">' . $newminor .
 407              Linker::revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
 408              '<div id="mw-diff-ntitle5">' . $newChangeTags[0] . '</div>' .
 409              '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() . '</div>';
 410  
 411          if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
 412              $deleted = true; // new revisions text is hidden
 413              if ( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
 414                  $suppressed = true; // also suppressed
 415              }
 416          }
 417  
 418          # If the diff cannot be shown due to a deleted revision, then output
 419          # the diff header and links to unhide (if available)...
 420          if ( $deleted && ( !$this->unhide || !$allowed ) ) {
 421              $this->showDiffStyle();
 422              $multi = $this->getMultiNotice();
 423              $out->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) );
 424              if ( !$allowed ) {
 425                  $msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff';
 426                  # Give explanation for why revision is not visible
 427                  $out->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
 428                      array( $msg ) );
 429              } else {
 430                  # Give explanation and add a link to view the diff...
 431                  $query = $this->getRequest()->appendQueryValue( 'unhide', '1', true );
 432                  $link = $this->getTitle()->getFullURL( $query );
 433                  $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
 434                  $out->wrapWikiMsg(
 435                      "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
 436                      array( $msg, $link )
 437                  );
 438              }
 439          # Otherwise, output a regular diff...
 440          } else {
 441              # Add deletion notice if the user is viewing deleted content
 442              $notice = '';
 443              if ( $deleted ) {
 444                  $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view';
 445                  $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" .
 446                      $this->msg( $msg )->parse() .
 447                      "</div>\n";
 448              }
 449              $this->showDiff( $oldHeader, $newHeader, $notice );
 450              if ( !$diffOnly ) {
 451                  $this->renderNewRevision();
 452              }
 453          }
 454          wfProfileOut( __METHOD__ );
 455      }
 456  
 457      /**
 458       * Get a link to mark the change as patrolled, or '' if there's either no
 459       * revision to patrol or the user is not allowed to to it.
 460       * Side effect: When the patrol link is build, this method will call
 461       * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
 462       *
 463       * @return string
 464       */
 465  	protected function markPatrolledLink() {
 466          global $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI;
 467          $user = $this->getUser();
 468  
 469          if ( $this->mMarkPatrolledLink === null ) {
 470              // Prepare a change patrol link, if applicable
 471              if (
 472                  // Is patrolling enabled and the user allowed to?
 473                  $wgUseRCPatrol && $this->mNewPage->quickUserCan( 'patrol', $user ) &&
 474                  // Only do this if the revision isn't more than 6 hours older
 475                  // than the Max RC age (6h because the RC might not be cleaned out regularly)
 476                  RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 )
 477              ) {
 478                  // Look for an unpatrolled change corresponding to this diff
 479  
 480                  $db = wfGetDB( DB_SLAVE );
 481                  $change = RecentChange::newFromConds(
 482                      array(
 483                          'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
 484                          'rc_this_oldid' => $this->mNewid,
 485                          'rc_patrolled' => 0
 486                      ),
 487                      __METHOD__,
 488                      array( 'USE INDEX' => 'rc_timestamp' )
 489                  );
 490  
 491                  if ( $change && $change->getPerformer()->getName() !== $user->getName() ) {
 492                      $rcid = $change->getAttribute( 'rc_id' );
 493                  } else {
 494                      // None found or the page has been created by the current user.
 495                      // If the user could patrol this it already would be patrolled
 496                      $rcid = 0;
 497                  }
 498                  // Build the link
 499                  if ( $rcid ) {
 500                      $this->getOutput()->preventClickjacking();
 501                      if ( $wgEnableAPI && $wgEnableWriteAPI
 502                          && $user->isAllowed( 'writeapi' )
 503                      ) {
 504                          $this->getOutput()->addModules( 'mediawiki.page.patrol.ajax' );
 505                      }
 506  
 507                      $token = $user->getEditToken( $rcid );
 508                      $this->mMarkPatrolledLink = ' <span class="patrollink">[' . Linker::linkKnown(
 509                          $this->mNewPage,
 510                          $this->msg( 'markaspatrolleddiff' )->escaped(),
 511                          array(),
 512                          array(
 513                              'action' => 'markpatrolled',
 514                              'rcid' => $rcid,
 515                              'token' => $token,
 516                          )
 517                      ) . ']</span>';
 518                  } else {
 519                      $this->mMarkPatrolledLink = '';
 520                  }
 521              } else {
 522                  $this->mMarkPatrolledLink = '';
 523              }
 524          }
 525  
 526          return $this->mMarkPatrolledLink;
 527      }
 528  
 529      /**
 530       * @param Revision $rev
 531       *
 532       * @return string
 533       */
 534  	protected function revisionDeleteLink( $rev ) {
 535          $link = Linker::getRevDeleteLink( $this->getUser(), $rev, $rev->getTitle() );
 536          if ( $link !== '' ) {
 537              $link = '&#160;&#160;&#160;' . $link . ' ';
 538          }
 539  
 540          return $link;
 541      }
 542  
 543      /**
 544       * Show the new revision of the page.
 545       */
 546  	public function renderNewRevision() {
 547          wfProfileIn( __METHOD__ );
 548          $out = $this->getOutput();
 549          $revHeader = $this->getRevisionHeader( $this->mNewRev );
 550          # Add "current version as of X" title
 551          $out->addHTML( "<hr class='diff-hr' />
 552          <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
 553          # Page content may be handled by a hooked call instead...
 554          # @codingStandardsIgnoreStart Ignoring long lines.
 555          if ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $out ) ) ) {
 556              $this->loadNewText();
 557              $out->setRevisionId( $this->mNewid );
 558              $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
 559              $out->setArticleFlag( true );
 560  
 561              // NOTE: only needed for B/C: custom rendering of JS/CSS via hook
 562              if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
 563                  // This needs to be synchronised with Article::showCssOrJsPage(), which sucks
 564                  // Give hooks a chance to customise the output
 565                  // @todo standardize this crap into one function
 566                  if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
 567                      // NOTE: deprecated hook, B/C only
 568                      // use the content object's own rendering
 569                      $cnt = $this->mNewRev->getContent();
 570                      $po = $cnt ? $cnt->getParserOutput( $this->mNewRev->getTitle(), $this->mNewRev->getId() ) : null;
 571                      if ( $po ) {
 572                          $out->addParserOutputContent( $po );
 573                      }
 574                  }
 575              } elseif ( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
 576                  // Handled by extension
 577              } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
 578                  // NOTE: deprecated hook, B/C only
 579                  // Handled by extension
 580              } else {
 581                  // Normal page
 582                  if ( $this->getTitle()->equals( $this->mNewPage ) ) {
 583                      // If the Title stored in the context is the same as the one
 584                      // of the new revision, we can use its associated WikiPage
 585                      // object.
 586                      $wikiPage = $this->getWikiPage();
 587                  } else {
 588                      // Otherwise we need to create our own WikiPage object
 589                      $wikiPage = WikiPage::factory( $this->mNewPage );
 590                  }
 591  
 592                  $parserOutput = $this->getParserOutput( $wikiPage, $this->mNewRev );
 593  
 594                  # WikiPage::getParserOutput() should not return false, but just in case
 595                  if ( $parserOutput ) {
 596                      $out->addParserOutput( $parserOutput );
 597                  }
 598              }
 599          }
 600          # @codingStandardsIgnoreEnd
 601  
 602          # Add redundant patrol link on bottom...
 603          $out->addHTML( $this->markPatrolledLink() );
 604  
 605          wfProfileOut( __METHOD__ );
 606      }
 607  
 608  	protected function getParserOutput( WikiPage $page, Revision $rev ) {
 609          $parserOptions = $page->makeParserOptions( $this->getContext() );
 610  
 611          if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( "edit" ) ) {
 612              $parserOptions->setEditSection( false );
 613          }
 614  
 615          $parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() );
 616  
 617          return $parserOutput;
 618      }
 619  
 620      /**
 621       * Get the diff text, send it to the OutputPage object
 622       * Returns false if the diff could not be generated, otherwise returns true
 623       *
 624       * @param string|bool $otitle Header for old text or false
 625       * @param string|bool $ntitle Header for new text or false
 626       * @param string $notice HTML between diff header and body
 627       *
 628       * @return bool
 629       */
 630  	public function showDiff( $otitle, $ntitle, $notice = '' ) {
 631          $diff = $this->getDiff( $otitle, $ntitle, $notice );
 632          if ( $diff === false ) {
 633              $this->showMissingRevision();
 634  
 635              return false;
 636          } else {
 637              $this->showDiffStyle();
 638              $this->getOutput()->addHTML( $diff );
 639  
 640              return true;
 641          }
 642      }
 643  
 644      /**
 645       * Add style sheets and supporting JS for diff display.
 646       */
 647  	public function showDiffStyle() {
 648          $this->getOutput()->addModuleStyles( 'mediawiki.action.history.diff' );
 649      }
 650  
 651      /**
 652       * Get complete diff table, including header
 653       *
 654       * @param string|bool $otitle Header for old text or false
 655       * @param string|bool $ntitle Header for new text or false
 656       * @param string $notice HTML between diff header and body
 657       *
 658       * @return mixed
 659       */
 660  	public function getDiff( $otitle, $ntitle, $notice = '' ) {
 661          $body = $this->getDiffBody();
 662          if ( $body === false ) {
 663              return false;
 664          }
 665  
 666          $multi = $this->getMultiNotice();
 667          // Display a message when the diff is empty
 668          if ( $body === '' ) {
 669              $notice .= '<div class="mw-diff-empty">' .
 670                  $this->msg( 'diff-empty' )->parse() .
 671                  "</div>\n";
 672          }
 673  
 674          return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
 675      }
 676  
 677      /**
 678       * Get the diff table body, without header
 679       *
 680       * @return mixed (string/false)
 681       */
 682  	public function getDiffBody() {
 683          global $wgMemc;
 684          wfProfileIn( __METHOD__ );
 685          $this->mCacheHit = true;
 686          // Check if the diff should be hidden from this user
 687          if ( !$this->loadRevisionData() ) {
 688              wfProfileOut( __METHOD__ );
 689  
 690              return false;
 691          } elseif ( $this->mOldRev &&
 692              !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
 693          ) {
 694              wfProfileOut( __METHOD__ );
 695  
 696              return false;
 697          } elseif ( $this->mNewRev &&
 698              !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
 699          ) {
 700              wfProfileOut( __METHOD__ );
 701  
 702              return false;
 703          }
 704          // Short-circuit
 705          if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
 706              && $this->mOldRev->getID() == $this->mNewRev->getID() )
 707          ) {
 708              wfProfileOut( __METHOD__ );
 709  
 710              return '';
 711          }
 712          // Cacheable?
 713          $key = false;
 714          if ( $this->mOldid && $this->mNewid ) {
 715              $key = $this->getDiffBodyCacheKey();
 716  
 717              // Try cache
 718              if ( !$this->mRefreshCache ) {
 719                  $difftext = $wgMemc->get( $key );
 720                  if ( $difftext ) {
 721                      wfIncrStats( 'diff_cache_hit' );
 722                      $difftext = $this->localiseLineNumbers( $difftext );
 723                      $difftext .= "\n<!-- diff cache key $key -->\n";
 724                      wfProfileOut( __METHOD__ );
 725  
 726                      return $difftext;
 727                  }
 728              } // don't try to load but save the result
 729          }
 730          $this->mCacheHit = false;
 731  
 732          // Loadtext is permission safe, this just clears out the diff
 733          if ( !$this->loadText() ) {
 734              wfProfileOut( __METHOD__ );
 735  
 736              return false;
 737          }
 738  
 739          $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
 740  
 741          // Save to cache for 7 days
 742          if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
 743              wfIncrStats( 'diff_uncacheable' );
 744          } elseif ( $key !== false && $difftext !== false ) {
 745              wfIncrStats( 'diff_cache_miss' );
 746              $wgMemc->set( $key, $difftext, 7 * 86400 );
 747          } else {
 748              wfIncrStats( 'diff_uncacheable' );
 749          }
 750          // Replace line numbers with the text in the user's language
 751          if ( $difftext !== false ) {
 752              $difftext = $this->localiseLineNumbers( $difftext );
 753          }
 754          wfProfileOut( __METHOD__ );
 755  
 756          return $difftext;
 757      }
 758  
 759      /**
 760       * Returns the cache key for diff body text or content.
 761       *
 762       * @since 1.23
 763       *
 764       * @throws MWException
 765       * @return string
 766       */
 767  	protected function getDiffBodyCacheKey() {
 768          if ( !$this->mOldid || !$this->mNewid ) {
 769              throw new MWException( 'mOldid and mNewid must be set to get diff cache key.' );
 770          }
 771  
 772          return wfMemcKey( 'diff', 'version', MW_DIFF_VERSION,
 773              'oldid', $this->mOldid, 'newid', $this->mNewid );
 774      }
 775  
 776      /**
 777       * Generate a diff, no caching.
 778       *
 779       * This implementation uses generateTextDiffBody() to generate a diff based on the default
 780       * serialization of the given Content objects. This will fail if $old or $new are not
 781       * instances of TextContent.
 782       *
 783       * Subclasses may override this to provide a different rendering for the diff,
 784       * perhaps taking advantage of the content's native form. This is required for all content
 785       * models that are not text based.
 786       *
 787       * @since 1.21
 788       *
 789       * @param Content $old Old content
 790       * @param Content $new New content
 791       *
 792       * @throws MWException If old or new content is not an instance of TextContent.
 793       * @return bool|string
 794       */
 795  	public function generateContentDiffBody( Content $old, Content $new ) {
 796          if ( !( $old instanceof TextContent ) ) {
 797              throw new MWException( "Diff not implemented for " . get_class( $old ) . "; " .
 798                  "override generateContentDiffBody to fix this." );
 799          }
 800  
 801          if ( !( $new instanceof TextContent ) ) {
 802              throw new MWException( "Diff not implemented for " . get_class( $new ) . "; "
 803                  . "override generateContentDiffBody to fix this." );
 804          }
 805  
 806          $otext = $old->serialize();
 807          $ntext = $new->serialize();
 808  
 809          return $this->generateTextDiffBody( $otext, $ntext );
 810      }
 811  
 812      /**
 813       * Generate a diff, no caching
 814       *
 815       * @param string $otext Old text, must be already segmented
 816       * @param string $ntext New text, must be already segmented
 817       *
 818       * @return bool|string
 819       * @deprecated since 1.21, use generateContentDiffBody() instead!
 820       */
 821  	public function generateDiffBody( $otext, $ntext ) {
 822          ContentHandler::deprecated( __METHOD__, "1.21" );
 823  
 824          return $this->generateTextDiffBody( $otext, $ntext );
 825      }
 826  
 827      /**
 828       * Generate a diff, no caching
 829       *
 830       * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
 831       *
 832       * @param string $otext Old text, must be already segmented
 833       * @param string $ntext New text, must be already segmented
 834       *
 835       * @return bool|string
 836       */
 837  	public function generateTextDiffBody( $otext, $ntext ) {
 838          global $wgExternalDiffEngine, $wgContLang;
 839  
 840          wfProfileIn( __METHOD__ );
 841  
 842          $otext = str_replace( "\r\n", "\n", $otext );
 843          $ntext = str_replace( "\r\n", "\n", $ntext );
 844  
 845          if ( $wgExternalDiffEngine == 'wikidiff' && function_exists( 'wikidiff_do_diff' ) ) {
 846              # For historical reasons, external diff engine expects
 847              # input text to be HTML-escaped already
 848              $otext = htmlspecialchars( $wgContLang->segmentForDiff( $otext ) );
 849              $ntext = htmlspecialchars( $wgContLang->segmentForDiff( $ntext ) );
 850              wfProfileOut( __METHOD__ );
 851  
 852              return $wgContLang->unsegmentForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) .
 853              $this->debug( 'wikidiff1' );
 854          }
 855  
 856          if ( $wgExternalDiffEngine == 'wikidiff2' && function_exists( 'wikidiff2_do_diff' ) ) {
 857              # Better external diff engine, the 2 may some day be dropped
 858              # This one does the escaping and segmenting itself
 859              wfProfileIn( 'wikidiff2_do_diff' );
 860              $text = wikidiff2_do_diff( $otext, $ntext, 2 );
 861              $text .= $this->debug( 'wikidiff2' );
 862              wfProfileOut( 'wikidiff2_do_diff' );
 863              wfProfileOut( __METHOD__ );
 864  
 865              return $text;
 866          }
 867          if ( $wgExternalDiffEngine != 'wikidiff3' && $wgExternalDiffEngine !== false ) {
 868              # Diff via the shell
 869              $tmpDir = wfTempDir();
 870              $tempName1 = tempnam( $tmpDir, 'diff_' );
 871              $tempName2 = tempnam( $tmpDir, 'diff_' );
 872  
 873              $tempFile1 = fopen( $tempName1, "w" );
 874              if ( !$tempFile1 ) {
 875                  wfProfileOut( __METHOD__ );
 876  
 877                  return false;
 878              }
 879              $tempFile2 = fopen( $tempName2, "w" );
 880              if ( !$tempFile2 ) {
 881                  wfProfileOut( __METHOD__ );
 882  
 883                  return false;
 884              }
 885              fwrite( $tempFile1, $otext );
 886              fwrite( $tempFile2, $ntext );
 887              fclose( $tempFile1 );
 888              fclose( $tempFile2 );
 889              $cmd = wfEscapeShellArg( $wgExternalDiffEngine, $tempName1, $tempName2 );
 890              wfProfileIn( __METHOD__ . "-shellexec" );
 891              $difftext = wfShellExec( $cmd );
 892              $difftext .= $this->debug( "external $wgExternalDiffEngine" );
 893              wfProfileOut( __METHOD__ . "-shellexec" );
 894              unlink( $tempName1 );
 895              unlink( $tempName2 );
 896              wfProfileOut( __METHOD__ );
 897  
 898              return $difftext;
 899          }
 900  
 901          # Native PHP diff
 902          $ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) );
 903          $nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
 904          $diffs = new Diff( $ota, $nta );
 905          $formatter = new TableDiffFormatter();
 906          $difftext = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) ) .
 907              wfProfileOut( __METHOD__ );
 908  
 909          return $difftext;
 910      }
 911  
 912      /**
 913       * Generate a debug comment indicating diff generating time,
 914       * server node, and generator backend.
 915       *
 916       * @param string $generator : What diff engine was used
 917       *
 918       * @return string
 919       */
 920  	protected function debug( $generator = "internal" ) {
 921          global $wgShowHostnames;
 922          if ( !$this->enableDebugComment ) {
 923              return '';
 924          }
 925          $data = array( $generator );
 926          if ( $wgShowHostnames ) {
 927              $data[] = wfHostname();
 928          }
 929          $data[] = wfTimestamp( TS_DB );
 930  
 931          return "<!-- diff generator: " .
 932              implode( " ", array_map( "htmlspecialchars", $data ) ) .
 933              " -->\n";
 934      }
 935  
 936      /**
 937       * Replace line numbers with the text in the user's language
 938       *
 939       * @param string $text
 940       *
 941       * @return mixed
 942       */
 943  	public function localiseLineNumbers( $text ) {
 944          return preg_replace_callback(
 945              '/<!--LINE (\d+)-->/',
 946              array( &$this, 'localiseLineNumbersCb' ),
 947              $text
 948          );
 949      }
 950  
 951  	public function localiseLineNumbersCb( $matches ) {
 952          if ( $matches[1] === '1' && $this->mReducedLineNumbers ) {
 953              return '';
 954          }
 955  
 956          return $this->msg( 'lineno' )->numParams( $matches[1] )->escaped();
 957      }
 958  
 959      /**
 960       * If there are revisions between the ones being compared, return a note saying so.
 961       *
 962       * @return string
 963       */
 964  	public function getMultiNotice() {
 965          if ( !is_object( $this->mOldRev ) || !is_object( $this->mNewRev ) ) {
 966              return '';
 967          } elseif ( !$this->mOldPage->equals( $this->mNewPage ) ) {
 968              // Comparing two different pages? Count would be meaningless.
 969              return '';
 970          }
 971  
 972          if ( $this->mOldRev->getTimestamp() > $this->mNewRev->getTimestamp() ) {
 973              $oldRev = $this->mNewRev; // flip
 974              $newRev = $this->mOldRev; // flip
 975          } else { // normal case
 976              $oldRev = $this->mOldRev;
 977              $newRev = $this->mNewRev;
 978          }
 979  
 980          // Sanity: don't show the notice if too many rows must be scanned
 981          // @todo show some special message for that case
 982          $nEdits = $this->mNewPage->countRevisionsBetween( $oldRev, $newRev, 1000 );
 983          if ( $nEdits > 0 && $nEdits <= 1000 ) {
 984              $limit = 100; // use diff-multi-manyusers if too many users
 985              $users = $this->mNewPage->getAuthorsBetween( $oldRev, $newRev, $limit );
 986              $numUsers = count( $users );
 987  
 988              if ( $numUsers == 1 && $users[0] == $newRev->getRawUserText() ) {
 989                  $numUsers = 0; // special case to say "by the same user" instead of "by one other user"
 990              }
 991  
 992              return self::intermediateEditsMsg( $nEdits, $numUsers, $limit );
 993          }
 994  
 995          return ''; // nothing
 996      }
 997  
 998      /**
 999       * Get a notice about how many intermediate edits and users there are
1000       *
1001       * @param int $numEdits
1002       * @param int $numUsers
1003       * @param int $limit
1004       *
1005       * @return string
1006       */
1007  	public static function intermediateEditsMsg( $numEdits, $numUsers, $limit ) {
1008          if ( $numUsers === 0 ) {
1009              $msg = 'diff-multi-sameuser';
1010          } elseif ( $numUsers > $limit ) {
1011              $msg = 'diff-multi-manyusers';
1012              $numUsers = $limit;
1013          } else {
1014              $msg = 'diff-multi-otherusers';
1015          }
1016  
1017          return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1018      }
1019  
1020      /**
1021       * Get a header for a specified revision.
1022       *
1023       * @param Revision $rev
1024       * @param string $complete 'complete' to get the header wrapped depending
1025       *        the visibility of the revision and a link to edit the page.
1026       *
1027       * @return string HTML fragment
1028       */
1029  	protected function getRevisionHeader( Revision $rev, $complete = '' ) {
1030          $lang = $this->getLanguage();
1031          $user = $this->getUser();
1032          $revtimestamp = $rev->getTimestamp();
1033          $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1034          $dateofrev = $lang->userDate( $revtimestamp, $user );
1035          $timeofrev = $lang->userTime( $revtimestamp, $user );
1036  
1037          $header = $this->msg(
1038              $rev->isCurrent() ? 'currentrev-asof' : 'revisionasof',
1039              $timestamp,
1040              $dateofrev,
1041              $timeofrev
1042          )->escaped();
1043  
1044          if ( $complete !== 'complete' ) {
1045              return $header;
1046          }
1047  
1048          $title = $rev->getTitle();
1049  
1050          $header = Linker::linkKnown( $title, $header, array(),
1051              array( 'oldid' => $rev->getID() ) );
1052  
1053          if ( $rev->userCan( Revision::DELETED_TEXT, $user ) ) {
1054              $editQuery = array( 'action' => 'edit' );
1055              if ( !$rev->isCurrent() ) {
1056                  $editQuery['oldid'] = $rev->getID();
1057              }
1058  
1059              $key = $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold';
1060              $msg = $this->msg( $key )->escaped();
1061              $editLink = $this->msg( 'parentheses' )->rawParams(
1062                  Linker::linkKnown( $title, $msg, array( ), $editQuery ) )->plain();
1063              $header .= ' ' . Html::rawElement(
1064                  'span',
1065                  array( 'class' => 'mw-diff-edit' ),
1066                  $editLink
1067              );
1068              if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
1069                  $header = Html::rawElement(
1070                      'span',
1071                      array( 'class' => 'history-deleted' ),
1072                      $header
1073                  );
1074              }
1075          } else {
1076              $header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
1077          }
1078  
1079          return $header;
1080      }
1081  
1082      /**
1083       * Add the header to a diff body
1084       *
1085       * @param string $diff Diff body
1086       * @param string $otitle Old revision header
1087       * @param string $ntitle New revision header
1088       * @param string $multi Notice telling user that there are intermediate
1089       *   revisions between the ones being compared
1090       * @param string $notice Other notices, e.g. that user is viewing deleted content
1091       *
1092       * @return string
1093       */
1094  	public function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
1095          // shared.css sets diff in interface language/dir, but the actual content
1096          // is often in a different language, mostly the page content language/dir
1097          $tableClass = 'diff diff-contentalign-' . htmlspecialchars( $this->getDiffLang()->alignStart() );
1098          $header = "<table class='$tableClass'>";
1099  
1100          if ( !$diff && !$otitle ) {
1101              $header .= "
1102              <tr style='vertical-align: top;'>
1103              <td class='diff-ntitle'>{$ntitle}</td>
1104              </tr>";
1105              $multiColspan = 1;
1106          } else {
1107              if ( $diff ) { // Safari/Chrome show broken output if cols not used
1108                  $header .= "
1109                  <col class='diff-marker' />
1110                  <col class='diff-content' />
1111                  <col class='diff-marker' />
1112                  <col class='diff-content' />";
1113                  $colspan = 2;
1114                  $multiColspan = 4;
1115              } else {
1116                  $colspan = 1;
1117                  $multiColspan = 2;
1118              }
1119              if ( $otitle || $ntitle ) {
1120                  $header .= "
1121                  <tr style='vertical-align: top;'>
1122                  <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
1123                  <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
1124                  </tr>";
1125              }
1126          }
1127  
1128          if ( $multi != '' ) {
1129              $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;' " .
1130                  "class='diff-multi'>{$multi}</td></tr>";
1131          }
1132          if ( $notice != '' ) {
1133              $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;'>{$notice}</td></tr>";
1134          }
1135  
1136          return $header . $diff . "</table>";
1137      }
1138  
1139      /**
1140       * Use specified text instead of loading from the database
1141       * @deprecated since 1.21, use setContent() instead.
1142       */
1143  	public function setText( $oldText, $newText ) {
1144          ContentHandler::deprecated( __METHOD__, "1.21" );
1145  
1146          $oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
1147          $newContent = ContentHandler::makeContent( $newText, $this->getTitle() );
1148  
1149          $this->setContent( $oldContent, $newContent );
1150      }
1151  
1152      /**
1153       * Use specified text instead of loading from the database
1154       * @param Content $oldContent
1155       * @param Content $newContent
1156       * @since 1.21
1157       */
1158  	public function setContent( Content $oldContent, Content $newContent ) {
1159          $this->mOldContent = $oldContent;
1160          $this->mNewContent = $newContent;
1161  
1162          $this->mTextLoaded = 2;
1163          $this->mRevisionsLoaded = true;
1164      }
1165  
1166      /**
1167       * Set the language in which the diff text is written
1168       * (Defaults to page content language).
1169       * @param Language|string $lang
1170       * @since 1.19
1171       */
1172  	public function setTextLanguage( $lang ) {
1173          $this->mDiffLang = wfGetLangObj( $lang );
1174      }
1175  
1176      /**
1177       * Maps a revision pair definition as accepted by DifferenceEngine constructor
1178       * to a pair of actual integers representing revision ids.
1179       *
1180       * @param int $old Revision id, e.g. from URL parameter 'oldid'
1181       * @param int|string $new Revision id or strings 'next' or 'prev', e.g. from URL parameter 'diff'
1182       *
1183       * @return int[] List of two revision ids, older first, later second.
1184       *     Zero signifies invalid argument passed.
1185       *     false signifies that there is no previous/next revision ($old is the oldest/newest one).
1186       */
1187  	public function mapDiffPrevNext( $old, $new ) {
1188          if ( $new === 'prev' ) {
1189              // Show diff between revision $old and the previous one. Get previous one from DB.
1190              $newid = intval( $old );
1191              $oldid = $this->getTitle()->getPreviousRevisionID( $newid );
1192          } elseif ( $new === 'next' ) {
1193              // Show diff between revision $old and the next one. Get next one from DB.
1194              $oldid = intval( $old );
1195              $newid = $this->getTitle()->getNextRevisionID( $oldid );
1196          } else {
1197              $oldid = intval( $old );
1198              $newid = intval( $new );
1199          }
1200  
1201          return array( $oldid, $newid );
1202      }
1203  
1204      /**
1205       * Load revision IDs
1206       */
1207  	private function loadRevisionIds() {
1208          if ( $this->mRevisionsIdsLoaded ) {
1209              return;
1210          }
1211  
1212          $this->mRevisionsIdsLoaded = true;
1213  
1214          $old = $this->mOldid;
1215          $new = $this->mNewid;
1216  
1217          list( $this->mOldid, $this->mNewid ) = self::mapDiffPrevNext( $old, $new );
1218          if ( $new === 'next' && $this->mNewid === false ) {
1219              # if no result, NewId points to the newest old revision. The only newer
1220              # revision is cur, which is "0".
1221              $this->mNewid = 0;
1222          }
1223  
1224          wfRunHooks(
1225              'NewDifferenceEngine',
1226              array( $this->getTitle(), &$this->mOldid, &$this->mNewid, $old, $new )
1227          );
1228      }
1229  
1230      /**
1231       * Load revision metadata for the specified articles. If newid is 0, then compare
1232       * the old article in oldid to the current article; if oldid is 0, then
1233       * compare the current article to the immediately previous one (ignoring the
1234       * value of newid).
1235       *
1236       * If oldid is false, leave the corresponding revision object set
1237       * to false. This is impossible via ordinary user input, and is provided for
1238       * API convenience.
1239       *
1240       * @return bool
1241       */
1242  	public function loadRevisionData() {
1243          if ( $this->mRevisionsLoaded ) {
1244              return true;
1245          }
1246  
1247          // Whether it succeeds or fails, we don't want to try again
1248          $this->mRevisionsLoaded = true;
1249  
1250          $this->loadRevisionIds();
1251  
1252          // Load the new revision object
1253          if ( $this->mNewid ) {
1254              $this->mNewRev = Revision::newFromId( $this->mNewid );
1255          } else {
1256              $this->mNewRev = Revision::newFromTitle(
1257                  $this->getTitle(),
1258                  false,
1259                  Revision::READ_NORMAL
1260              );
1261          }
1262  
1263          if ( !$this->mNewRev instanceof Revision ) {
1264              return false;
1265          }
1266  
1267          // Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
1268          $this->mNewid = $this->mNewRev->getId();
1269          $this->mNewPage = $this->mNewRev->getTitle();
1270  
1271          // Load the old revision object
1272          $this->mOldRev = false;
1273          if ( $this->mOldid ) {
1274              $this->mOldRev = Revision::newFromId( $this->mOldid );
1275          } elseif ( $this->mOldid === 0 ) {
1276              $rev = $this->mNewRev->getPrevious();
1277              if ( $rev ) {
1278                  $this->mOldid = $rev->getId();
1279                  $this->mOldRev = $rev;
1280              } else {
1281                  // No previous revision; mark to show as first-version only.
1282                  $this->mOldid = false;
1283                  $this->mOldRev = false;
1284              }
1285          } /* elseif ( $this->mOldid === false ) leave mOldRev false; */
1286  
1287          if ( is_null( $this->mOldRev ) ) {
1288              return false;
1289          }
1290  
1291          if ( $this->mOldRev ) {
1292              $this->mOldPage = $this->mOldRev->getTitle();
1293          }
1294  
1295          // Load tags information for both revisions
1296          $dbr = wfGetDB( DB_SLAVE );
1297          if ( $this->mOldid !== false ) {
1298              $this->mOldTags = $dbr->selectField(
1299                  'tag_summary',
1300                  'ts_tags',
1301                  array( 'ts_rev_id' => $this->mOldid ),
1302                  __METHOD__
1303              );
1304          } else {
1305              $this->mOldTags = false;
1306          }
1307          $this->mNewTags = $dbr->selectField(
1308              'tag_summary',
1309              'ts_tags',
1310              array( 'ts_rev_id' => $this->mNewid ),
1311              __METHOD__
1312          );
1313  
1314          return true;
1315      }
1316  
1317      /**
1318       * Load the text of the revisions, as well as revision data.
1319       *
1320       * @return bool
1321       */
1322  	public function loadText() {
1323          if ( $this->mTextLoaded == 2 ) {
1324              return true;
1325          }
1326  
1327          // Whether it succeeds or fails, we don't want to try again
1328          $this->mTextLoaded = 2;
1329  
1330          if ( !$this->loadRevisionData() ) {
1331              return false;
1332          }
1333  
1334          if ( $this->mOldRev ) {
1335              $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
1336              if ( $this->mOldContent === null ) {
1337                  return false;
1338              }
1339          }
1340  
1341          if ( $this->mNewRev ) {
1342              $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
1343              if ( $this->mNewContent === null ) {
1344                  return false;
1345              }
1346          }
1347  
1348          return true;
1349      }
1350  
1351      /**
1352       * Load the text of the new revision, not the old one
1353       *
1354       * @return bool
1355       */
1356  	public function loadNewText() {
1357          if ( $this->mTextLoaded >= 1 ) {
1358              return true;
1359          }
1360  
1361          $this->mTextLoaded = 1;
1362  
1363          if ( !$this->loadRevisionData() ) {
1364              return false;
1365          }
1366  
1367          $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
1368  
1369          return true;
1370      }
1371  
1372  }


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