[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 = '   ' . $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 = ' '; 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 = ' '; 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 = '   ' . $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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |