From 222b01f5169f1c7e69762e0e8904c24f78f71882 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Wed, 28 Jul 2010 11:52:48 +0200 Subject: update to MediaWiki 1.16.0 --- includes/diff/DifferenceInterface.php | 1021 +++++++++++++++++++++++++++++++++ 1 file changed, 1021 insertions(+) create mode 100644 includes/diff/DifferenceInterface.php (limited to 'includes/diff/DifferenceInterface.php') diff --git a/includes/diff/DifferenceInterface.php b/includes/diff/DifferenceInterface.php new file mode 100644 index 00000000..d7d36799 --- /dev/null +++ b/includes/diff/DifferenceInterface.php @@ -0,0 +1,1021 @@ +mTitle = $titleObj; + } else { + global $wgTitle; + $this->mTitle = $wgTitle; + } + wfDebug("DifferenceEngine old '$old' new '$new' rcid '$rcid'\n"); + + if ( 'prev' === $new ) { + # Show diff between revision $old and the previous one. + # Get previous one from DB. + $this->mNewid = intval($old); + $this->mOldid = $this->mTitle->getPreviousRevisionID( $this->mNewid ); + } elseif ( 'next' === $new ) { + # Show diff between revision $old and the next one. + # Get next one from DB. + $this->mOldid = intval($old); + $this->mNewid = $this->mTitle->getNextRevisionID( $this->mOldid ); + if ( false === $this->mNewid ) { + # if no result, NewId points to the newest old revision. The only newer + # revision is cur, which is "0". + $this->mNewid = 0; + } + } else { + $this->mOldid = intval($old); + $this->mNewid = intval($new); + wfRunHooks( 'NewDifferenceEngine', array(&$titleObj, &$this->mOldid, &$this->mNewid, $old, $new) ); + } + $this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer + $this->mRefreshCache = $refreshCache; + $this->unhide = $unhide; + } + + function setReducedLineNumbers( $value = true ) { + $this->mReducedLineNumbers = $value; + } + + function getTitle() { + return $this->mTitle; + } + + function wasCacheHit() { + return $this->mCacheHit; + } + + function getOldid() { + return $this->mOldid; + } + + function getNewid() { + return $this->mNewid; + } + + function showDiffPage( $diffOnly = false ) { + global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol; + wfProfileIn( __METHOD__ ); + + + # If external diffs are enabled both globally and for the user, + # we'll use the application/x-external-editor interface to call + # an external diff tool like kompare, kdiff3, etc. + if($wgUseExternalEditor && $wgUser->getOption('externaldiff')) { + global $wgInputEncoding,$wgServer,$wgScript,$wgLang; + $wgOut->disable(); + header ( "Content-type: application/x-external-editor; charset=".$wgInputEncoding ); + $url1=$this->mTitle->getFullURL( array( + 'action' => 'raw', + 'oldid' => $this->mOldid + ) ); + $url2=$this->mTitle->getFullURL( array( + 'action' => 'raw', + 'oldid' => $this->mNewid + ) ); + $special=$wgLang->getNsText(NS_SPECIAL); + $control=<<setArticleFlag( false ); + if ( !$this->loadRevisionData() ) { + $t = $this->mTitle->getPrefixedText(); + $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ), $this->mOldid, $this->mNewid ); + $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) ); + $wgOut->addWikiMsg( 'missing-article', "$t", $d ); + wfProfileOut( __METHOD__ ); + return; + } + + wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) ); + + if ( $this->mNewRev->isCurrent() ) { + $wgOut->setArticleFlag( true ); + } + + # mOldid is false if the difference engine is called with a "vague" query for + # a diff between a version V and its previous version V' AND the version V + # is the first version of that article. In that case, V' does not exist. + if ( $this->mOldid === false ) { + $this->showFirstRevision(); + $this->renderNewRevision(); // should we respect $diffOnly here or not? + wfProfileOut( __METHOD__ ); + return; + } + + $wgOut->suppressQuickbar(); + + $oldTitle = $this->mOldPage->getPrefixedText(); + $newTitle = $this->mNewPage->getPrefixedText(); + if( $oldTitle == $newTitle ) { + $wgOut->setPageTitle( $newTitle ); + } else { + $wgOut->setPageTitle( $oldTitle . ', ' . $newTitle ); + } + $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); + + if ( !$this->mOldPage->userCanRead() || !$this->mNewPage->userCanRead() ) { + $wgOut->loginToUse(); + $wgOut->output(); + $wgOut->disable(); + wfProfileOut( __METHOD__ ); + return; + } + + $sk = $wgUser->getSkin(); + + // Check if page is editable + $editable = $this->mNewRev->getTitle()->userCan( 'edit' ); + if ( $editable && $this->mNewRev->isCurrent() && $wgUser->isAllowed( 'rollback' ) ) { + $rollback = '   ' . $sk->generateRollback( $this->mNewRev ); + } else { + $rollback = ''; + } + + // Prepare a change patrol link, if applicable + if( $wgUseRCPatrol && $this->mTitle->userCan('patrol') ) { + // If we've been given an explicit change identifier, use it; saves time + if( $this->mRcidMarkPatrolled ) { + $rcid = $this->mRcidMarkPatrolled; + $rc = RecentChange::newFromId( $rcid ); + // Already patrolled? + $rcid = is_object($rc) && !$rc->getAttribute('rc_patrolled') ? $rcid : 0; + } else { + // Look for an unpatrolled change corresponding to this diff + $db = wfGetDB( DB_SLAVE ); + $change = RecentChange::newFromConds( + array( + // Redundant user,timestamp condition so we can use the existing index + 'rc_user_text' => $this->mNewRev->getRawUserText(), + 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ), + 'rc_this_oldid' => $this->mNewid, + 'rc_last_oldid' => $this->mOldid, + 'rc_patrolled' => 0 + ), + __METHOD__ + ); + if( $change instanceof RecentChange ) { + $rcid = $change->mAttribs['rc_id']; + $this->mRcidMarkPatrolled = $rcid; + } else { + // None found + $rcid = 0; + } + } + // Build the link + if( $rcid ) { + $patrol = ' [' . $sk->link( + $this->mTitle, + wfMsgHtml( 'markaspatrolleddiff' ), + array(), + array( + 'action' => 'markpatrolled', + 'rcid' => $rcid + ), + array( + 'known', + 'noclasses' + ) + ) . ']'; + } else { + $patrol = ''; + } + } else { + $patrol = ''; + } + + # Carry over 'diffonly' param via navigation links + if( $diffOnly != $wgUser->getBoolOption('diffonly') ) { + $query['diffonly'] = $diffOnly; + } + + # Make "previous revision link" + $query['diff'] = 'prev'; + $query['oldid'] = $this->mOldid; + # Cascade unhide param in links for easy deletion browsing + if( $this->unhide ) { + $query['unhide'] = 1; + } + $prevlink = $sk->link( + $this->mTitle, + wfMsgHtml( 'previousdiff' ), + array( + 'id' => 'differences-prevlink' + ), + $query, + array( + 'known', + 'noclasses' + ) + ); + + # Make "next revision link" + $query['diff'] = 'next'; + $query['oldid'] = $this->mNewid; + # Skip next link on the top revision + if( $this->mNewRev->isCurrent() ) { + $nextlink = ' '; + } else { + $nextlink = $sk->link( + $this->mTitle, + wfMsgHtml( 'nextdiff' ), + array( + 'id' => 'differences-nextlink' + ), + $query, + array( + 'known', + 'noclasses' + ) + ); + } + + $oldminor = ''; + $newminor = ''; + + if( $this->mOldRev->isMinor() ) { + $oldminor = ChangesList::flag( 'minor' ); + } + if( $this->mNewRev->isMinor() ) { + $newminor = ChangesList::flag( 'minor' ); + } + + # Handle RevisionDelete links... + $ldel = $this->revisionDeleteLink( $this->mOldRev ); + $rdel = $this->revisionDeleteLink( $this->mNewRev ); + + $oldHeader = '
'.$this->mOldtitle.'
' . + '
' . + $sk->revUserTools( $this->mOldRev, !$this->unhide ).'
' . + '
' . $oldminor . + $sk->revComment( $this->mOldRev, !$diffOnly, !$this->unhide ).$ldel.'
' . + '
' . $prevlink .'
'; + $newHeader = '
'.$this->mNewtitle.'
' . + '
' . $sk->revUserTools( $this->mNewRev, !$this->unhide ) . + " $rollback
" . + '
' . $newminor . + $sk->revComment( $this->mNewRev, !$diffOnly, !$this->unhide ).$rdel.'
' . + '
' . $nextlink . $patrol . '
'; + + # Check if this user can see the revisions + $allowed = $this->mOldRev->userCan(Revision::DELETED_TEXT) + && $this->mNewRev->userCan(Revision::DELETED_TEXT); + # Check if one of the revisions is deleted/suppressed + $deleted = $suppressed = false; + if( $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) { + $deleted = true; // old revisions text is hidden + if( $this->mOldRev->isDeleted(Revision::DELETED_RESTRICTED) ) + $suppressed = true; // also suppressed + } + if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) { + $deleted = true; // new revisions text is hidden + if( $this->mNewRev->isDeleted(Revision::DELETED_RESTRICTED) ) + $suppressed = true; // also suppressed + } + # If the diff cannot be shown due to a deleted revision, then output + # the diff header and links to unhide (if available)... + if( $deleted && (!$this->unhide || !$allowed) ) { + $this->showDiffStyle(); + $multi = $this->getMultiNotice(); + $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) ); + if( !$allowed ) { + $msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff'; + # Give explanation for why revision is not visible + $wgOut->wrapWikiMsg( "\n", + array( $msg ) ); + } else { + # Give explanation and add a link to view the diff... + $link = $this->mTitle->getFullUrl( array( + 'diff' => $this->mNewid, + 'oldid' => $this->mOldid, + 'unhide' => 1 + ) ); + $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff'; + $wgOut->wrapWikiMsg( "\n", array( $msg, $link ) ); + } + # Otherwise, output a regular diff... + } else { + # Add deletion notice if the user is viewing deleted content + $notice = ''; + if( $deleted ) { + $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view'; + $notice = "\n"; + } + $this->showDiff( $oldHeader, $newHeader, $notice ); + if( !$diffOnly ) { + $this->renderNewRevision(); + } + } + wfProfileOut( __METHOD__ ); + } + + protected function revisionDeleteLink( $rev ) { + global $wgUser; + $link = ''; + $canHide = $wgUser->isAllowed( 'deleterevision' ); + // Show del/undel link if: + // (a) the user can delete revisions, or + // (b) the user can view deleted revision *and* this one is deleted + if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' )) ) { + $sk = $wgUser->getSkin(); + if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { + $link = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops + } else { + $query = array( + 'type' => 'revision', + 'target' => $rev->mTitle->getPrefixedDbkey(), + 'ids' => $rev->getId() + ); + $link = $sk->revDeleteLink( $query, + $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide ); + } + $link = '   ' . $link . ' '; + } + return $link; + } + + /** + * Show the new revision of the page. + */ + function renderNewRevision() { + global $wgOut, $wgUser; + wfProfileIn( __METHOD__ ); + + $wgOut->addHTML( "

{$this->mPagetitle}

\n" ); + # Add deleted rev tag if needed + if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) { + $wgOut->wrapWikiMsg( "\n", 'rev-deleted-text-permission' ); + } else if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) { + $wgOut->wrapWikiMsg( "\n", 'rev-deleted-text-view' ); + } + + if( !$this->mNewRev->isCurrent() ) { + $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false ); + } + + $this->loadNewText(); + if( is_object( $this->mNewRev ) ) { + $wgOut->setRevisionId( $this->mNewRev->getId() ); + } + + if( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) { + // Stolen from Article::view --AG 2007-10-11 + // Give hooks a chance to customise the output + if( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mTitle, $wgOut ) ) ) { + // Wrap the whole lot in a
 and don't parse
+				$m = array();
+				preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
+				$wgOut->addHTML( "
\n" );
+				$wgOut->addHTML( htmlspecialchars( $this->mNewtext ) );
+				$wgOut->addHTML( "\n
\n" ); + } + } else { + $wgOut->addWikiTextTidy( $this->mNewtext ); + } + + if( is_object( $this->mNewRev ) && !$this->mNewRev->isCurrent() ) { + $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting ); + } + # Add redundant patrol link on bottom... + if( $this->mRcidMarkPatrolled && $this->mTitle->quickUserCan('patrol') ) { + $sk = $wgUser->getSkin(); + $wgOut->addHTML( + "' + ); + } + + wfProfileOut( __METHOD__ ); + } + + /** + * Show the first revision of an article. Uses normal diff headers in + * contrast to normal "old revision" display style. + */ + function showFirstRevision() { + global $wgOut, $wgUser; + wfProfileIn( __METHOD__ ); + + # Get article text from the DB + # + if ( ! $this->loadNewText() ) { + $t = $this->mTitle->getPrefixedText(); + $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ), $this->mOldid, $this->mNewid ); + $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) ); + $wgOut->addWikiMsg( 'missing-article', "$t", $d ); + wfProfileOut( __METHOD__ ); + return; + } + if ( $this->mNewRev->isCurrent() ) { + $wgOut->setArticleFlag( true ); + } + + # Check if user is allowed to look at this page. If not, bail out. + # + if ( !$this->mTitle->userCanRead() ) { + $wgOut->loginToUse(); + $wgOut->output(); + wfProfileOut( __METHOD__ ); + throw new MWException("Permission Error: you do not have access to view this page"); + } + + # Prepare the header box + # + $sk = $wgUser->getSkin(); + + $next = $this->mTitle->getNextRevisionID( $this->mNewid ); + if( !$next ) { + $nextlink = ''; + } else { + $nextlink = '
' . $sk->link( + $this->mTitle, + wfMsgHtml( 'nextdiff' ), + array( + 'id' => 'differences-nextlink' + ), + array( + 'diff' => 'next', + 'oldid' => $this->mNewid, + ), + array( + 'known', + 'noclasses' + ) + ); + } + $header = "
" . + $sk->revUserTools( $this->mNewRev ) . "
" . $sk->revComment( $this->mNewRev ) . $nextlink . "
\n"; + + $wgOut->addHTML( $header ); + + $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); + + wfProfileOut( __METHOD__ ); + } + + /** + * Get the diff text, send it to $wgOut + * Returns false if the diff could not be generated, otherwise returns true + */ + function showDiff( $otitle, $ntitle, $notice = '' ) { + global $wgOut; + $diff = $this->getDiff( $otitle, $ntitle, $notice ); + if ( $diff === false ) { + $wgOut->addWikiMsg( 'missing-article', "(fixme, bug)", '' ); + return false; + } else { + $this->showDiffStyle(); + $wgOut->addHTML( $diff ); + return true; + } + } + + /** + * Add style sheets and supporting JS for diff display. + */ + function showDiffStyle() { + global $wgStylePath, $wgStyleVersion, $wgOut; + $wgOut->addStyle( 'common/diff.css' ); + + // JS is needed to detect old versions of Mozilla to work around an annoyance bug. + $wgOut->addScript( "" ); + } + + /** + * Get complete diff table, including header + * + * @param Title $otitle Old title + * @param Title $ntitle New title + * @param string $notice HTML between diff header and body + * @return mixed + */ + function getDiff( $otitle, $ntitle, $notice = '' ) { + $body = $this->getDiffBody(); + if ( $body === false ) { + return false; + } else { + $multi = $this->getMultiNotice(); + return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice ); + } + } + + /** + * Get the diff table body, without header + * + * @return mixed + */ + function getDiffBody() { + global $wgMemc; + wfProfileIn( __METHOD__ ); + $this->mCacheHit = true; + // Check if the diff should be hidden from this user + if ( !$this->loadRevisionData() ) + return ''; + if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) { + return ''; + } else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) { + return ''; + } else if ( $this->mOldRev && $this->mNewRev && $this->mOldRev->getID() == $this->mNewRev->getID() ) { + return ''; + } + // Cacheable? + $key = false; + if ( $this->mOldid && $this->mNewid ) { + $key = wfMemcKey( 'diff', 'version', MW_DIFF_VERSION, 'oldid', $this->mOldid, 'newid', $this->mNewid ); + // Try cache + if ( !$this->mRefreshCache ) { + $difftext = $wgMemc->get( $key ); + if ( $difftext ) { + wfIncrStats( 'diff_cache_hit' ); + $difftext = $this->localiseLineNumbers( $difftext ); + $difftext .= "\n\n"; + wfProfileOut( __METHOD__ ); + return $difftext; + } + } // don't try to load but save the result + } + $this->mCacheHit = false; + + // Loadtext is permission safe, this just clears out the diff + if ( !$this->loadText() ) { + wfProfileOut( __METHOD__ ); + return false; + } + + $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext ); + + // Save to cache for 7 days + if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) { + wfIncrStats( 'diff_uncacheable' ); + } else if ( $key !== false && $difftext !== false ) { + wfIncrStats( 'diff_cache_miss' ); + $wgMemc->set( $key, $difftext, 7*86400 ); + } else { + wfIncrStats( 'diff_uncacheable' ); + } + // Replace line numbers with the text in the user's language + if ( $difftext !== false ) { + $difftext = $this->localiseLineNumbers( $difftext ); + } + wfProfileOut( __METHOD__ ); + return $difftext; + } + + /** + * Make sure the proper modules are loaded before we try to + * make the diff + */ + private function initDiffEngines() { + global $wgExternalDiffEngine; + if ( $wgExternalDiffEngine == 'wikidiff' && !function_exists( 'wikidiff_do_diff' ) ) { + wfProfileIn( __METHOD__ . '-php_wikidiff.so' ); + wfSuppressWarnings(); + dl( 'php_wikidiff.so' ); + wfRestoreWarnings(); + wfProfileOut( __METHOD__ . '-php_wikidiff.so' ); + } + else if ( $wgExternalDiffEngine == 'wikidiff2' && !function_exists( 'wikidiff2_do_diff' ) ) { + wfProfileIn( __METHOD__ . '-php_wikidiff2.so' ); + wfSuppressWarnings(); + dl( 'php_wikidiff2.so' ); + wfRestoreWarnings(); + wfProfileOut( __METHOD__ . '-php_wikidiff2.so' ); + } + } + + /** + * Generate a diff, no caching + * $otext and $ntext must be already segmented + */ + function generateDiffBody( $otext, $ntext ) { + global $wgExternalDiffEngine, $wgContLang; + + $otext = str_replace( "\r\n", "\n", $otext ); + $ntext = str_replace( "\r\n", "\n", $ntext ); + + $this->initDiffEngines(); + + if ( $wgExternalDiffEngine == 'wikidiff' && function_exists( 'wikidiff_do_diff' ) ) { + # For historical reasons, external diff engine expects + # input text to be HTML-escaped already + $otext = htmlspecialchars ( $wgContLang->segmentForDiff( $otext ) ); + $ntext = htmlspecialchars ( $wgContLang->segmentForDiff( $ntext ) ); + return $wgContLang->unsegementForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) . + $this->debug( 'wikidiff1' ); + } + + if ( $wgExternalDiffEngine == 'wikidiff2' && function_exists( 'wikidiff2_do_diff' ) ) { + # Better external diff engine, the 2 may some day be dropped + # This one does the escaping and segmenting itself + wfProfileIn( 'wikidiff2_do_diff' ); + $text = wikidiff2_do_diff( $otext, $ntext, 2 ); + $text .= $this->debug( 'wikidiff2' ); + wfProfileOut( 'wikidiff2_do_diff' ); + return $text; + } + if ( $wgExternalDiffEngine != 'wikidiff3' && $wgExternalDiffEngine !== false ) { + # Diff via the shell + global $wgTmpDirectory; + $tempName1 = tempnam( $wgTmpDirectory, 'diff_' ); + $tempName2 = tempnam( $wgTmpDirectory, 'diff_' ); + + $tempFile1 = fopen( $tempName1, "w" ); + if ( !$tempFile1 ) { + wfProfileOut( __METHOD__ ); + return false; + } + $tempFile2 = fopen( $tempName2, "w" ); + if ( !$tempFile2 ) { + wfProfileOut( __METHOD__ ); + return false; + } + fwrite( $tempFile1, $otext ); + fwrite( $tempFile2, $ntext ); + fclose( $tempFile1 ); + fclose( $tempFile2 ); + $cmd = wfEscapeShellArg( $wgExternalDiffEngine, $tempName1, $tempName2 ); + wfProfileIn( __METHOD__ . "-shellexec" ); + $difftext = wfShellExec( $cmd ); + $difftext .= $this->debug( "external $wgExternalDiffEngine" ); + wfProfileOut( __METHOD__ . "-shellexec" ); + unlink( $tempName1 ); + unlink( $tempName2 ); + return $difftext; + } + + # Native PHP diff + $ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) ); + $nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) ); + $diffs = new Diff( $ota, $nta ); + $formatter = new TableDiffFormatter(); + return $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) ) . + $this->debug(); + } + + /** + * Generate a debug comment indicating diff generating time, + * server node, and generator backend. + */ + protected function debug( $generator="internal" ) { + global $wgShowHostnames; + if ( !$this->enableDebugComment ) { + return ''; + } + $data = array( $generator ); + if( $wgShowHostnames ) { + $data[] = wfHostname(); + } + $data[] = wfTimestamp( TS_DB ); + return "\n"; + } + + /** + * Replace line numbers with the text in the user's language + */ + function localiseLineNumbers( $text ) { + return preg_replace_callback( '//', + array( &$this, 'localiseLineNumbersCb' ), $text ); + } + + function localiseLineNumbersCb( $matches ) { + global $wgLang; + if ( $matches[1] === '1' && $this->mReducedLineNumbers ) return ''; + return wfMsgExt( 'lineno', 'escape', $wgLang->formatNum( $matches[1] ) ); + } + + + /** + * If there are revisions between the ones being compared, return a note saying so. + */ + function getMultiNotice() { + if ( !is_object($this->mOldRev) || !is_object($this->mNewRev) ) + return ''; + + if( !$this->mOldPage->equals( $this->mNewPage ) ) { + // Comparing two different pages? Count would be meaningless. + return ''; + } + + $oldid = $this->mOldRev->getId(); + $newid = $this->mNewRev->getId(); + if ( $oldid > $newid ) { + $tmp = $oldid; $oldid = $newid; $newid = $tmp; + } + + $n = $this->mTitle->countRevisionsBetween( $oldid, $newid ); + if ( !$n ) + return ''; + + return wfMsgExt( 'diff-multi', array( 'parseinline' ), $n ); + } + + + /** + * Add the header to a diff body + */ + static function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) { + $header = ""; + if( $diff ) { // Safari/Chrome show broken output if cols not used + $header .= " + + + + "; + $colspan = 2; + $multiColspan = 4; + } else { + $colspan = 1; + $multiColspan = 2; + } + $header .= " + + + + "; + + if ( $multi != '' ) { + $header .= ""; + } + if ( $notice != '' ) { + $header .= ""; + } + + return $header . $diff . "
{$otitle}{$ntitle}
{$multi}
{$notice}
"; + } + + /** + * Use specified text instead of loading from the database + */ + function setText( $oldText, $newText ) { + $this->mOldtext = $oldText; + $this->mNewtext = $newText; + $this->mTextLoaded = 2; + $this->mRevisionsLoaded = true; + } + + /** + * Load revision metadata for the specified articles. If newid is 0, then compare + * the old article in oldid to the current article; if oldid is 0, then + * compare the current article to the immediately previous one (ignoring the + * value of newid). + * + * If oldid is false, leave the corresponding revision object set + * to false. This is impossible via ordinary user input, and is provided for + * API convenience. + */ + function loadRevisionData() { + global $wgLang, $wgUser; + if ( $this->mRevisionsLoaded ) { + return true; + } else { + // Whether it succeeds or fails, we don't want to try again + $this->mRevisionsLoaded = true; + } + + // Load the new revision object + $this->mNewRev = $this->mNewid + ? Revision::newFromId( $this->mNewid ) + : Revision::newFromTitle( $this->mTitle ); + if( !$this->mNewRev instanceof Revision ) + return false; + + // Update the new revision ID in case it was 0 (makes life easier doing UI stuff) + $this->mNewid = $this->mNewRev->getId(); + + // Check if page is editable + $editable = $this->mNewRev->getTitle()->userCan( 'edit' ); + + // Set assorted variables + $timestamp = $wgLang->timeanddate( $this->mNewRev->getTimestamp(), true ); + $dateofrev = $wgLang->date( $this->mNewRev->getTimestamp(), true ); + $timeofrev = $wgLang->time( $this->mNewRev->getTimestamp(), true ); + $this->mNewPage = $this->mNewRev->getTitle(); + if( $this->mNewRev->isCurrent() ) { + $newLink = $this->mNewPage->escapeLocalUrl( array( + 'oldid' => $this->mNewid + ) ); + $this->mPagetitle = htmlspecialchars( wfMsg( + 'currentrev-asof', + $timestamp, + $dateofrev, + $timeofrev + ) ); + $newEdit = $this->mNewPage->escapeLocalUrl( array( + 'action' => 'edit' + ) ); + + $this->mNewtitle = "{$this->mPagetitle}"; + $this->mNewtitle .= " (" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . ")"; + } else { + $newLink = $this->mNewPage->escapeLocalUrl( array( + 'oldid' => $this->mNewid + ) ); + $newEdit = $this->mNewPage->escapeLocalUrl( array( + 'action' => 'edit', + 'oldid' => $this->mNewid + ) ); + $this->mPagetitle = htmlspecialchars( wfMsg( + 'revisionasof', + $timestamp, + $dateofrev, + $timeofrev + ) ); + + $this->mNewtitle = "{$this->mPagetitle}"; + $this->mNewtitle .= " (" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . ")"; + } + if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) { + $this->mNewtitle = "{$this->mPagetitle}"; + } else if ( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) { + $this->mNewtitle = "{$this->mNewtitle}"; + } + + // Load the old revision object + $this->mOldRev = false; + if( $this->mOldid ) { + $this->mOldRev = Revision::newFromId( $this->mOldid ); + } elseif ( $this->mOldid === 0 ) { + $rev = $this->mNewRev->getPrevious(); + if( $rev ) { + $this->mOldid = $rev->getId(); + $this->mOldRev = $rev; + } else { + // No previous revision; mark to show as first-version only. + $this->mOldid = false; + $this->mOldRev = false; + } + }/* elseif ( $this->mOldid === false ) leave mOldRev false; */ + + if( is_null( $this->mOldRev ) ) { + return false; + } + + if ( $this->mOldRev ) { + $this->mOldPage = $this->mOldRev->getTitle(); + + $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true ); + $dateofrev = $wgLang->date( $this->mOldRev->getTimestamp(), true ); + $timeofrev = $wgLang->time( $this->mOldRev->getTimestamp(), true ); + $oldLink = $this->mOldPage->escapeLocalUrl( array( + 'oldid' => $this->mOldid + ) ); + $oldEdit = $this->mOldPage->escapeLocalUrl( array( + 'action' => 'edit', + 'oldid' => $this->mOldid + ) ); + $this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t, $dateofrev, $timeofrev ) ); + + $this->mOldtitle = "{$this->mOldPagetitle}" + . " (" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . ")"; + // Add an "undo" link + $newUndo = $this->mNewPage->escapeLocalUrl( array( + 'action' => 'edit', + 'undoafter' => $this->mOldid, + 'undo' => $this->mNewid + ) ); + $htmlLink = htmlspecialchars( wfMsg( 'editundo' ) ); + $htmlTitle = $wgUser->getSkin()->tooltip( 'undo' ); + if( $editable && !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) { + $this->mNewtitle .= " (" . $htmlLink . ")"; + } + + if( !$this->mOldRev->userCan( Revision::DELETED_TEXT ) ) { + $this->mOldtitle = '' . $this->mOldPagetitle . ''; + } else if( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) { + $this->mOldtitle = '' . $this->mOldtitle . ''; + } + } + + return true; + } + + /** + * Load the text of the revisions, as well as revision data. + */ + function loadText() { + if ( $this->mTextLoaded == 2 ) { + return true; + } else { + // Whether it succeeds or fails, we don't want to try again + $this->mTextLoaded = 2; + } + + if ( !$this->loadRevisionData() ) { + return false; + } + if ( $this->mOldRev ) { + $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER ); + if ( $this->mOldtext === false ) { + return false; + } + } + if ( $this->mNewRev ) { + $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER ); + if ( $this->mNewtext === false ) { + return false; + } + } + return true; + } + + /** + * Load the text of the new revision, not the old one + */ + function loadNewText() { + if ( $this->mTextLoaded >= 1 ) { + return true; + } else { + $this->mTextLoaded = 1; + } + if ( !$this->loadRevisionData() ) { + return false; + } + $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER ); + return true; + } +} -- cgit v1.2.2