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';
 
 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