From 2a113f6a3a170c8b81338540a20fefd5429c7e9f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 1 Oct 2009 19:32:28 +0000 Subject: [PATCH] fix eol style --- includes/diff/DifferenceInterface.php | 2254 ++++++++++++------------- 1 file changed, 1127 insertions(+), 1127 deletions(-) diff --git a/includes/diff/DifferenceInterface.php b/includes/diff/DifferenceInterface.php index 6cfb9717a5..b8478651b5 100644 --- a/includes/diff/DifferenceInterface.php +++ b/includes/diff/DifferenceInterface.php @@ -1,1127 +1,1127 @@ -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->htmldiff = $htmldiff; - $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, $wgEnableHtmlDiff; - 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; - } - - $htmldiffarg = $this->htmlDiffArgument(); - - if( $htmldiffarg ) { - $query['htmldiff'] = $htmldiffarg['htmldiff']; - } - - # Make "previous revision link" - $query['diff'] = 'prev'; - $query['oldid'] = $this->mOldid; - - $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; - - 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' ); - } - - $rdel = ''; $ldel = ''; - if( $wgUser->isAllowed( 'deletedhistory' ) ) { - // Don't show useless link to people who cannot hide revisions - if( $this->mOldRev->getVisibility() || $wgUser->isAllowed( 'deleterevision' ) ) { - if( !$this->mOldRev->userCan( Revision::DELETED_RESTRICTED ) ) { - // If revision was hidden from sysops - $ldel = Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), - '(' . wfMsgHtml( 'rev-delundel' ) . ')' ); - } else { - $query = array( - 'type' => 'revision', - 'target' => $this->mOldRev->mTitle->getPrefixedDbkey(), - 'ids' => $this->mOldRev->getId() - ); - $ldel = $sk->revDeleteLink( $query, $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) ); - } - $ldel = "   $ldel "; - } - // Don't show useless link to people who cannot hide revisions - if( $this->mNewRev->getVisibility() || $wgUser->isAllowed( 'deleterevision' ) ) { - if( !$this->mNewRev->userCan( Revision::DELETED_RESTRICTED ) ) { - // If revision was hidden from sysops - $rdel = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.wfMsgHtml( 'rev-delundel' ).')' ); - } else { - $query = array( - 'type' => 'revision', - 'target' => $this->mNewRev->mTitle->getPrefixedDbkey(), - 'ids' => $this->mNewRev->getId() - ); - $rdel = $sk->revDeleteLink( $query, $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) ); - } - $rdel = "   $rdel "; - } - } - - $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 - } - # Output the diff if allowed... - if( $deleted && (!$this->unhide || !$allowed) ) { - $this->showDiffStyle(); - $multi = $this->getMultiNotice(); - $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) ); - if( !$allowed ) { - # Give explanation for why revision is not visible - $wgOut->wrapWikiMsg( "\n", - array( 'rev-deleted-no-diff' ) ); - } 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 ) ); - } - } else if( $wgEnableHtmlDiff && $this->htmldiff ) { - $multi = $this->getMultiNotice(); - $wgOut->addHTML( '
' . $sk->link( - $this->mTitle, - wfMsgHtml( 'wikicodecomparison' ), - array( - 'id' => 'differences-switchtype' - ), - array( - 'diff' => $this->mNewid, - 'oldid' => $this->mOldid, - 'htmldiff' => 0 - ), - array( - 'known', - 'noclasses' - ) - ) . '
'); - $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) ); - $this->renderHtmlDiff(); - } else { - if( $wgEnableHtmlDiff ) { - $wgOut->addHTML( '
' . $sk->link( - $this->mTitle, - wfMsgHtml( 'visualcomparison' ), - array( - 'id' => 'differences-switchtype' - ), - array( - 'diff' => $this->mNewid, - 'oldid' => $this->mOldid, - 'htmldiff' => 1 - ), - array( - 'known', - 'noclasses' - ) - ) . '
'); - } - $this->showDiff( $oldHeader, $newHeader ); - if( !$diffOnly ) { - $this->renderNewRevision(); - } - } - wfProfileOut( __METHOD__ ); - } - - /** - * 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__ ); - } - - - function renderHtmlDiff() { - global $wgOut, $wgParser, $wgDebugComments; - wfProfileIn( __METHOD__ ); - - $this->showDiffStyle(); - - $wgOut->addHTML( '

'.wfMsgHtml( 'visual-comparison' )."

\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->loadText(); - - // Old revision - if( is_object( $this->mOldRev ) ) { - $wgOut->setRevisionId( $this->mOldRev->getId() ); - } - - $popts = $wgOut->parserOptions(); - $oldTidy = $popts->setTidy( true ); - $popts->setEditSection( false ); - - $parserOutput = $wgParser->parse( $this->mOldtext, $this->getTitle(), $popts, true, true, $wgOut->getRevisionId() ); - $popts->setTidy( $oldTidy ); - - //only for new? - //$wgOut->addParserOutputNoText( $parserOutput ); - $oldHtml = $parserOutput->getText(); - wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$oldHtml ) ); - - // New revision - if( is_object( $this->mNewRev ) ) { - $wgOut->setRevisionId( $this->mNewRev->getId() ); - } - - $popts = $wgOut->parserOptions(); - $oldTidy = $popts->setTidy( true ); - - $parserOutput = $wgParser->parse( $this->mNewtext, $this->getTitle(), $popts, true, true, $wgOut->getRevisionId() ); - $popts->setTidy( $oldTidy ); - - $wgOut->addParserOutputNoText( $parserOutput ); - $newHtml = $parserOutput->getText(); - wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$newHtml ) ); - - unset($parserOutput, $popts); - - $differ = new HTMLDiffer(new DelegatingContentHandler($wgOut)); - $differ->htmlDiff($oldHtml, $newHtml); - if ( $wgDebugComments ) { - $wgOut->addHTML( "\n" ); - } - - 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, - $this->htmlDiffArgument() - ), - 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__ ); - } - - function htmlDiffArgument(){ - global $wgEnableHtmlDiff; - if($wgEnableHtmlDiff){ - if($this->htmldiff){ - return array( 'htmldiff' => 1 ); - }else{ - return array( 'htmldiff' => 0 ); - } - }else{ - return array(); - } - } - - /** - * Get the diff text, send it to $wgOut - * Returns false if the diff could not be generated, otherwise returns true - */ - function showDiff( $otitle, $ntitle ) { - global $wgOut; - $diff = $this->getDiff( $otitle, $ntitle ); - 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 - * @return mixed - */ - function getDiff( $otitle, $ntitle ) { - $body = $this->getDiffBody(); - if ( $body === false ) { - return false; - } else { - $multi = $this->getMultiNotice(); - return $this->addHeader( $body, $otitle, $ntitle, $multi ); - } - } - - /** - * 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 = '' ) { - $colspan = 1; - $header = ""; - if( $diff ) { // Safari/Chrome show broken output if cols not used - $header .= " - - - - "; - $colspan = 2; - } - $header .= " - - - - "; - - if ( $multi != '' ) - $header .= ""; - - return $header . $diff . "
{$otitle}{$ntitle}
{$multi}
"; - } - - /** - * 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; - } -} +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->htmldiff = $htmldiff; + $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, $wgEnableHtmlDiff; + 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; + } + + $htmldiffarg = $this->htmlDiffArgument(); + + if( $htmldiffarg ) { + $query['htmldiff'] = $htmldiffarg['htmldiff']; + } + + # Make "previous revision link" + $query['diff'] = 'prev'; + $query['oldid'] = $this->mOldid; + + $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; + + 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' ); + } + + $rdel = ''; $ldel = ''; + if( $wgUser->isAllowed( 'deletedhistory' ) ) { + // Don't show useless link to people who cannot hide revisions + if( $this->mOldRev->getVisibility() || $wgUser->isAllowed( 'deleterevision' ) ) { + if( !$this->mOldRev->userCan( Revision::DELETED_RESTRICTED ) ) { + // If revision was hidden from sysops + $ldel = Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), + '(' . wfMsgHtml( 'rev-delundel' ) . ')' ); + } else { + $query = array( + 'type' => 'revision', + 'target' => $this->mOldRev->mTitle->getPrefixedDbkey(), + 'ids' => $this->mOldRev->getId() + ); + $ldel = $sk->revDeleteLink( $query, $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) ); + } + $ldel = "   $ldel "; + } + // Don't show useless link to people who cannot hide revisions + if( $this->mNewRev->getVisibility() || $wgUser->isAllowed( 'deleterevision' ) ) { + if( !$this->mNewRev->userCan( Revision::DELETED_RESTRICTED ) ) { + // If revision was hidden from sysops + $rdel = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.wfMsgHtml( 'rev-delundel' ).')' ); + } else { + $query = array( + 'type' => 'revision', + 'target' => $this->mNewRev->mTitle->getPrefixedDbkey(), + 'ids' => $this->mNewRev->getId() + ); + $rdel = $sk->revDeleteLink( $query, $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) ); + } + $rdel = "   $rdel "; + } + } + + $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 + } + # Output the diff if allowed... + if( $deleted && (!$this->unhide || !$allowed) ) { + $this->showDiffStyle(); + $multi = $this->getMultiNotice(); + $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) ); + if( !$allowed ) { + # Give explanation for why revision is not visible + $wgOut->wrapWikiMsg( "\n", + array( 'rev-deleted-no-diff' ) ); + } 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 ) ); + } + } else if( $wgEnableHtmlDiff && $this->htmldiff ) { + $multi = $this->getMultiNotice(); + $wgOut->addHTML( '
' . $sk->link( + $this->mTitle, + wfMsgHtml( 'wikicodecomparison' ), + array( + 'id' => 'differences-switchtype' + ), + array( + 'diff' => $this->mNewid, + 'oldid' => $this->mOldid, + 'htmldiff' => 0 + ), + array( + 'known', + 'noclasses' + ) + ) . '
'); + $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) ); + $this->renderHtmlDiff(); + } else { + if( $wgEnableHtmlDiff ) { + $wgOut->addHTML( '
' . $sk->link( + $this->mTitle, + wfMsgHtml( 'visualcomparison' ), + array( + 'id' => 'differences-switchtype' + ), + array( + 'diff' => $this->mNewid, + 'oldid' => $this->mOldid, + 'htmldiff' => 1 + ), + array( + 'known', + 'noclasses' + ) + ) . '
'); + } + $this->showDiff( $oldHeader, $newHeader ); + if( !$diffOnly ) { + $this->renderNewRevision(); + } + } + wfProfileOut( __METHOD__ ); + } + + /** + * 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__ ); + } + + + function renderHtmlDiff() { + global $wgOut, $wgParser, $wgDebugComments; + wfProfileIn( __METHOD__ ); + + $this->showDiffStyle(); + + $wgOut->addHTML( '

'.wfMsgHtml( 'visual-comparison' )."

\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->loadText(); + + // Old revision + if( is_object( $this->mOldRev ) ) { + $wgOut->setRevisionId( $this->mOldRev->getId() ); + } + + $popts = $wgOut->parserOptions(); + $oldTidy = $popts->setTidy( true ); + $popts->setEditSection( false ); + + $parserOutput = $wgParser->parse( $this->mOldtext, $this->getTitle(), $popts, true, true, $wgOut->getRevisionId() ); + $popts->setTidy( $oldTidy ); + + //only for new? + //$wgOut->addParserOutputNoText( $parserOutput ); + $oldHtml = $parserOutput->getText(); + wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$oldHtml ) ); + + // New revision + if( is_object( $this->mNewRev ) ) { + $wgOut->setRevisionId( $this->mNewRev->getId() ); + } + + $popts = $wgOut->parserOptions(); + $oldTidy = $popts->setTidy( true ); + + $parserOutput = $wgParser->parse( $this->mNewtext, $this->getTitle(), $popts, true, true, $wgOut->getRevisionId() ); + $popts->setTidy( $oldTidy ); + + $wgOut->addParserOutputNoText( $parserOutput ); + $newHtml = $parserOutput->getText(); + wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$newHtml ) ); + + unset($parserOutput, $popts); + + $differ = new HTMLDiffer(new DelegatingContentHandler($wgOut)); + $differ->htmlDiff($oldHtml, $newHtml); + if ( $wgDebugComments ) { + $wgOut->addHTML( "\n" ); + } + + 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, + $this->htmlDiffArgument() + ), + 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__ ); + } + + function htmlDiffArgument(){ + global $wgEnableHtmlDiff; + if($wgEnableHtmlDiff){ + if($this->htmldiff){ + return array( 'htmldiff' => 1 ); + }else{ + return array( 'htmldiff' => 0 ); + } + }else{ + return array(); + } + } + + /** + * Get the diff text, send it to $wgOut + * Returns false if the diff could not be generated, otherwise returns true + */ + function showDiff( $otitle, $ntitle ) { + global $wgOut; + $diff = $this->getDiff( $otitle, $ntitle ); + 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 + * @return mixed + */ + function getDiff( $otitle, $ntitle ) { + $body = $this->getDiffBody(); + if ( $body === false ) { + return false; + } else { + $multi = $this->getMultiNotice(); + return $this->addHeader( $body, $otitle, $ntitle, $multi ); + } + } + + /** + * 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 = '' ) { + $colspan = 1; + $header = ""; + if( $diff ) { // Safari/Chrome show broken output if cols not used + $header .= " + + + + "; + $colspan = 2; + } + $header .= " + + + + "; + + if ( $multi != '' ) + $header .= ""; + + return $header . $diff . "
{$otitle}{$ntitle}
{$multi}
"; + } + + /** + * 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; + } +} -- 2.20.1