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 while (list($junk, $y) = each($matches)) {
1028 if (empty($this->in_seq[$y])) {
1031 $ymids[$k] = $ymids[$k - 1];
1035 while (list( , $y) = each($matches)) {
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';
1180 $len =
sizeof($lines);
1181 $other_len =
sizeof($other_changed);
1195 while ($j < $other_len && $other_changed[$j]) {
1200 USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
1203 while ($j < $other_len && $other_changed[$j]) {
1224 $runlength =
$i - $start;
1231 while ($start > 0 && $lines[$start - 1] == $lines[
$i - 1]) {
1234 while ($start > 0 &&
$changed[$start - 1]) {
1238 while ($other_changed[--$j]) {
1241 USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
1249 $corresponding = $j < $other_len ?
$i : $len;
1258 while (
$i < $len && $lines[$start] == $lines[
$i]) {
1265 USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
1267 if ($j < $other_len && $other_changed[$j]) {
1268 $corresponding =
$i;
1269 while ($j < $other_len && $other_changed[$j]) {
1274 }
while ($runlength !=
$i - $start);
1280 while ($corresponding <
$i) {
1284 while ($other_changed[--$j]) {
1287 USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
1315 $this->edits = $eng->
diff($from_lines, $to_lines);
1332 $rev->edits = array();
1333 foreach ($this->edits as $edit) {
1334 $rev->edits[] = $edit->reverse();
1346 foreach ($this->edits as $edit) {
1347 if ($edit->type !=
'copy') {
1364 foreach ($this->edits as $edit) {
1365 if ($edit->type ==
'copy') {
1366 $lcs +=
sizeof($edit->orig);
1384 foreach ($this->edits as $edit) {
1386 array_splice($lines,
sizeof($lines), 0, $edit->orig);
1404 foreach ($this->edits as $edit) {
1405 if ($edit->closing) {
1406 array_splice($lines,
sizeof($lines), 0, $edit->closing);
1417 public function _check($from_lines, $to_lines)
1419 $fname =
'Diff::_check';
1421 if (serialize($from_lines) != serialize($this->
orig())) {
1422 trigger_error(
"Reconstructed original doesn't match", E_USER_ERROR);
1424 if (serialize($to_lines) != serialize($this->
closing())) {
1425 trigger_error(
"Reconstructed closing doesn't match", E_USER_ERROR);
1429 if (serialize($to_lines) != serialize($rev->orig())) {
1430 trigger_error(
"Reversed original doesn't match", E_USER_ERROR);
1432 if (serialize($from_lines) != serialize($rev->closing())) {
1433 trigger_error(
"Reversed closing doesn't match", E_USER_ERROR);
1438 foreach ($this->edits as $edit) {
1439 if ($prevtype == $edit->type) {
1440 trigger_error(
"Edit sequence is non-optimal", E_USER_ERROR);
1442 $prevtype = $edit->type;
1445 $lcs = $this->
lcs();
1446 trigger_error(
'Diff okay: LCS = ' . $lcs, E_USER_NOTICE);
1487 $fname =
'MappedDiff::MappedDiff';
1490 assert(
sizeof($from_lines) ==
sizeof($mapped_from_lines));
1491 assert(
sizeof($to_lines) ==
sizeof($mapped_to_lines));
1497 $orig = &$this->edits[
$i]->orig;
1498 if (is_array($orig)) {
1499 $orig = array_slice($from_lines, $xi,
sizeof($orig));
1500 $xi +=
sizeof($orig);
1503 $closing = &$this->edits[
$i]->closing;
1504 if (is_array($closing)) {
1505 $closing = array_slice($to_lines, $yi,
sizeof($closing));
1506 $yi +=
sizeof($closing);
1549 $fname =
'DiffFormatter::format';
1561 foreach ($diff->edits as $edit) {
1562 if ($edit->type ==
'copy') {
1563 if (is_array($block)) {
1564 if (
sizeof($edit->orig) <= $nlead + $ntrail) {
1568 $context = array_slice($edit->orig, 0, $ntrail);
1573 $ntrail + $xi - $x0,
1575 $ntrail + $yi - $y0,
1583 if (!is_array($block)) {
1596 $xi +=
sizeof($edit->orig);
1598 if ($edit->closing) {
1599 $yi +=
sizeof($edit->closing);
1603 if (is_array($block)) {
1618 public function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
1620 $fname =
'DiffFormatter::_block';
1623 foreach ($edits as $edit) {
1624 if ($edit->type ==
'copy') {
1626 } elseif ($edit->type ==
'add') {
1627 $this->
_added($edit->closing);
1628 } elseif ($edit->type ==
'delete') {
1630 } elseif ($edit->type ==
'change') {
1631 $this->
_changed($edit->orig, $edit->closing);
1633 trigger_error(
'Unknown edit type', E_USER_ERROR);
1647 $val = ob_get_contents();
1655 $xbeg .=
"," . ($xbeg + $xlen - 1);
1658 $ybeg .=
"," . ($ybeg + $ylen - 1);
1661 return $xbeg . ($xlen ? ($ylen ?
'c' :
'd') :
'a') . $ybeg;
1673 public function _lines($lines, $prefix =
' ')
1675 foreach ($lines as $line) {
1676 echo
"$prefix $line\n";
1687 $this->
_lines($lines,
'>');
1691 $this->
_lines($lines,
'<');
1708define(
'NBSP',
' ');
1719 $this->_lines = array();
1727 if ($this->_group !==
'') {
1728 if ($this->_tag ==
'ins') {
1729 $this->_line .=
'[ilDiffInsStart]' .
1730 htmlspecialchars($this->_group) .
'[ilDiffInsEnd]';
1731 } elseif ($this->_tag ==
'del') {
1732 $this->_line .=
'[ilDiffDelStart]' .
1733 htmlspecialchars($this->_group) .
'[ilDiffDelEnd]';
1735 $this->_line .= htmlspecialchars($this->_group);
1739 $this->_tag = $new_tag;
1745 if ($this->_line !=
'') {
1746 array_push($this->_lines, $this->_line);
1748 # make empty lines visible by inserting an NBSP
1749 array_push($this->_lines,
NBSP);
1756 if ($tag != $this->_tag) {
1760 foreach ($words as $word) {
1765 if ($word[0] ==
"\n") {
1767 $word = substr($word, 1);
1769 assert(!strstr($word,
"\n"));
1770 $this->_group .= $word;
1777 return $this->_lines;
1792 $fname =
'WordLevelDiff::WordLevelDiff';
1795 list($orig_words, $orig_stripped) = $this->
_split($orig_lines);
1796 list($closing_words, $closing_stripped) = $this->
_split($closing_lines);
1809 $fname =
'WordLevelDiff::_split';
1813 $stripped = array();
1815 foreach ($lines as $line) {
1816 # If the line is too long, just pretend the entire line is one big word
1817 # This prevents resource exhaustion problems
1824 if (strlen($line) > self::MAX_LINE_LENGTH) {
1826 $stripped[] = $line;
1830 '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
1834 $words = array_merge($words, $m[0]);
1835 $stripped = array_merge($stripped, $m[1]);
1840 return array($words, $stripped);
1845 $fname =
'WordLevelDiff::orig';
1849 foreach ($this->edits as $edit) {
1850 if ($edit->type ==
'copy') {
1852 } elseif ($edit->orig) {
1853 $orig->addWords($edit->orig,
'del');
1856 $lines = $orig->getLines();
1863 $fname =
'WordLevelDiff::closing';
1867 foreach ($this->edits as $edit) {
1868 if ($edit->type ==
'copy') {
1869 $closing->
addWords($edit->closing);
1870 } elseif ($edit->closing) {
1871 $closing->addWords($edit->closing,
'ins');
1874 $lines = $closing->getLines();
1890 $this->leading_context_lines = 2;
1891 $this->trailing_context_lines = 2;
1896 $r =
'<tr><td colspan="2" align="left"><strong><!--LINE ' . $xbeg .
"--></strong></td>\n" .
1897 '<td colspan="2" align="left"><strong><!--LINE ' . $ybeg .
"--></strong></td></tr>\n";
1910 public function _lines($lines, $prefix =
' ', $color =
'white')
1914 # HTML-escape parameter before calling this
1917 return "<td>+</td><td class='diff-addedline'>{$line}</td>";
1920 # HTML-escape parameter before calling this
1923 return "<td>-</td><td class='diff-deletedline'>{$line}</td>";
1926 # HTML-escape parameter before calling this
1929 return "<td> </td><td class='diff-context'>{$line}</td>";
1934 return '<td colspan="2"> </td>';
1939 foreach ($lines as $line) {
1941 $this->
addedLine(htmlspecialchars($line)) .
"</tr>\n";
1947 foreach ($lines as $line) {
1948 echo
'<tr>' . $this->
deletedLine(htmlspecialchars($line)) .
1955 foreach ($lines as $line) {
1958 $this->
contextLine(htmlspecialchars($line)) .
"</tr>\n";
1964 $fname =
'TableDiffFormatter::_changed';
1968 $del = $diff->orig();
1969 $add = $diff->closing();
1971 # Notice that WordLevelDiff returns HTML-escaped output.
1972 # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
1974 while ($line = array_shift($del)) {
1975 $aline = array_shift($add);
1979 foreach ($add as $line) { # If any leftovers
foreach($mandatory_scripts as $file) $timestamp
An exception for terminatinating execution or to throw for unit testing.
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