X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FDifferenceEngine.php;h=349c88ec58923a79cfba4f928bf139e0ff03fee5;hb=839f3ffd651465380923a5110dc834c23fd9c27f;hp=af65ce3aaf31bfffa566b8772b6f476277c7abaf;hpb=113bb1c772d2ddb70345e5027676338ad00f1c2a;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/DifferenceEngine.php b/includes/DifferenceEngine.php index af65ce3aaf..349c88ec58 100644 --- a/includes/DifferenceEngine.php +++ b/includes/DifferenceEngine.php @@ -5,6 +5,14 @@ * @addtogroup DifferenceEngine */ +/** + * Constant to indicate diff cache compatibility. + * Bump this when changing the diff formatting in a way that + * fixes important bugs or such to force cached diff views to + * clear. + */ +define( 'MW_DIFF_VERSION', '1.11a' ); + /** * @todo document * @public @@ -30,8 +38,9 @@ class DifferenceEngine { * @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"); @@ -60,6 +69,7 @@ class DifferenceEngine { $this->mNewid = intval($new); } $this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer + $this->mRefreshCache = $refreshCache; } function showDiffPage( $diffOnly = false ) { @@ -148,8 +158,43 @@ CONTROL; } 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 = ''; } @@ -175,15 +220,50 @@ CONTROL; $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 = "$ldel"; + } + $ldel = "   ($ldel) "; + // 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 = "$rdel"; + } + $rdel = "   ($rdel) "; + } - $oldHeader = "{$this->mOldtitle}
" . - $sk->revUserTools( $this->mOldRev ) . "
" . - $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly ) . "
" . - $prevlink; - $newHeader = "{$this->mNewtitle}
" . - $sk->revUserTools( $this->mNewRev ) . " $rollback
" . - $newminor . $sk->revComment( $this->mNewRev, !$diffOnly ) . "
" . - $nextlink . $patrol; + $oldHeader = '
'.$this->mOldtitle.'
' . + '
' . $sk->revUserTools( $this->mOldRev, true ) . "
" . + '
' . $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly, true ) . $ldel . "
" . + '
' . $prevlink .'
'; + $newHeader = '
'.$this->mNewtitle.'
' . + '
' . $sk->revUserTools( $this->mNewRev, true ) . " $rollback
" . + '
' . $newminor . $sk->revComment( $this->mNewRev, !$diffOnly, true ) . $rdel . "
" . + '
' . $nextlink . $patrol . '
'; $this->showDiff( $oldHeader, $newHeader ); @@ -203,8 +283,10 @@ CONTROL; $wgOut->addHTML( "

{$this->mPagetitle}

\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() ) { @@ -216,7 +298,20 @@ CONTROL; $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
 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( !$this->mNewRev->isCurrent() ) { $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting ); @@ -288,15 +383,29 @@ CONTROL; $wgOut->addWikitext( wfMsg( 'missingarticle', "(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 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(); @@ -310,8 +419,8 @@ CONTROL; /** * Get the diff table body, without header - * Results are cached - * Returns false on error + * + * @return mixed */ function getDiffBody() { global $wgMemc; @@ -321,32 +430,39 @@ CONTROL; // Cacheable? $key = false; if ( $this->mOldid && $this->mNewid ) { + $key = wfMemcKey( 'diff', 'version', MW_DIFF_VERSION, 'oldid', $this->mOldid, 'newid', $this->mNewid ); // Try cache - $key = wfMemcKey( 'diff', 'oldid', $this->mOldid, 'newid', $this->mNewid ); - $difftext = $wgMemc->get( $key ); - if ( $difftext ) { - wfIncrStats( 'diff_cache_hit' ); - $difftext = $this->localiseLineNumbers( $difftext ); - $difftext .= "\n\n"; - wfProfileOut( $fname ); - return $difftext; - } + if ( !$this->mRefreshCache ) { + $difftext = $wgMemc->get( $key ); + if ( $difftext ) { + wfIncrStats( 'diff_cache_hit' ); + $difftext = $this->localiseLineNumbers( $difftext ); + $difftext .= "\n\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 { @@ -478,20 +594,18 @@ CONTROL; /** * 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 = ''.$otitle.''; - } - if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) { - $ntitle = ''.$ntitle.''; - } + $header = " - +
+ + + + - - + + "; @@ -530,16 +644,15 @@ CONTROL; } // 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(); @@ -554,11 +667,16 @@ CONTROL; } 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 = "{$this->mPagetitle}" . " (" . htmlspecialchars( wfMsg( 'editold' ) ) . ")"; } + 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; @@ -586,11 +704,20 @@ CONTROL; $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 = "" . htmlspecialchars( wfMsg( 'revisionasof', $t ) ) - . " (" . htmlspecialchars( wfMsg( 'editold' ) ) . ")"; - //now that we considered old rev, we can make undo link (bug 8133, multi-edit undo) + $this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t ) ); + + $this->mOldtitle = "{$this->mOldPagetitle}" + . " (" . htmlspecialchars( wfMsg( 'editold' ) ) . ")"; + // Add an "undo" link $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undoafter=' . $this->mOldid . '&undo=' . $this->mNewid); - $this->mNewtitle .= " (" . htmlspecialchars( wfMsg( 'editundo' ) ) . ")"; + if ( $this->mNewRev->userCan(Revision::DELETED_TEXT) ) + $this->mNewtitle .= " (" . htmlspecialchars( wfMsg( 'editundo' ) ) . ")"; + + 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; @@ -611,7 +738,6 @@ CONTROL; return false; } if ( $this->mOldRev ) { - // FIXME: permission tests $this->mOldtext = $this->mOldRev->revText(); if ( $this->mOldtext === false ) { return false; @@ -1522,7 +1648,7 @@ class DiffFormatter } function _start_block($header) { - echo $header; + echo $header . "\n"; } function _end_block() { @@ -1551,6 +1677,84 @@ class DiffFormatter } } +/** + * 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 @@ -1721,8 +1925,8 @@ class TableDiffFormatter extends DiffFormatter } function _block_header( $xbeg, $xlen, $ybeg, $ylen ) { - $r = '\n" . - '\n"; + $r = '\n" . + '\n"; return $r; } @@ -1738,17 +1942,25 @@ class TableDiffFormatter extends DiffFormatter # HTML-escape parameter before calling this function addedLine( $line ) { - return ""; + return $this->wrapLine( '+', 'diff-addedline', $line ); } # HTML-escape parameter before calling this function deletedLine( $line ) { - return ""; + return $this->wrapLine( '-', 'diff-deletedline', $line ); } # HTML-escape parameter before calling this function contextLine( $line ) { - return ""; + return $this->wrapLine( ' ', 'diff-context', $line ); + } + + private function wrapLine( $marker, $class, $line ) { + if( $line !== '' ) { + // The
wrapper is needed for 'overflow: auto' style to scroll properly + $line = "
$line
"; + } + return "
"; } function emptyLine() { @@ -1758,13 +1970,15 @@ class TableDiffFormatter extends DiffFormatter function _added( $lines ) { foreach ($lines as $line) { echo '' . $this->emptyLine() . - $this->addedLine( htmlspecialchars ( $line ) ) . "\n"; + $this->addedLine( '' . + htmlspecialchars ( $line ) . '' ) . "\n"; } } function _deleted($lines) { foreach ($lines as $line) { - echo '' . $this->deletedLine( htmlspecialchars ( $line ) ) . + echo '' . $this->deletedLine( '' . + htmlspecialchars ( $line ) . '' ) . $this->emptyLine() . "\n"; } } @@ -1801,4 +2015,5 @@ class TableDiffFormatter extends DiffFormatter } } -?> + +
{$otitle}{$ntitle}{$otitle}{$ntitle}
+{$line}-{$line} {$line}$marker$line