46 public function __construct($titleObj =
null, $old = 0, $new = 0, $rcid = 0)
48 $this->mTitle = $titleObj;
49 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'\n");
51 if (
'prev' === $new) {
52 # Show diff between revision $old and the previous one.
53 # Get previous one from DB.
55 $this->mNewid = intval($old);
57 $this->mOldid = $this->mTitle->getPreviousRevisionID($this->mNewid);
58 } elseif (
'next' === $new) {
59 # Show diff between revision $old and the previous one.
60 # Get previous one from DB.
62 $this->mOldid = intval($old);
63 $this->mNewid = $this->mTitle->getNextRevisionID($this->mOldid);
64 if (
false === $this->mNewid) {
65 # if no result, NewId points to the newest old revision. The only newer
66 # revision is cur, which is "0".
70 $this->mOldid = intval($old);
71 $this->mNewid = intval($new);
73 $this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer
78 global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
79 $fname =
'DifferenceEngine::showDiffPage';
82 # If external diffs are enabled both globally and for the user,
83 # we'll use the application/x-external-editor interface to call
84 # an external diff tool like kompare, kdiff3, etc.
85 if ($wgUseExternalEditor && $wgUser->getOption(
'externaldiff')) {
86 global $wgInputEncoding,$wgServer,$wgScript,$wgLang;
88 header(
"Content-type: application/x-external-editor; charset=" . $wgInputEncoding);
89 $url1 = $this->mTitle->getFullURL(
"action=raw&oldid=" . $this->mOldid);
90 $url2 = $this->mTitle->getFullURL(
"action=raw&oldid=" . $this->mNewid);
96Script={$wgServer}{$wgScript}
97Special
namespace={$special}
111 $wgOut->setArticleFlag(
false);
113 $t = $this->mTitle->getPrefixedText() .
" (Diff: {$this->mOldid}, {$this->mNewid})";
114 $mtext = wfMsg(
'missingarticle',
"<nowiki>$t</nowiki>");
115 $wgOut->setPagetitle(wfMsg(
'errorpagetitle'));
116 $wgOut->addWikitext($mtext);
121 wfRunHooks(
'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ));
123 if ($this->mNewRev->isCurrent()) {
124 $wgOut->setArticleFlag(
true);
127 # mOldid is false if the difference engine is called with a "vague" query for
128 # a diff between a version V and its previous version V' AND the version V
129 # is the first version of that article. In that case, V' does not exist.
130 if ($this->mOldid ===
false) {
137 $wgOut->suppressQuickbar();
139 $oldTitle = $this->mOldPage->getPrefixedText();
140 $newTitle = $this->mNewPage->getPrefixedText();
141 if ($oldTitle == $newTitle) {
142 $wgOut->setPageTitle($newTitle);
144 $wgOut->setPageTitle($oldTitle .
', ' . $newTitle);
146 $wgOut->setSubtitle(wfMsg(
'difference'));
147 $wgOut->setRobotpolicy(
'noindex,nofollow');
149 if (!($this->mOldPage->userCanRead() && $this->mNewPage->userCanRead())) {
150 $wgOut->loginToUse();
156 $sk = $wgUser->getSkin();
158 if ($this->mNewRev->isCurrent() && $wgUser->isAllowed(
'rollback')) {
159 $rollback =
' ' . $sk->generateRollback($this->mNewRev);
163 if ($wgUseRCPatrol && $this->mRcidMarkPatrolled != 0 && $wgUser->isAllowed(
'patrol')) {
164 $patrol =
' [' . $sk->makeKnownLinkObj($this->mTitle, wfMsg(
'markaspatrolleddiff'),
"action=markpatrolled&rcid={$this->mRcidMarkPatrolled}") .
']';
169 $prevlink = $sk->makeKnownLinkObj(
171 wfMsgHtml(
'previousdiff'),
172 'diff=prev&oldid=' . $this->mOldid,
175 'id="differences-prevlink"'
177 if ($this->mNewRev->isCurrent()) {
178 $nextlink =
' ';
180 $nextlink = $sk->makeKnownLinkObj(
182 wfMsgHtml(
'nextdiff'),
183 'diff=next&oldid=' . $this->mNewid,
186 'id="differences-nextlink"'
193 if ($this->mOldRev->mMinorEdit == 1) {
194 $oldminor = wfElement(
196 array(
'class' =>
'minor' ),
197 wfMsg(
'minoreditletter')
201 if ($this->mNewRev->mMinorEdit == 1) {
202 $newminor = wfElement(
204 array(
'class' =>
'minor' ),
205 wfMsg(
'minoreditletter')
209 $oldHeader =
"<strong>{$this->mOldtitle}</strong><br />" .
210 $sk->revUserTools($this->mOldRev) .
"<br />" .
211 $oldminor . $sk->revComment($this->mOldRev, !$diffOnly) .
"<br />" .
213 $newHeader =
"<strong>{$this->mNewtitle}</strong><br />" .
214 $sk->revUserTools($this->mNewRev) .
" $rollback<br />" .
215 $newminor . $sk->revComment($this->mNewRev, !$diffOnly) .
"<br />" .
218 $this->
showDiff($oldHeader, $newHeader);
233 $fname =
'DifferenceEngine::renderNewRevision';
236 $wgOut->addHTML(
"<hr /><h2>{$this->mPagetitle}</h2>\n");
237 #add deleted rev tag if needed
238 if (!$this->mNewRev->userCan(Revision::DELETED_TEXT)) {
239 $wgOut->addWikiText(wfMsg(
'rev-deleted-text-permission'));
242 if (!$this->mNewRev->isCurrent()) {
243 $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection(
false);
247 if (is_object($this->mNewRev)) {
248 $wgOut->setRevisionId($this->mNewRev->getId());
251 $wgOut->addWikiTextTidy($this->mNewtext);
253 if (!$this->mNewRev->isCurrent()) {
254 $wgOut->parserOptions()->setEditSection($oldEditSectionSetting);
266 global $wgOut, $wgUser;
268 $fname =
'DifferenceEngine::showFirstRevision';
271 # Get article text from the DB
274 $t = $this->mTitle->getPrefixedText() .
" (Diff: {$this->mOldid}, " .
276 $mtext = wfMsg(
'missingarticle',
"<nowiki>$t</nowiki>");
277 $wgOut->setPagetitle(wfMsg(
'errorpagetitle'));
278 $wgOut->addWikitext($mtext);
282 if ($this->mNewRev->isCurrent()) {
283 $wgOut->setArticleFlag(
true);
286 # Check if user is allowed to look at this page. If not, bail out.
288 if (!($this->mTitle->userCanRead())) {
289 $wgOut->loginToUse();
295 # Prepare the header box
297 $sk = $wgUser->getSkin();
299 $nextlink = $sk->makeKnownLinkObj($this->mTitle, wfMsgHtml(
'nextdiff'),
'diff=next&oldid=' . $this->mNewid,
'',
'',
'id="differences-nextlink"');
300 $header =
"<div class=\"firstrevisionheader\" style=\"text-align: center\"><strong>{$this->mOldtitle}</strong><br />" .
301 $sk->revUserTools($this->mNewRev) .
"<br />" .
302 $sk->revComment($this->mNewRev) .
"<br />" .
303 $nextlink .
"</div>\n";
305 $wgOut->addHTML($header);
307 $wgOut->setSubtitle(wfMsg(
'difference'));
308 $wgOut->setRobotpolicy(
'noindex,nofollow');
320 $diff = $this->
getDiff($otitle, $ntitle);
321 if ($diff ===
false) {
322 $wgOut->addWikitext(wfMsg(
'missingarticle',
"<nowiki>(fixme, bug)</nowiki>"));
325 $wgOut->addHTML($diff);
338 if ($body ===
false) {
342 return $this->
addHeader($body, $otitle, $ntitle, $multi);
354 $fname =
'DifferenceEngine::getDiffBody';
359 if ($this->mOldid && $this->mNewid) {
361 $key = wfMemcKey(
'diff',
'oldid', $this->mOldid,
'newid', $this->mNewid);
362 $difftext = $wgMemc->get(
$key);
364 wfIncrStats(
'diff_cache_hit');
366 $difftext .=
"\n<!-- diff cache key $key -->\n";
372 #loadtext is permission safe, this just clears out the diff
376 } elseif ($this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT)) {
378 } elseif ($this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT)) {
385 if (
$key !==
false && $difftext !==
false) {
386 wfIncrStats(
'diff_cache_miss');
387 $wgMemc->set(
$key, $difftext, 7 * 86400);
389 wfIncrStats(
'diff_uncacheable');
392 if ($difftext !==
false) {
405 global $wgExternalDiffEngine, $wgContLang;
406 $fname =
'DifferenceEngine::generateDiffBody';
408 $otext = str_replace(
"\r\n",
"\n", $otext);
409 $ntext = str_replace(
"\r\n",
"\n", $ntext);
411 if ($wgExternalDiffEngine ==
'wikidiff') {
412 # For historical reasons, external diff engine expects
413 # input text to be HTML-escaped already
414 $otext = htmlspecialchars($wgContLang->segmentForDiff($otext));
415 $ntext = htmlspecialchars($wgContLang->segmentForDiff($ntext));
416 if (!function_exists(
'wikidiff_do_diff')) {
417 dl(
'php_wikidiff.so');
419 return $wgContLang->unsegementForDiff(wikidiff_do_diff($otext, $ntext, 2));
422 if ($wgExternalDiffEngine ==
'wikidiff2') {
423 # Better external diff engine, the 2 may some day be dropped
424 # This one does the escaping and segmenting itself
425 if (!function_exists(
'wikidiff2_do_diff')) {
427 @dl(
'php_wikidiff2.so');
430 if (function_exists(
'wikidiff2_do_diff')) {
432 $text = wikidiff2_do_diff($otext, $ntext, 2);
437 if ($wgExternalDiffEngine !==
false) {
439 global $wgTmpDirectory;
440 $tempName1 = tempnam($wgTmpDirectory,
'diff_');
441 $tempName2 = tempnam($wgTmpDirectory,
'diff_');
443 $tempFile1 = fopen($tempName1,
"w");
448 $tempFile2 = fopen($tempName2,
"w");
453 fwrite($tempFile1, $otext);
454 fwrite($tempFile2, $ntext);
457 $cmd = wfEscapeShellArg($wgExternalDiffEngine, $tempName1, $tempName2);
459 $difftext = wfShellExec($cmd);
467 $ota = explode(
"\n", $wgContLang->segmentForDiff($otext));
468 $nta = explode(
"\n", $wgContLang->segmentForDiff($ntext));
469 $diffs =
new Diff($ota, $nta);
471 return $wgContLang->unsegmentForDiff($formatter->format($diffs));
480 return preg_replace_callback(
481 '/<!--LINE (\d+)-->/',
482 array( &$this,
'localiseLineNumbersCb' ),
490 return wfMsgExt(
'lineno', array(
'parseinline'), $wgLang->formatNum($matches[1]));
499 if (!is_object($this->mOldRev) || !is_object($this->mNewRev)) {
503 if (!$this->mOldPage->equals($this->mNewPage)) {
508 $oldid = $this->mOldRev->getId();
509 $newid = $this->mNewRev->getId();
510 if ($oldid > $newid) {
516 $n = $this->mTitle->countRevisionsBetween($oldid, $newid);
521 return wfMsgExt(
'diff-multi', array(
'parseinline' ), $n);
528 public function addHeader($diff, $otitle, $ntitle, $multi =
'')
532 if ($this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT)) {
533 $otitle =
'<span class="history-deleted">' . $otitle .
'</span>';
535 if ($this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT)) {
536 $ntitle =
'<span class="history-deleted">' . $ntitle .
'</span>';
539 <table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>
541 <td colspan='2' width='50%' align='center' class='diff-otitle'>{$otitle}</td>
542 <td colspan='2' width='50%' align='center' class='diff-ntitle'>{$ntitle}</td>
547 $header .=
"<tr><td colspan='4' align='center' class='diff-multi'>{$multi}</td></tr>";
550 return $header . $diff .
"</table>";
558 $this->mOldtext = $oldText;
559 $this->mNewtext = $newText;
560 $this->mTextLoaded = 2;
576 if ($this->mRevisionsLoaded) {
580 $this->mRevisionsLoaded =
true;
585 $this->mNewRev = Revision::newFromId($this->mNewid);
587 $this->mNewRev = Revision::newFromTitle($this->mTitle);
590 if (is_null($this->mNewRev)) {
595 $timestamp = $wgLang->timeanddate($this->mNewRev->getTimestamp(),
true);
596 $this->mNewPage = $this->mNewRev->getTitle();
597 if ($this->mNewRev->isCurrent()) {
598 $newLink = $this->mNewPage->escapeLocalUrl();
599 $this->mPagetitle = htmlspecialchars(wfMsg(
'currentrev'));
600 $newEdit = $this->mNewPage->escapeLocalUrl(
'action=edit');
602 $this->mNewtitle =
"<a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)"
603 .
" (<a href='$newEdit'>" . htmlspecialchars(wfMsg(
'editold')) .
"</a>)";
605 $newLink = $this->mNewPage->escapeLocalUrl(
'oldid=' . $this->mNewid);
606 $newEdit = $this->mNewPage->escapeLocalUrl(
'action=edit&oldid=' . $this->mNewid);
607 $this->mPagetitle = htmlspecialchars(wfMsg(
'revisionasof',
$timestamp));
609 $this->mNewtitle =
"<a href='$newLink'>{$this->mPagetitle}</a>"
610 .
" (<a href='$newEdit'>" . htmlspecialchars(wfMsg(
'editold')) .
"</a>)";
614 $this->mOldRev =
false;
616 $this->mOldRev = Revision::newFromId($this->mOldid);
617 } elseif ($this->mOldid === 0) {
618 $rev = $this->mNewRev->getPrevious();
620 $this->mOldid = $rev->getId();
621 $this->mOldRev = $rev;
624 $this->mOldid =
false;
625 $this->mOldRev =
false;
629 if (is_null($this->mOldRev)) {
633 if ($this->mOldRev) {
634 $this->mOldPage = $this->mOldRev->getTitle();
636 $t = $wgLang->timeanddate($this->mOldRev->getTimestamp(),
true);
637 $oldLink = $this->mOldPage->escapeLocalUrl(
'oldid=' . $this->mOldid);
638 $oldEdit = $this->mOldPage->escapeLocalUrl(
'action=edit&oldid=' . $this->mOldid);
639 $this->mOldtitle =
"<a href='$oldLink'>" . htmlspecialchars(wfMsg(
'revisionasof', $t))
640 .
"</a> (<a href='$oldEdit'>" . htmlspecialchars(wfMsg(
'editold')) .
"</a>)";
642 $newUndo = $this->mNewPage->escapeLocalUrl(
'action=edit&undoafter=' . $this->mOldid .
'&undo=' . $this->mNewid);
643 $this->mNewtitle .=
" (<a href='$newUndo'>" . htmlspecialchars(wfMsg(
'editundo')) .
"</a>)";
654 if ($this->mTextLoaded == 2) {
658 $this->mTextLoaded = 2;
664 if ($this->mOldRev) {
666 $this->mOldtext = $this->mOldRev->revText();
667 if ($this->mOldtext ===
false) {
671 if ($this->mNewRev) {
672 $this->mNewtext = $this->mNewRev->revText();
673 if ($this->mNewtext ===
false) {
685 if ($this->mTextLoaded >= 1) {
688 $this->mTextLoaded = 1;
693 $this->mNewtext = $this->mNewRev->getText();
704define(
'USE_ASSERTS', function_exists(
'assert'));
719 trigger_error(
'pure virtual', E_USER_ERROR);
768 $this->orig = $lines;
769 $this->closing =
false;
789 $this->closing = $lines;
848 public function diff($from_lines, $to_lines)
850 $fname =
'_DiffEngine::diff';
853 $n_from =
sizeof($from_lines);
854 $n_to =
sizeof($to_lines);
856 $this->xchanged = $this->ychanged = array();
857 $this->xv = $this->yv = array();
858 $this->xind = $this->yind = array();
860 unset($this->in_seq);
864 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
865 if ($from_lines[$skip] !== $to_lines[$skip]) {
868 $this->xchanged[$skip] = $this->ychanged[$skip] =
false;
873 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
874 if ($from_lines[$xi] !== $to_lines[$yi]) {
877 $this->xchanged[$xi] = $this->ychanged[$yi] =
false;
881 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
882 $xhash[$this->
_line_hash($from_lines[$xi])] = 1;
885 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
886 $line = $to_lines[$yi];
887 if (($this->ychanged[$yi] = empty($xhash[$this->
_line_hash($line)]))) {
894 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
895 $line = $from_lines[$xi];
896 if (($this->xchanged[$xi] = empty($yhash[$this->
_line_hash($line)]))) {
904 $this->
_compareseq(0,
sizeof($this->xv), 0,
sizeof($this->yv));
913 while ($xi < $n_from || $yi < $n_to) {
914 USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
915 USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
919 while ($xi < $n_from && $yi < $n_to
920 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
921 $copy[] = $from_lines[$xi++];
930 while ($xi < $n_from && $this->xchanged[$xi]) {
931 $delete[] = $from_lines[$xi++];
935 while ($yi < $n_to && $this->ychanged[$yi]) {
936 $add[] = $to_lines[$yi++];
939 if ($delete && $add) {
956 if (strlen($line) > self::MAX_XREF_LENGTH) {
980 public function _diag($xoff, $xlim, $yoff, $ylim, $nchunks)
982 $fname =
'_DiffEngine::_diag';
986 if ($xlim - $xoff > $ylim - $yoff) {
990 list($xoff, $xlim, $yoff, $ylim)
991 = array( $yoff, $ylim, $xoff, $xlim);
995 for (
$i = $ylim - 1;
$i >= $yoff;
$i--) {
996 $ymatches[$this->xv[
$i]][] =
$i;
999 for (
$i = $ylim - 1;
$i >= $yoff;
$i--) {
1000 $ymatches[$this->yv[
$i]][] =
$i;
1005 $this->seq[0] = $yoff - 1;
1006 $this->in_seq = array();
1007 $ymids[0] = array();
1009 $numer = $xlim - $xoff + $nchunks - 1;
1011 for ($chunk = 0; $chunk < $nchunks; $chunk++) {
1014 for (
$i = 0;
$i <= $this->lcs;
$i++) {
1015 $ymids[
$i][$chunk - 1] = $this->seq[
$i];
1019 $x1 = $xoff + (
int) (($numer + ($xlim - $xoff) * $chunk) / $nchunks);
1020 for (; $x < $x1; $x++) {
1021 $line = $flip ? $this->yv[$x] : $this->xv[$x];
1022 if (empty($ymatches[$line])) {
1025 $matches = $ymatches[$line];
1027 foreach ($matches as $junk => $y) {
1028 if (empty($this->in_seq[$y])) {
1031 $ymids[$k] = $ymids[$k - 1];
1035 foreach ($matches as $y) {
1036 if ($y > $this->seq[$k - 1]) {
1040 $this->in_seq[$this->seq[$k]] =
false;
1041 $this->seq[$k] = $y;
1042 $this->in_seq[$y] = 1;
1043 } elseif (empty($this->in_seq[$y])) {
1046 $ymids[$k] = $ymids[$k - 1];
1053 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
1054 $ymid = $ymids[$this->lcs];
1055 for ($n = 0; $n < $nchunks - 1; $n++) {
1056 $x1 = $xoff + (
int) (($numer + ($xlim - $xoff) * $n) / $nchunks);
1057 $y1 = $ymid[$n] + 1;
1058 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
1060 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
1063 return array($this->lcs, $seps);
1068 $fname =
'_DiffEngine::_lcs_pos';
1072 if ($end == 0 || $ypos > $this->seq[$end]) {
1073 $this->seq[++$this->lcs] = $ypos;
1074 $this->in_seq[$ypos] = 1;
1080 while ($beg < $end) {
1081 $mid = (
int) (($beg + $end) / 2);
1082 if ($ypos > $this->seq[$mid]) {
1091 $this->in_seq[$this->seq[$end]] =
false;
1092 $this->seq[$end] = $ypos;
1093 $this->in_seq[$ypos] = 1;
1111 $fname =
'_DiffEngine::_compareseq';
1115 while ($xoff < $xlim && $yoff < $ylim
1116 && $this->xv[$xoff] == $this->yv[$yoff]) {
1122 while ($xlim > $xoff && $ylim > $yoff
1123 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
1128 if ($xoff == $xlim || $yoff == $ylim) {
1134 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
1136 = $this->
_diag($xoff, $xlim, $yoff, $ylim, $nchunks);
1142 while ($yoff < $ylim) {
1143 $this->ychanged[$this->yind[$yoff++]] = 1;
1145 while ($xoff < $xlim) {
1146 $this->xchanged[$this->xind[$xoff++]] = 1;
1152 while ($pt2 = next($seps)) {
1153 $this->
_compareseq($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
1174 $fname =
'_DiffEngine::_shift_boundaries';
1179 USE_ASSERTS && assert(
sizeof($lines) ==
sizeof($changed));
1180 $len =
sizeof($lines);
1181 $other_len =
sizeof($other_changed);
1195 while ($j < $other_len && $other_changed[$j]) {
1199 while (
$i < $len && !$changed[
$i]) {
1200 USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
1203 while ($j < $other_len && $other_changed[$j]) {
1215 while (++
$i < $len && $changed[
$i]) {
1223 $runlength =
$i - $start;
1230 while ($start > 0 && $lines[$start - 1] == $lines[
$i - 1]) {
1231 $changed[--$start] = 1;
1232 $changed[--
$i] =
false;
1233 while ($start > 0 && $changed[$start - 1]) {
1237 while ($other_changed[--$j]) {
1239 USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
1247 $corresponding = $j < $other_len ?
$i : $len;
1256 while (
$i < $len && $lines[$start] == $lines[
$i]) {
1257 $changed[$start++] =
false;
1259 while (
$i < $len && $changed[
$i]) {
1263 USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
1265 if ($j < $other_len && $other_changed[$j]) {
1266 $corresponding =
$i;
1267 while ($j < $other_len && $other_changed[$j]) {
1272 }
while ($runlength !=
$i - $start);
1278 while ($corresponding <
$i) {
1279 $changed[--$start] = 1;
1282 while ($other_changed[--$j]) {
1284 USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
1312 $this->edits = $eng->diff($from_lines, $to_lines);
1329 $rev->edits = array();
1330 foreach ($this->edits as $edit) {
1331 $rev->edits[] = $edit->reverse();
1343 foreach ($this->edits as $edit) {
1344 if ($edit->type !=
'copy') {
1361 foreach ($this->edits as $edit) {
1362 if ($edit->type ==
'copy') {
1363 $lcs +=
sizeof($edit->orig);
1381 foreach ($this->edits as $edit) {
1383 array_splice($lines,
sizeof($lines), 0, $edit->orig);
1401 foreach ($this->edits as $edit) {
1402 if ($edit->closing) {
1403 array_splice($lines,
sizeof($lines), 0, $edit->closing);
1414 public function _check($from_lines, $to_lines)
1416 $fname =
'Diff::_check';
1418 if (serialize($from_lines) != serialize($this->
orig())) {
1419 trigger_error(
"Reconstructed original doesn't match", E_USER_ERROR);
1421 if (serialize($to_lines) != serialize($this->
closing())) {
1422 trigger_error(
"Reconstructed closing doesn't match", E_USER_ERROR);
1426 if (serialize($to_lines) != serialize($rev->orig())) {
1427 trigger_error(
"Reversed original doesn't match", E_USER_ERROR);
1429 if (serialize($from_lines) != serialize($rev->closing())) {
1430 trigger_error(
"Reversed closing doesn't match", E_USER_ERROR);
1435 foreach ($this->edits as $edit) {
1436 if ($prevtype == $edit->type) {
1437 trigger_error(
"Edit sequence is non-optimal", E_USER_ERROR);
1439 $prevtype = $edit->type;
1442 $lcs = $this->
lcs();
1443 trigger_error(
'Diff okay: LCS = ' . $lcs, E_USER_NOTICE);
1484 $fname =
'MappedDiff::MappedDiff';
1487 assert(
sizeof($from_lines) ==
sizeof($mapped_from_lines));
1488 assert(
sizeof($to_lines) ==
sizeof($mapped_to_lines));
1494 $orig = &$this->edits[
$i]->orig;
1495 if (is_array($orig)) {
1496 $orig = array_slice($from_lines, $xi,
sizeof($orig));
1497 $xi +=
sizeof($orig);
1500 $closing = &$this->edits[
$i]->closing;
1501 if (is_array($closing)) {
1502 $closing = array_slice($to_lines, $yi,
sizeof($closing));
1503 $yi +=
sizeof($closing);
1546 $fname =
'DiffFormatter::format';
1558 foreach ($diff->edits as $edit) {
1559 if ($edit->type ==
'copy') {
1560 if (is_array($block)) {
1561 if (
sizeof($edit->orig) <= $nlead + $ntrail) {
1565 $context = array_slice($edit->orig, 0, $ntrail);
1570 $ntrail + $xi - $x0,
1572 $ntrail + $yi - $y0,
1580 if (!is_array($block)) {
1593 $xi +=
sizeof($edit->orig);
1595 if ($edit->closing) {
1596 $yi +=
sizeof($edit->closing);
1600 if (is_array($block)) {
1615 public function _block($xbeg, $xlen, $ybeg, $ylen, $edits)
1617 $fname =
'DiffFormatter::_block';
1620 foreach ($edits as $edit) {
1621 if ($edit->type ==
'copy') {
1623 } elseif ($edit->type ==
'add') {
1624 $this->
_added($edit->closing);
1625 } elseif ($edit->type ==
'delete') {
1627 } elseif ($edit->type ==
'change') {
1628 $this->
_changed($edit->orig, $edit->closing);
1630 trigger_error(
'Unknown edit type', E_USER_ERROR);
1644 $val = ob_get_contents();
1652 $xbeg .=
"," . ($xbeg + $xlen - 1);
1655 $ybeg .=
"," . ($ybeg + $ylen - 1);
1658 return $xbeg . ($xlen ? ($ylen ?
'c' :
'd') :
'a') . $ybeg;
1670 public function _lines($lines, $prefix =
' ')
1672 foreach ($lines as $line) {
1673 echo
"$prefix $line\n";
1684 $this->
_lines($lines,
'>');
1688 $this->
_lines($lines,
'<');
1705define(
'NBSP',
' ');
1716 $this->_lines = array();
1724 if ($this->_group !==
'') {
1725 if ($this->_tag ==
'ins') {
1726 $this->_line .=
'[ilDiffInsStart]' .
1727 htmlspecialchars($this->_group) .
'[ilDiffInsEnd]';
1728 } elseif ($this->_tag ==
'del') {
1729 $this->_line .=
'[ilDiffDelStart]' .
1730 htmlspecialchars($this->_group) .
'[ilDiffDelEnd]';
1732 $this->_line .= htmlspecialchars($this->_group);
1736 $this->_tag = $new_tag;
1742 if ($this->_line !=
'') {
1743 array_push($this->_lines, $this->_line);
1745 # make empty lines visible by inserting an NBSP
1746 array_push($this->_lines,
NBSP);
1753 if ($tag != $this->_tag) {
1757 foreach ($words as $word) {
1762 if ($word[0] ==
"\n") {
1764 $word = substr($word, 1);
1766 assert(!strstr($word,
"\n"));
1767 $this->_group .= $word;
1774 return $this->_lines;
1789 $fname =
'WordLevelDiff::WordLevelDiff';
1792 list($orig_words, $orig_stripped) = $this->
_split($orig_lines);
1793 list($closing_words, $closing_stripped) = $this->
_split($closing_lines);
1806 $fname =
'WordLevelDiff::_split';
1810 $stripped = array();
1812 foreach ($lines as $line) {
1813 # If the line is too long, just pretend the entire line is one big word
1814 # This prevents resource exhaustion problems
1821 if (strlen($line) > self::MAX_LINE_LENGTH) {
1823 $stripped[] = $line;
1827 '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
1831 $words = array_merge($words, $m[0]);
1832 $stripped = array_merge($stripped, $m[1]);
1837 return array($words, $stripped);
1842 $fname =
'WordLevelDiff::orig';
1846 foreach ($this->edits as $edit) {
1847 if ($edit->type ==
'copy') {
1848 $orig->addWords($edit->orig);
1849 } elseif ($edit->orig) {
1850 $orig->addWords($edit->orig,
'del');
1853 $lines = $orig->getLines();
1860 $fname =
'WordLevelDiff::closing';
1864 foreach ($this->edits as $edit) {
1865 if ($edit->type ==
'copy') {
1866 $closing->addWords($edit->closing);
1867 } elseif ($edit->closing) {
1868 $closing->addWords($edit->closing,
'ins');
1871 $lines = $closing->getLines();
1887 $this->leading_context_lines = 2;
1888 $this->trailing_context_lines = 2;
1893 $r =
'<tr><td colspan="2" align="left"><strong><!--LINE ' . $xbeg .
"--></strong></td>\n" .
1894 '<td colspan="2" align="left"><strong><!--LINE ' . $ybeg .
"--></strong></td></tr>\n";
1907 public function _lines($lines, $prefix =
' ', $color =
'white')
1911 # HTML-escape parameter before calling this
1914 return "<td>+</td><td class='diff-addedline'>{$line}</td>";
1917 # HTML-escape parameter before calling this
1920 return "<td>-</td><td class='diff-deletedline'>{$line}</td>";
1923 # HTML-escape parameter before calling this
1926 return "<td> </td><td class='diff-context'>{$line}</td>";
1931 return '<td colspan="2"> </td>';
1936 foreach ($lines as $line) {
1938 $this->
addedLine(htmlspecialchars($line)) .
"</tr>\n";
1944 foreach ($lines as $line) {
1945 echo
'<tr>' . $this->
deletedLine(htmlspecialchars($line)) .
1952 foreach ($lines as $line) {
1955 $this->
contextLine(htmlspecialchars($line)) .
"</tr>\n";
1961 $fname =
'TableDiffFormatter::_changed';
1965 $del = $diff->orig();
1966 $add = $diff->closing();
1968 # Notice that WordLevelDiff returns HTML-escaped output.
1969 # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
1971 while ($line = array_shift($del)) {
1972 $aline = array_shift($add);
1976 foreach ($add as $line) { # If any leftovers
foreach($mandatory_scripts as $file) $timestamp
reverse()
Compute reversed Diff.
orig()
Get the original set of lines.
__construct($from_lines, $to_lines)
Constructor.
isEmpty()
Check for empty diff.
lcs()
Compute the length of the Longest Common Subsequence (LCS).
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.
loadText()
Load the text of the revisions, as well as revision data.
addHeader($diff, $otitle, $ntitle, $multi='')
Add the header to a diff body.
getDiffBody()
Get the diff table body, without header Results are cached Returns false on error.
localiseLineNumbers($text)
Replace line numbers with the text in the user's language.
loadRevisionData()
Load revision metadata for the specified articles.
showDiff($otitle, $ntitle)
Get the diff text, send it to $wgOut Returns false if the diff could not be generated,...
getMultiNotice()
If there are revisions between the ones being compared, return a note saying so.
__construct($titleObj=null, $old=0, $new=0, $rcid=0)
#-
generateDiffBody($otext, $ntext)
Generate a diff, no caching $otext and $ntext must be already segmented.
showDiffPage($diffOnly=false)
renderNewRevision()
Show the new revision of the page.
localiseLineNumbersCb($matches)
getDiff($otitle, $ntitle)
Get diff table, including header Note that the interface has changed, it's no longer static.
setText($oldText, $newText)
Use specified text instead of loading from the database.
__construct( $from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines)
Constructor.
__construct($orig_lines, $closing_lines)
Constructor.
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)
diff($from_lines, $to_lines)
_shift_boundaries($lines, &$changed, $other_changed)
_line_hash($line)
Returns the whole line if it's small enough, or the MD5 hash otherwise.
_compareseq($xoff, $xlim, $yoff, $ylim)
__construct($orig, $closing)
__construct($orig, $closing=false)
addWords($words, $tag='')
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc