* fixes important bugs or such to force cached diff views to
* clear.
*/
-define( 'MW_DIFF_VERSION', '1.11' );
+define( 'MW_DIFF_VERSION', '1.11a' );
/**
* @todo document
* @param $old Integer: old ID we want to show and diff with.
* @param $new String: either 'prev' or 'next'.
* @param $rcid Integer: ??? FIXME (default 0)
+ * @param $refreshCache boolean If set, refreshes the diff cache
*/
- function DifferenceEngine( $titleObj = null, $old = 0, $new = 0, $rcid = 0 ) {
+ function DifferenceEngine( $titleObj = null, $old = 0, $new = 0, $rcid = 0, $refreshCache = false ) {
$this->mTitle = $titleObj;
wfDebug("DifferenceEngine old '$old' new '$new' rcid '$rcid'\n");
$this->mNewid = intval($new);
}
$this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer
+ $this->mRefreshCache = $refreshCache;
}
function showDiffPage( $diffOnly = false ) {
} else {
$rollback = '';
}
- if( $wgUseRCPatrol && $this->mRcidMarkPatrolled != 0 && $wgUser->isAllowed( 'patrol' ) ) {
- $patrol = ' [' . $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'markaspatrolleddiff' ), "action=markpatrolled&rcid={$this->mRcidMarkPatrolled}" ) . ']';
+
+ // Prepare a change patrol link, if applicable
+ if( $wgUseRCPatrol && $wgUser->isAllowed( 'patrol' ) ) {
+ // If we've been given an explicit change identifier, use it; saves time
+ if( $this->mRcidMarkPatrolled ) {
+ $rcid = $this->mRcidMarkPatrolled;
+ } else {
+ // Look for an unpatrolled change corresponding to this diff
+ $db = wfGetDB( DB_SLAVE );
+ $change = RecentChange::newFromConds(
+ array(
+ // Add redundant timestamp condition so we can use the
+ // existing index
+ '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'];
+ } else {
+ // None found
+ $rcid = 0;
+ }
+ }
+ // Build the link
+ if( $rcid ) {
+ $patrol = ' [' . $sk->makeKnownLinkObj(
+ $this->mTitle,
+ wfMsgHtml( 'markaspatrolleddiff' ),
+ "action=markpatrolled&rcid={$rcid}"
+ ) . ']';
+ } else {
+ $patrol = '';
+ }
} else {
$patrol = '';
}
$newminor = wfElement( 'span', array( 'class' => 'minor' ),
wfMsg( 'minoreditletter') ) . ' ';
}
+
+ $rdel = ''; $ldel = '';
+ if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ if( !$this->mOldRev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ // If revision was hidden from sysops
+ $ldel = wfMsgHtml('rev-delundel');
+ } else {
+ $ldel = $sk->makeKnownLinkObj( $revdel,
+ wfMsgHtml('rev-delundel'),
+ 'target=' . urlencode( $this->mOldRev->mTitle->getPrefixedDbkey() ) .
+ '&oldid=' . urlencode( $this->mOldRev->getId() ) );
+ // Bolden oversighted content
+ if( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $ldel = "<strong>$ldel</strong>";
+ }
+ $ldel = " <tt>(<small>$ldel</small>)</tt> ";
+ // We don't currently handle well changing the top revision's settings
+ if( $this->mNewRev->isCurrent() ) {
+ // If revision was hidden from sysops
+ $rdel = wfMsgHtml('rev-delundel');
+ } else if( !$this->mNewRev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ // If revision was hidden from sysops
+ $rdel = wfMsgHtml('rev-delundel');
+ } else {
+ $rdel = $sk->makeKnownLinkObj( $revdel,
+ wfMsgHtml('rev-delundel'),
+ 'target=' . urlencode( $this->mNewRev->mTitle->getPrefixedDbkey() ) .
+ '&oldid=' . urlencode( $this->mNewRev->getId() ) );
+ // Bolden oversighted content
+ if( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $rdel = "<strong>$rdel</strong>";
+ }
+ $rdel = " <tt>(<small>$rdel</small>)</tt> ";
+ }
- $oldHeader = "<strong>{$this->mOldtitle}</strong><br />" .
- $sk->revUserTools( $this->mOldRev ) . "<br />" .
- $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly ) . "<br />" .
- $prevlink;
- $newHeader = "<strong>{$this->mNewtitle}</strong><br />" .
- $sk->revUserTools( $this->mNewRev ) . " $rollback<br />" .
- $newminor . $sk->revComment( $this->mNewRev, !$diffOnly ) . "<br />" .
- $nextlink . $patrol;
+ $oldHeader = '<div id="mw-diff-otitle1"><strong>'.$this->mOldtitle.'</strong></div>' .
+ '<div id="mw-diff-otitle2">' . $sk->revUserTools( $this->mOldRev, true ) . "</div>" .
+ '<div id="mw-diff-otitle3">' . $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly, true ) . $ldel . "</div>" .
+ '<div id="mw-diff-otitle4">' . $prevlink .'</div>';
+ $newHeader = '<div id="mw-diff-ntitle1"><strong>'.$this->mNewtitle.'</strong></div>' .
+ '<div id="mw-diff-ntitle2">' . $sk->revUserTools( $this->mNewRev, true ) . " $rollback</div>" .
+ '<div id="mw-diff-ntitle3">' . $newminor . $sk->revComment( $this->mNewRev, !$diffOnly, true ) . $rdel . "</div>" .
+ '<div id="mw-diff-ntitle4">' . $nextlink . $patrol . '</div>';
$this->showDiff( $oldHeader, $newHeader );
$wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
#add deleted rev tag if needed
- if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
+ if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
$wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
+ } else if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+ $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) );
}
if( !$this->mNewRev->isCurrent() ) {
$wgOut->setRevisionId( $this->mNewRev->getId() );
}
- $wgOut->addWikiTextTidy( $this->mNewtext );
+ 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 <pre> and don't parse
+ $m = array();
+ preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
+ $wgOut->addHtml( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
+ $wgOut->addHtml( htmlspecialchars( $this->mNewtext ) );
+ $wgOut->addHtml( "\n</pre>\n" );
+ }
+ } else
+ $wgOut->addWikiTextTidy( $this->mNewtext );
if( !$this->mNewRev->isCurrent() ) {
$wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
$wgOut->addWikitext( wfMsg( 'missingarticle', "<nowiki>(fixme, bug)</nowiki>" ) );
return false;
} else {
- $wgOut->addStyle( 'common/diff.css' );
+ $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( "<script type=\"text/javascript\" src=\"$wgStylePath/common/diff.js?$wgStyleVersion\"></script>" );
+ }
/**
- * Get diff table, including header
- * Note that the interface has changed, it's no longer static.
- * Returns false on error
+ * 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();
/**
* Get the diff table body, without header
- * Results are cached
- * Returns false on error
+ *
+ * @return mixed
*/
function getDiffBody() {
global $wgMemc;
// Cacheable?
$key = false;
if ( $this->mOldid && $this->mNewid ) {
- // Try cache
$key = wfMemcKey( 'diff', 'version', MW_DIFF_VERSION, 'oldid', $this->mOldid, 'newid', $this->mNewid );
- $difftext = $wgMemc->get( $key );
- if ( $difftext ) {
- wfIncrStats( 'diff_cache_hit' );
- $difftext = $this->localiseLineNumbers( $difftext );
- $difftext .= "\n<!-- diff cache key $key -->\n";
- wfProfileOut( $fname );
- return $difftext;
- }
+ // Try cache
+ if ( !$this->mRefreshCache ) {
+ $difftext = $wgMemc->get( $key );
+ if ( $difftext ) {
+ wfIncrStats( 'diff_cache_hit' );
+ $difftext = $this->localiseLineNumbers( $difftext );
+ $difftext .= "\n<!-- diff cache key $key -->\n";
+ wfProfileOut( $fname );
+ return $difftext;
+ }
+ } // don't try to load but save the result
}
- #loadtext is permission safe, this just clears out the diff
+ // Loadtext is permission safe, this just clears out the diff
if ( !$this->loadText() ) {
wfProfileOut( $fname );
return false;
} else if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
- return '';
+ return '';
} else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
- return '';
+ return '';
}
$difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
// Save to cache for 7 days
- if ( $key !== false && $difftext !== false ) {
+ // Only do this for public revs, otherwise an admin can view the diff and a non-admin can nab it!
+ if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
+ wfIncrStats( 'diff_uncacheable' );
+ } else if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+ wfIncrStats( 'diff_uncacheable' );
+ } else if ( $key !== false && $difftext !== false ) {
wfIncrStats( 'diff_cache_miss' );
$wgMemc->set( $key, $difftext, 7*86400 );
} else {
/**
* Add the header to a diff body
*/
- function addHeader( $diff, $otitle, $ntitle, $multi = '' ) {
+ static function addHeader( $diff, $otitle, $ntitle, $multi = '' ) {
global $wgOut;
-
- if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
- $otitle = '<span class="history-deleted">'.$otitle.'</span>';
- }
- if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
- $ntitle = '<span class="history-deleted">'.$ntitle.'</span>';
- }
+
$header = "
<table class='diff'>
<col class='diff-marker' />
}
// Load the new revision object
- if( $this->mNewid ) {
- $this->mNewRev = Revision::newFromId( $this->mNewid );
- } else {
- $this->mNewRev = Revision::newFromTitle( $this->mTitle );
- }
-
- if( is_null( $this->mNewRev ) ) {
+ $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();
+
// Set assorted variables
$timestamp = $wgLang->timeanddate( $this->mNewRev->getTimestamp(), true );
$this->mNewPage = $this->mNewRev->getTitle();
} else {
$newLink = $this->mNewPage->escapeLocalUrl( 'oldid=' . $this->mNewid );
$newEdit = $this->mNewPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mNewid );
- $this->mPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $timestamp ) );
+ $this->mPagetitle = wfMsgHTML( 'revisionasof', $timestamp );
$this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>"
. " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
}
+ if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
+ $this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
+ } else if ( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+ $this->mNewtitle = '<span class="history-deleted">'.$this->mNewtitle.'</span>';
+ }
// Load the old revision object
$this->mOldRev = false;
$t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
$oldLink = $this->mOldPage->escapeLocalUrl( 'oldid=' . $this->mOldid );
$oldEdit = $this->mOldPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mOldid );
- $this->mOldtitle = "<a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) )
- . "</a> (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
- //now that we considered old rev, we can make undo link (bug 8133, multi-edit undo)
+ $this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t ) );
+
+ $this->mOldtitle = "<a href='$oldLink'>{$this->mOldPagetitle}</a>"
+ . " (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
+ // Add an "undo" link
$newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undoafter=' . $this->mOldid . '&undo=' . $this->mNewid);
- $this->mNewtitle .= " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
+ if ( $this->mNewRev->userCan(Revision::DELETED_TEXT) )
+ $this->mNewtitle .= " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
+
+ if ( !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
+ $this->mOldtitle = "<span class='history-deleted'>{$this->mOldPagetitle}</span>";
+ } else if ( $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
+ $this->mOldtitle = '<span class="history-deleted">'.$this->mOldtitle.'</span>';
+ }
}
return true;
return false;
}
if ( $this->mOldRev ) {
- // FIXME: permission tests
$this->mOldtext = $this->mOldRev->revText();
if ( $this->mOldtext === false ) {
return false;
}
function _start_block($header) {
- echo $header;
+ echo $header . "\n";
}
function _end_block() {
}
}
+/**
+ * A formatter that outputs unified diffs
+ * @addtogroup DifferenceEngine
+ */
+
+class UnifiedDiffFormatter extends DiffFormatter
+{
+ var $leading_context_lines = 2;
+ var $trailing_context_lines = 2;
+
+ function _added($lines) {
+ $this->_lines($lines, '+');
+ }
+ function _deleted($lines) {
+ $this->_lines($lines, '-');
+ }
+ function _changed($orig, $closing) {
+ $this->_deleted($orig);
+ $this->_added($closing);
+ }
+ function _block_header($xbeg, $xlen, $ybeg, $ylen) {
+ return "@@ -$xbeg,$xlen +$ybeg,$ylen @@";
+ }
+}
+
+/**
+ * A pseudo-formatter that just passes along the Diff::$edits array
+ * @addtogroup DifferenceEngine
+ */
+class ArrayDiffFormatter extends DiffFormatter
+{
+ function format($diff)
+ {
+ $oldline = 1;
+ $newline = 1;
+ $retval = array();
+ foreach($diff->edits as $edit)
+ switch($edit->type)
+ {
+ case 'add':
+ foreach($edit->closing as $l)
+ {
+ $retval[] = array(
+ 'action' => 'add',
+ 'new'=> $l,
+ 'newline' => $newline++
+ );
+ }
+ break;
+ case 'delete':
+ foreach($edit->orig as $l)
+ {
+ $retval[] = array(
+ 'action' => 'delete',
+ 'old' => $l,
+ 'oldline' => $oldline++,
+ );
+ }
+ break;
+ case 'change':
+ foreach($edit->orig as $i => $l)
+ {
+ $retval[] = array(
+ 'action' => 'change',
+ 'old' => $l,
+ 'new' => @$edit->closing[$i],
+ 'oldline' => $oldline++,
+ 'newline' => $newline++,
+ );
+ }
+ break;
+ case 'copy':
+ $oldline += count($edit->orig);
+ $newline += count($edit->orig);
+ }
+ return $retval;
+ }
+}
/**
* Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
function _added( $lines ) {
foreach ($lines as $line) {
echo '<tr>' . $this->emptyLine() .
- $this->addedLine( htmlspecialchars ( $line ) ) . "</tr>\n";
+ $this->addedLine( '<ins class="diffchange">' .
+ htmlspecialchars ( $line ) . '</ins>' ) . "</tr>\n";
}
}
function _deleted($lines) {
foreach ($lines as $line) {
- echo '<tr>' . $this->deletedLine( htmlspecialchars ( $line ) ) .
+ echo '<tr>' . $this->deletedLine( '<del class="diffchange">' .
+ htmlspecialchars ( $line ) . '</del>' ) .
$this->emptyLine() . "</tr>\n";
}
}
}
}
-?>
+
+