39 $this->mTitle = $titleObj;
40 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'\n");
42 if (
'prev' === $new ) {
43 # Show diff between revision $old and the previous one.
44 # Get previous one from DB.
46 $this->mNewid = intval($old);
48 $this->mOldid = $this->mTitle->getPreviousRevisionID( $this->mNewid );
50 } elseif (
'next' === $new ) {
51 # Show diff between revision $old and the previous one.
52 # Get previous one from DB.
54 $this->mOldid = intval($old);
55 $this->mNewid = $this->mTitle->getNextRevisionID( $this->mOldid );
56 if (
false === $this->mNewid ) {
57 # if no result, NewId points to the newest old revision. The only newer
58 # revision is cur, which is "0".
63 $this->mOldid = intval($old);
64 $this->mNewid = intval($new);
66 $this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer
70 global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
71 $fname =
'DifferenceEngine::showDiffPage';
74 # If external diffs are enabled both globally and for the user,
75 # we'll use the application/x-external-editor interface to call
76 # an external diff tool like kompare, kdiff3, etc.
77 if($wgUseExternalEditor && $wgUser->getOption(
'externaldiff')) {
78 global $wgInputEncoding,$wgServer,$wgScript,$wgLang;
80 header (
"Content-type: application/x-external-editor; charset=".$wgInputEncoding );
81 $url1=$this->mTitle->getFullURL(
"action=raw&oldid=".$this->mOldid);
82 $url2=$this->mTitle->getFullURL(
"action=raw&oldid=".$this->mNewid);
83 $special=$wgLang->getNsText(NS_SPECIAL);
88Script={$wgServer}{$wgScript}
89Special
namespace={$special}
103 $wgOut->setArticleFlag(
false );
105 $t = $this->mTitle->getPrefixedText() .
" (Diff: {$this->mOldid}, {$this->mNewid})";
106 $mtext =
wfMsg(
'missingarticle',
"<nowiki>$t</nowiki>" );
107 $wgOut->setPagetitle(
wfMsg(
'errorpagetitle' ) );
108 $wgOut->addWikitext( $mtext );
113 wfRunHooks(
'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
115 if ( $this->mNewRev->isCurrent() ) {
116 $wgOut->setArticleFlag(
true );
119 # mOldid is false if the difference engine is called with a "vague" query for
120 # a diff between a version V and its previous version V' AND the version V
121 # is the first version of that article. In that case, V' does not exist.
122 if ( $this->mOldid ===
false ) {
129 $wgOut->suppressQuickbar();
131 $oldTitle = $this->mOldPage->getPrefixedText();
132 $newTitle = $this->mNewPage->getPrefixedText();
133 if( $oldTitle == $newTitle ) {
134 $wgOut->setPageTitle( $newTitle );
136 $wgOut->setPageTitle( $oldTitle .
', ' . $newTitle );
138 $wgOut->setSubtitle(
wfMsg(
'difference' ) );
139 $wgOut->setRobotpolicy(
'noindex,nofollow' );
141 if ( !( $this->mOldPage->userCanRead() && $this->mNewPage->userCanRead() ) ) {
142 $wgOut->loginToUse();
148 $sk = $wgUser->getSkin();
150 if ( $this->mNewRev->isCurrent() && $wgUser->isAllowed(
'rollback') ) {
151 $rollback =
' ' . $sk->generateRollback( $this->mNewRev );
155 if( $wgUseRCPatrol && $this->mRcidMarkPatrolled != 0 && $wgUser->isAllowed(
'patrol' ) ) {
156 $patrol =
' [' . $sk->makeKnownLinkObj( $this->mTitle,
wfMsg(
'markaspatrolleddiff' ),
"action=markpatrolled&rcid={$this->mRcidMarkPatrolled}" ) .
']';
161 $prevlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml(
'previousdiff' ),
162 'diff=prev&oldid='.$this->mOldid,
'',
'',
'id="differences-prevlink"' );
163 if ( $this->mNewRev->isCurrent() ) {
164 $nextlink =
' ';
166 $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml(
'nextdiff' ),
167 'diff=next&oldid='.$this->mNewid,
'',
'',
'id="differences-nextlink"' );
173 if ($this->mOldRev->mMinorEdit == 1) {
174 $oldminor = wfElement(
'span', array(
'class' =>
'minor' ),
175 wfMsg(
'minoreditletter') ) .
' ';
178 if ($this->mNewRev->mMinorEdit == 1) {
179 $newminor = wfElement(
'span', array(
'class' =>
'minor' ),
180 wfMsg(
'minoreditletter') ) .
' ';
183 $oldHeader =
"<strong>{$this->mOldtitle}</strong><br />" .
184 $sk->revUserTools( $this->mOldRev ) .
"<br />" .
185 $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly ) .
"<br />" .
187 $newHeader =
"<strong>{$this->mNewtitle}</strong><br />" .
188 $sk->revUserTools( $this->mNewRev ) .
" $rollback<br />" .
189 $newminor . $sk->revComment( $this->mNewRev, !$diffOnly ) .
"<br />" .
192 $this->
showDiff( $oldHeader, $newHeader );
205 $fname =
'DifferenceEngine::renderNewRevision';
208 $wgOut->addHTML(
"<hr /><h2>{$this->mPagetitle}</h2>\n" );
209 #add deleted rev tag if needed
210 if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
211 $wgOut->addWikiText(
wfMsg(
'rev-deleted-text-permission' ) );
214 if( !$this->mNewRev->isCurrent() ) {
215 $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection(
false );
219 if( is_object( $this->mNewRev ) ) {
220 $wgOut->setRevisionId( $this->mNewRev->getId() );
223 $wgOut->addWikiTextTidy( $this->mNewtext );
225 if( !$this->mNewRev->isCurrent() ) {
226 $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
237 global $wgOut, $wgUser;
239 $fname =
'DifferenceEngine::showFirstRevision';
242 # Get article text from the DB
245 $t = $this->mTitle->getPrefixedText() .
" (Diff: {$this->mOldid}, " .
247 $mtext =
wfMsg(
'missingarticle',
"<nowiki>$t</nowiki>" );
248 $wgOut->setPagetitle(
wfMsg(
'errorpagetitle' ) );
249 $wgOut->addWikitext( $mtext );
253 if ( $this->mNewRev->isCurrent() ) {
254 $wgOut->setArticleFlag(
true );
257 # Check if user is allowed to look at this page. If not, bail out.
259 if ( !( $this->mTitle->userCanRead() ) ) {
260 $wgOut->loginToUse();
266 # Prepare the header box
268 $sk = $wgUser->getSkin();
270 $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml(
'nextdiff' ),
'diff=next&oldid='.$this->mNewid,
'',
'',
'id="differences-nextlink"' );
271 $header =
"<div class=\"firstrevisionheader\" style=\"text-align: center\"><strong>{$this->mOldtitle}</strong><br />" .
272 $sk->revUserTools( $this->mNewRev ) .
"<br />" .
273 $sk->revComment( $this->mNewRev ) .
"<br />" .
274 $nextlink .
"</div>\n";
278 $wgOut->setSubtitle(
wfMsg(
'difference' ) );
279 $wgOut->setRobotpolicy(
'noindex,nofollow' );
290 $diff = $this->
getDiff( $otitle, $ntitle );
291 if ( $diff ===
false ) {
292 $wgOut->addWikitext(
wfMsg(
'missingarticle',
"<nowiki>(fixme, bug)</nowiki>" ) );
295 $wgOut->addHTML( $diff );
307 if ( $body ===
false ) {
311 return $this->
addHeader( $body, $otitle, $ntitle, $multi );
322 $fname =
'DifferenceEngine::getDiffBody';
327 if ( $this->mOldid && $this->mNewid ) {
329 $key = wfMemcKey(
'diff',
'oldid', $this->mOldid,
'newid', $this->mNewid );
330 $difftext = $wgMemc->get( $key );
332 wfIncrStats(
'diff_cache_hit' );
334 $difftext .=
"\n<!-- diff cache key $key -->\n";
340 #loadtext is permission safe, this just clears out the diff
344 }
else if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
346 }
else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
353 if ( $key !==
false && $difftext !==
false ) {
354 wfIncrStats(
'diff_cache_miss' );
355 $wgMemc->set( $key, $difftext, 7*86400 );
357 wfIncrStats(
'diff_uncacheable' );
360 if ( $difftext !==
false ) {
372 global $wgExternalDiffEngine, $wgContLang;
373 $fname =
'DifferenceEngine::generateDiffBody';
375 $otext = str_replace(
"\r\n",
"\n", $otext );
376 $ntext = str_replace(
"\r\n",
"\n", $ntext );
378 if ( $wgExternalDiffEngine ==
'wikidiff' ) {
379 # For historical reasons, external diff engine expects
380 # input text to be HTML-escaped already
381 $otext = htmlspecialchars ( $wgContLang->segmentForDiff( $otext ) );
382 $ntext = htmlspecialchars ( $wgContLang->segmentForDiff( $ntext ) );
383 if( !function_exists(
'wikidiff_do_diff' ) ) {
384 dl(
'php_wikidiff.so');
386 return $wgContLang->unsegementForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) );
389 if ( $wgExternalDiffEngine ==
'wikidiff2' ) {
390 # Better external diff engine, the 2 may some day be dropped
391 # This one does the escaping and segmenting itself
392 if ( !function_exists(
'wikidiff2_do_diff' ) ) {
394 @dl(
'php_wikidiff2.so');
397 if ( function_exists(
'wikidiff2_do_diff' ) ) {
399 $text = wikidiff2_do_diff( $otext, $ntext, 2 );
404 if ( $wgExternalDiffEngine !==
false ) {
406 global $wgTmpDirectory;
407 $tempName1 = tempnam( $wgTmpDirectory,
'diff_' );
408 $tempName2 = tempnam( $wgTmpDirectory,
'diff_' );
410 $tempFile1 = fopen( $tempName1,
"w" );
415 $tempFile2 = fopen( $tempName2,
"w" );
420 fwrite( $tempFile1, $otext );
421 fwrite( $tempFile2, $ntext );
422 fclose( $tempFile1 );
423 fclose( $tempFile2 );
424 $cmd = wfEscapeShellArg( $wgExternalDiffEngine, $tempName1, $tempName2 );
426 $difftext = wfShellExec(
$cmd );
428 unlink( $tempName1 );
429 unlink( $tempName2 );
434 $ota = explode(
"\n", $wgContLang->segmentForDiff( $otext ) );
435 $nta = explode(
"\n", $wgContLang->segmentForDiff( $ntext ) );
436 $diffs =
new Diff( $ota, $nta );
438 return $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) );
446 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
447 array( &$this,
'localiseLineNumbersCb' ),
$text );
452 return wfMsgExt(
'lineno', array(
'parseinline'), $wgLang->formatNum( $matches[1] ) );
460 if ( !is_object($this->mOldRev) || !is_object($this->mNewRev) )
463 if( !$this->mOldPage->equals( $this->mNewPage ) ) {
468 $oldid = $this->mOldRev->getId();
469 $newid = $this->mNewRev->getId();
470 if ( $oldid > $newid ) {
471 $tmp = $oldid; $oldid = $newid; $newid = $tmp;
474 $n = $this->mTitle->countRevisionsBetween( $oldid, $newid );
478 return wfMsgExt(
'diff-multi', array(
'parseinline' ),
$n );
485 function addHeader( $diff, $otitle, $ntitle, $multi =
'' ) {
488 if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
489 $otitle =
'<span class="history-deleted">'.$otitle.
'</span>';
491 if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
492 $ntitle =
'<span class="history-deleted">'.$ntitle.
'</span>';
495 <table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>
497 <td colspan='2' width='50%' align='center' class='diff-otitle'>{$otitle}</td>
498 <td colspan='2' width='50%' align='center' class='diff-ntitle'>{$ntitle}</td>
503 $header .=
"<tr><td colspan='4' align='center' class='diff-multi'>{$multi}</td></tr>";
505 return $header . $diff .
"</table>";
512 $this->mOldtext = $oldText;
513 $this->mNewtext = $newText;
514 $this->mTextLoaded = 2;
529 if ( $this->mRevisionsLoaded ) {
533 $this->mRevisionsLoaded =
true;
537 if( $this->mNewid ) {
538 $this->mNewRev = Revision::newFromId( $this->mNewid );
540 $this->mNewRev = Revision::newFromTitle( $this->mTitle );
543 if( is_null( $this->mNewRev ) ) {
548 $timestamp = $wgLang->timeanddate( $this->mNewRev->getTimestamp(),
true );
549 $this->mNewPage = $this->mNewRev->getTitle();
550 if( $this->mNewRev->isCurrent() ) {
551 $newLink = $this->mNewPage->escapeLocalUrl();
552 $this->mPagetitle = htmlspecialchars(
wfMsg(
'currentrev' ) );
553 $newEdit = $this->mNewPage->escapeLocalUrl(
'action=edit' );
555 $this->mNewtitle =
"<a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)"
556 .
" (<a href='$newEdit'>" . htmlspecialchars(
wfMsg(
'editold' ) ) .
"</a>)";
559 $newLink = $this->mNewPage->escapeLocalUrl(
'oldid=' . $this->mNewid );
560 $newEdit = $this->mNewPage->escapeLocalUrl(
'action=edit&oldid=' . $this->mNewid );
561 $this->mPagetitle = htmlspecialchars(
wfMsg(
'revisionasof',
$timestamp ) );
563 $this->mNewtitle =
"<a href='$newLink'>{$this->mPagetitle}</a>"
564 .
" (<a href='$newEdit'>" . htmlspecialchars(
wfMsg(
'editold' ) ) .
"</a>)";
568 $this->mOldRev =
false;
569 if( $this->mOldid ) {
570 $this->mOldRev = Revision::newFromId( $this->mOldid );
571 } elseif ( $this->mOldid === 0 ) {
572 $rev = $this->mNewRev->getPrevious();
574 $this->mOldid = $rev->getId();
575 $this->mOldRev = $rev;
578 $this->mOldid =
false;
579 $this->mOldRev =
false;
583 if( is_null( $this->mOldRev ) ) {
587 if ( $this->mOldRev ) {
588 $this->mOldPage = $this->mOldRev->getTitle();
590 $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(),
true );
591 $oldLink = $this->mOldPage->escapeLocalUrl(
'oldid=' . $this->mOldid );
592 $oldEdit = $this->mOldPage->escapeLocalUrl(
'action=edit&oldid=' . $this->mOldid );
593 $this->mOldtitle =
"<a href='$oldLink'>" . htmlspecialchars(
wfMsg(
'revisionasof',
$t ) )
594 .
"</a> (<a href='$oldEdit'>" . htmlspecialchars(
wfMsg(
'editold' ) ) .
"</a>)";
596 $newUndo = $this->mNewPage->escapeLocalUrl(
'action=edit&undoafter=' . $this->mOldid .
'&undo=' . $this->mNewid);
597 $this->mNewtitle .=
" (<a href='$newUndo'>" . htmlspecialchars(
wfMsg(
'editundo' ) ) .
"</a>)";
607 if ( $this->mTextLoaded == 2 ) {
611 $this->mTextLoaded = 2;
617 if ( $this->mOldRev ) {
619 $this->mOldtext = $this->mOldRev->revText();
620 if ( $this->mOldtext ===
false ) {
624 if ( $this->mNewRev ) {
625 $this->mNewtext = $this->mNewRev->revText();
626 if ( $this->mNewtext ===
false ) {
637 if ( $this->mTextLoaded >= 1 ) {
640 $this->mTextLoaded = 1;
645 $this->mNewtext = $this->mNewRev->getText();
658define(
'USE_ASSERTS', function_exists(
'assert'));
671 trigger_error(
'pure virtual', E_USER_ERROR);
712 $this->orig = $lines;
713 $this->closing =
false;
730 $this->closing = $lines;
785 function diff ($from_lines, $to_lines) {
786 $fname =
'_DiffEngine::diff';
789 $n_from =
sizeof($from_lines);
790 $n_to =
sizeof($to_lines);
792 $this->xchanged = $this->ychanged = array();
793 $this->xv = $this->yv = array();
794 $this->xind = $this->yind = array();
796 unset($this->in_seq);
800 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
801 if ($from_lines[$skip] !== $to_lines[$skip])
803 $this->xchanged[$skip] = $this->ychanged[$skip] =
false;
806 $xi = $n_from; $yi = $n_to;
807 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
808 if ($from_lines[$xi] !== $to_lines[$yi])
810 $this->xchanged[$xi] = $this->ychanged[$yi] =
false;
814 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
815 $xhash[$this->
_line_hash($from_lines[$xi])] = 1;
818 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
819 $line = $to_lines[$yi];
820 if ( ($this->ychanged[$yi] = empty($xhash[$this->
_line_hash($line)])) )
826 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
827 $line = $from_lines[$xi];
828 if ( ($this->xchanged[$xi] = empty($yhash[$this->
_line_hash($line)])) )
835 $this->
_compareseq(0,
sizeof($this->xv), 0,
sizeof($this->yv));
844 while ($xi < $n_from || $yi < $n_to) {
845 USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
846 USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
850 while ( $xi < $n_from && $yi < $n_to
851 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
852 $copy[] = $from_lines[$xi++];
860 while ($xi < $n_from && $this->xchanged[$xi])
861 $delete[] = $from_lines[$xi++];
864 while ($yi < $n_to && $this->ychanged[$yi])
865 $add[] = $to_lines[$yi++];
882 if ( strlen( $line ) > self::MAX_XREF_LENGTH ) {
906 function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) {
907 $fname =
'_DiffEngine::_diag';
911 if ($xlim - $xoff > $ylim - $yoff) {
915 list ($xoff, $xlim, $yoff, $ylim)
916 = array( $yoff, $ylim, $xoff, $xlim);
920 for ($i = $ylim - 1; $i >= $yoff; $i--)
921 $ymatches[$this->xv[$i]][] = $i;
923 for ($i = $ylim - 1; $i >= $yoff; $i--)
924 $ymatches[$this->yv[$i]][] = $i;
927 $this->seq[0]= $yoff - 1;
928 $this->in_seq = array();
931 $numer = $xlim - $xoff + $nchunks - 1;
933 for ($chunk = 0; $chunk < $nchunks; $chunk++) {
936 for ($i = 0; $i <= $this->lcs; $i++)
937 $ymids[$i][$chunk-1] = $this->seq[$i];
939 $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
940 for ( ;
$x < $x1;
$x++) {
941 $line = $flip ? $this->yv[
$x] : $this->xv[
$x];
942 if (empty($ymatches[$line]))
944 $matches = $ymatches[$line];
946 while (list ($junk,
$y) = each($matches))
947 if (empty($this->in_seq[
$y])) {
950 $ymids[$k] = $ymids[$k-1];
953 while (list ( ,
$y) = each($matches)) {
954 if (
$y > $this->seq[$k-1]) {
958 $this->in_seq[$this->seq[$k]] =
false;
960 $this->in_seq[
$y] = 1;
961 }
else if (empty($this->in_seq[
$y])) {
964 $ymids[$k] = $ymids[$k-1];
971 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
972 $ymid = $ymids[$this->lcs];
973 for (
$n = 0;
$n < $nchunks - 1;
$n++) {
974 $x1 = $xoff + (int)(($numer + ($xlim - $xoff) *
$n) / $nchunks);
976 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
978 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
981 return array($this->lcs, $seps);
985 $fname =
'_DiffEngine::_lcs_pos';
989 if ($end == 0 || $ypos > $this->seq[$end]) {
990 $this->seq[++$this->lcs] = $ypos;
991 $this->in_seq[$ypos] = 1;
997 while ($beg < $end) {
998 $mid = (int)(($beg + $end) / 2);
999 if ( $ypos > $this->seq[$mid] )
1007 $this->in_seq[$this->seq[$end]] =
false;
1008 $this->seq[$end] = $ypos;
1009 $this->in_seq[$ypos] = 1;
1026 $fname =
'_DiffEngine::_compareseq';
1030 while ($xoff < $xlim && $yoff < $ylim
1031 && $this->xv[$xoff] == $this->yv[$yoff]) {
1037 while ($xlim > $xoff && $ylim > $yoff
1038 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
1043 if ($xoff == $xlim || $yoff == $ylim)
1049 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
1051 = $this->
_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
1057 while ($yoff < $ylim)
1058 $this->ychanged[$this->yind[$yoff++]] = 1;
1059 while ($xoff < $xlim)
1060 $this->xchanged[$this->xind[$xoff++]] = 1;
1065 while ($pt2 = next($seps)) {
1066 $this->
_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
1086 $fname =
'_DiffEngine::_shift_boundaries';
1091 USE_ASSERTS && assert(
'sizeof($lines) == sizeof($changed)');
1092 $len =
sizeof($lines);
1093 $other_len =
sizeof($other_changed);
1107 while ($j < $other_len && $other_changed[$j])
1110 while ($i < $len && !
$changed[$i]) {
1111 USE_ASSERTS && assert(
'$j < $other_len && ! $other_changed[$j]');
1113 while ($j < $other_len && $other_changed[$j])
1123 while (++$i < $len &&
$changed[$i])
1131 $runlength = $i - $start;
1138 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
1141 while ($start > 0 &&
$changed[$start - 1])
1144 while ($other_changed[--$j])
1146 USE_ASSERTS && assert(
'$j >= 0 && !$other_changed[$j]');
1154 $corresponding = $j < $other_len ? $i : $len;
1163 while ($i < $len && $lines[$start] == $lines[$i]) {
1169 USE_ASSERTS && assert(
'$j < $other_len && ! $other_changed[$j]');
1171 if ($j < $other_len && $other_changed[$j]) {
1172 $corresponding = $i;
1173 while ($j < $other_len && $other_changed[$j])
1177 }
while ($runlength != $i - $start);
1183 while ($corresponding < $i) {
1187 while ($other_changed[--$j])
1189 USE_ASSERTS && assert(
'$j >= 0 && !$other_changed[$j]');
1214 function Diff($from_lines, $to_lines) {
1216 $this->edits = $eng->
diff($from_lines, $to_lines);
1232 $rev->edits = array();
1233 foreach ($this->edits as $edit) {
1234 $rev->edits[] = $edit->reverse();
1245 foreach ($this->edits as $edit) {
1246 if ($edit->type !=
'copy')
1261 foreach ($this->edits as $edit) {
1262 if ($edit->type ==
'copy')
1263 $lcs +=
sizeof($edit->orig);
1279 foreach ($this->edits as $edit) {
1281 array_splice($lines,
sizeof($lines), 0, $edit->orig);
1297 foreach ($this->edits as $edit) {
1299 array_splice($lines,
sizeof($lines), 0, $edit->closing);
1310 $fname =
'Diff::_check';
1312 if (serialize($from_lines) != serialize($this->
orig()))
1313 trigger_error(
"Reconstructed original doesn't match", E_USER_ERROR);
1314 if (serialize($to_lines) != serialize($this->
closing()))
1315 trigger_error(
"Reconstructed closing doesn't match", E_USER_ERROR);
1318 if (serialize($to_lines) != serialize($rev->orig()))
1319 trigger_error(
"Reversed original doesn't match", E_USER_ERROR);
1320 if (serialize($from_lines) != serialize($rev->closing()))
1321 trigger_error(
"Reversed closing doesn't match", E_USER_ERROR);
1325 foreach ($this->edits as $edit) {
1326 if ( $prevtype == $edit->type )
1327 trigger_error(
"Edit sequence is non-optimal", E_USER_ERROR);
1328 $prevtype = $edit->type;
1331 $lcs = $this->
lcs();
1332 trigger_error(
'Diff okay: LCS = '.$lcs, E_USER_NOTICE);
1368 $mapped_from_lines, $mapped_to_lines) {
1369 $fname =
'MappedDiff::MappedDiff';
1372 assert(
sizeof($from_lines) ==
sizeof($mapped_from_lines));
1373 assert(
sizeof($to_lines) ==
sizeof($mapped_to_lines));
1375 $this->
Diff($mapped_from_lines, $mapped_to_lines);
1379 $orig = &$this->edits[$i]->orig;
1380 if (is_array($orig)) {
1381 $orig = array_slice($from_lines, $xi,
sizeof($orig));
1382 $xi +=
sizeof($orig);
1385 $closing = &$this->edits[$i]->closing;
1386 if (is_array($closing)) {
1387 $closing = array_slice($to_lines, $yi,
sizeof($closing));
1388 $yi +=
sizeof($closing);
1430 $fname =
'DiffFormatter::format';
1442 foreach ($diff->edits as $edit) {
1443 if ($edit->type ==
'copy') {
1444 if (is_array($block)) {
1445 if (
sizeof($edit->orig) <= $nlead + $ntrail) {
1450 $context = array_slice($edit->orig, 0, $ntrail);
1453 $this->
_block($x0, $ntrail + $xi - $x0,
1454 $y0, $ntrail + $yi - $y0,
1459 $context = $edit->orig;
1462 if (! is_array($block)) {
1463 $context = array_slice($context,
sizeof($context) - $nlead);
1464 $x0 = $xi -
sizeof($context);
1465 $y0 = $yi -
sizeof($context);
1474 $xi +=
sizeof($edit->orig);
1476 $yi +=
sizeof($edit->closing);
1479 if (is_array($block))
1480 $this->
_block($x0, $xi - $x0,
1489 function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
1490 $fname =
'DiffFormatter::_block';
1493 foreach ($edits as $edit) {
1494 if ($edit->type ==
'copy')
1496 elseif ($edit->type ==
'add')
1497 $this->
_added($edit->closing);
1498 elseif ($edit->type ==
'delete')
1500 elseif ($edit->type ==
'change')
1501 $this->
_changed($edit->orig, $edit->closing);
1503 trigger_error(
'Unknown edit type', E_USER_ERROR);
1514 $val = ob_get_contents();
1521 $xbeg .=
"," . ($xbeg + $xlen - 1);
1523 $ybeg .=
"," . ($ybeg + $ylen - 1);
1525 return $xbeg . ($xlen ? ($ylen ?
'c' :
'd') :
'a') . $ybeg;
1536 foreach ($lines as $line)
1537 echo
"$prefix $line\n";
1545 $this->
_lines($lines,
'>');
1548 $this->
_lines($lines,
'<');
1564define(
'NBSP',
' ');
1573 $this->_lines = array();
1580 if ($this->_group !==
'') {
1581 if ($this->_tag ==
'ins')
1582 $this->_line .=
'[ilDiffInsStart]' .
1583 htmlspecialchars ( $this->_group ) .
'[ilDiffInsEnd]';
1584 elseif ($this->_tag ==
'del')
1585 $this->_line .=
'[ilDiffDelStart]' .
1586 htmlspecialchars ( $this->_group ) .
'[ilDiffDelEnd]';
1588 $this->_line .= htmlspecialchars ( $this->_group );
1591 $this->_tag = $new_tag;
1596 if ($this->_line !=
'')
1597 array_push ( $this->_lines, $this->_line );
1599 # make empty lines visible by inserting an NBSP
1600 array_push ( $this->_lines,
NBSP );
1605 if ($tag != $this->_tag)
1608 foreach ($words as $word) {
1612 if ($word[0] ==
"\n") {
1614 $word = substr($word, 1);
1616 assert(!strstr($word,
"\n"));
1617 $this->_group .= $word;
1623 return $this->_lines;
1637 $fname =
'WordLevelDiff::WordLevelDiff';
1640 list ($orig_words, $orig_stripped) = $this->
_split($orig_lines);
1641 list ($closing_words, $closing_stripped) = $this->
_split($closing_lines);
1643 $this->
MappedDiff($orig_words, $closing_words,
1644 $orig_stripped, $closing_stripped);
1649 $fname =
'WordLevelDiff::_split';
1653 $stripped = array();
1655 foreach ( $lines as $line ) {
1656 # If the line is too long, just pretend the entire line is one big word
1657 # This prevents resource exhaustion problems
1664 if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
1666 $stripped[] = $line;
1669 if (preg_match_all(
'/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
1672 $words = array_merge( $words, $m[0] );
1673 $stripped = array_merge( $stripped, $m[1] );
1678 return array($words, $stripped);
1682 $fname =
'WordLevelDiff::orig';
1686 foreach ($this->edits as $edit) {
1687 if ($edit->type ==
'copy')
1689 elseif ($edit->orig)
1690 $orig->addWords($edit->orig,
'del');
1692 $lines = $orig->getLines();
1698 $fname =
'WordLevelDiff::closing';
1702 foreach ($this->edits as $edit) {
1703 if ($edit->type ==
'copy')
1704 $closing->
addWords($edit->closing);
1705 elseif ($edit->closing)
1706 $closing->addWords($edit->closing,
'ins');
1708 $lines = $closing->getLines();
1723 $this->leading_context_lines = 2;
1724 $this->trailing_context_lines = 2;
1728 $r =
'<tr><td colspan="2" align="left"><strong><!--LINE '.$xbeg.
"--></strong></td>\n" .
1729 '<td colspan="2" align="left"><strong><!--LINE '.$ybeg.
"--></strong></td></tr>\n";
1740 function _lines( $lines, $prefix=
' ', $color=
'white' ) {
1743 # HTML-escape parameter before calling this
1745 return "<td>+</td><td class='diff-addedline'>{$line}</td>";
1748 # HTML-escape parameter before calling this
1750 return "<td>-</td><td class='diff-deletedline'>{$line}</td>";
1753 # HTML-escape parameter before calling this
1755 return "<td> </td><td class='diff-context'>{$line}</td>";
1759 return '<td colspan="2"> </td>';
1763 foreach ($lines as $line) {
1765 $this->
addedLine( htmlspecialchars ( $line ) ) .
"</tr>\n";
1770 foreach ($lines as $line) {
1771 echo
'<tr>' . $this->
deletedLine( htmlspecialchars ( $line ) ) .
1777 foreach ($lines as $line) {
1779 $this->
contextLine( htmlspecialchars ( $line ) ) .
1780 $this->
contextLine( htmlspecialchars ( $line ) ) .
"</tr>\n";
1785 $fname =
'TableDiffFormatter::_changed';
1789 $del = $diff->orig();
1790 $add = $diff->closing();
1792 # Notice that WordLevelDiff returns HTML-escaped output.
1793 # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
1795 while ( $line = array_shift( $del ) ) {
1796 $aline = array_shift( $add );
1800 foreach ($add as $line) { # If any leftovers
foreach($mandatory_scripts as $file) $timestamp
reverse()
Compute reversed Diff.
orig()
Get the original set of lines.
isEmpty()
Check for empty diff.
lcs()
Compute the length of the Longest Common Subsequence (LCS).
Diff($from_lines, $to_lines)
Constructor.
closing()
Get the closing set of lines.
_check($from_lines, $to_lines)
Check a Diff for validity.
showFirstRevision()
Show the first revision of an article.
loadNewText()
Load the text of the new revision, not the old one.
showDiffPage( $diffOnly=false)
loadText()
Load the text of the revisions, as well as revision data.
getDiffBody()
Get the diff table body, without header Results are cached Returns false on error.
loadRevisionData()
Load revision metadata for the specified articles.
generateDiffBody( $otext, $ntext)
Generate a diff, no caching $otext and $ntext must be already segmented.
localiseLineNumbers( $text)
Replace line numbers with the text in the user's language.
localiseLineNumbersCb( $matches)
setText( $oldText, $newText)
Use specified text instead of loading from the database.
getMultiNotice()
If there are revisions between the ones being compared, return a note saying so.
DifferenceEngine( $titleObj=null, $old=0, $new=0, $rcid=0)
#-
showDiff( $otitle, $ntitle)
Get the diff text, send it to $wgOut Returns false if the diff could not be generated,...
renderNewRevision()
Show the new revision of the page.
getDiff( $otitle, $ntitle)
Get diff table, including header Note that the interface has changed, it's no longer static.
addHeader( $diff, $otitle, $ntitle, $multi='')
Add the header to a diff body.
MappedDiff($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines)
Constructor.
WordLevelDiff($orig_lines, $closing_lines)
closing()
Get the closing set of lines.
orig()
Get the original set of lines.
const NBSP
Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3.
_diag($xoff, $xlim, $yoff, $ylim, $nchunks)
_line_hash( $line)
Returns the whole line if it's small enough, or the MD5 hash otherwise.
diff($from_lines, $to_lines)
_shift_boundaries($lines, &$changed, $other_changed)
_compareseq($xoff, $xlim, $yoff, $ylim)
_DiffOp_Change($orig, $closing)
_DiffOp_Copy($orig, $closing=false)
addWords($words, $tag='')