Issues (847)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

inc/DifferenceEngine.php (14 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
4
 *
5
 * Additions by Axel Boldt for MediaWiki
6
 *
7
 * @copyright (C) 2000, 2001 Geoffrey T. Dairiki <[email protected]>
8
 * @license  You may copy this code freely under the conditions of the GPL.
9
 */
10
define('USE_ASSERTS', function_exists('assert'));
11
12
class _DiffOp {
13
    var $type;
14
    var $orig;
15
    var $closing;
16
17
    /**
18
     * @return _DiffOp
19
     */
20
    function reverse() {
21
        trigger_error("pure virtual", E_USER_ERROR);
22
    }
23
24
    function norig() {
25
        return $this->orig ? count($this->orig) : 0;
26
    }
27
28
    function nclosing() {
29
        return $this->closing ? count($this->closing) : 0;
30
    }
31
}
32
33
class _DiffOp_Copy extends _DiffOp {
34
    var $type = 'copy';
35
36
    function __construct($orig, $closing = false) {
37
        if (!is_array($closing))
38
            $closing = $orig;
39
        $this->orig = $orig;
40
        $this->closing = $closing;
41
    }
42
43
    function reverse() {
44
        return new _DiffOp_Copy($this->closing, $this->orig);
45
    }
46
}
47
48
class _DiffOp_Delete extends _DiffOp {
49
    var $type = 'delete';
50
51
    function __construct($lines) {
52
        $this->orig = $lines;
53
        $this->closing = false;
54
    }
55
56
    function reverse() {
57
        return new _DiffOp_Add($this->orig);
58
    }
59
}
60
61
class _DiffOp_Add extends _DiffOp {
62
    var $type = 'add';
63
64
    function __construct($lines) {
65
        $this->closing = $lines;
66
        $this->orig = false;
67
    }
68
69
    function reverse() {
70
        return new _DiffOp_Delete($this->closing);
71
    }
72
}
73
74
class _DiffOp_Change extends _DiffOp {
75
    var $type = 'change';
76
77
    function __construct($orig, $closing) {
78
        $this->orig = $orig;
79
        $this->closing = $closing;
80
    }
81
82
    function reverse() {
83
        return new _DiffOp_Change($this->closing, $this->orig);
84
    }
85
}
86
87
88
/**
89
 * Class used internally by Diff to actually compute the diffs.
90
 *
91
 * The algorithm used here is mostly lifted from the perl module
92
 * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
93
 *   http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
94
 *
95
 * More ideas are taken from:
96
 *   http://www.ics.uci.edu/~eppstein/161/960229.html
97
 *
98
 * Some ideas are (and a bit of code) are from from analyze.c, from GNU
99
 * diffutils-2.7, which can be found at:
100
 *   ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
101
 *
102
 * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
103
 * are my own.
104
 *
105
 * @author Geoffrey T. Dairiki
106
 * @access private
107
 */
108
class _DiffEngine {
109
110
    var $xchanged = array();
111
    var $ychanged = array();
112
    var $xv = array();
113
    var $yv = array();
114
    var $xind = array();
115
    var $yind = array();
116
    var $seq;
117
    var $in_seq;
118
    var $lcs;
119
120
    /**
121
     * @param array $from_lines
122
     * @param array $to_lines
123
     * @return _DiffOp[]
124
     */
125
    function diff($from_lines, $to_lines) {
126
        $n_from = count($from_lines);
127
        $n_to = count($to_lines);
128
129
        $this->xchanged = $this->ychanged = array();
130
        $this->xv = $this->yv = array();
131
        $this->xind = $this->yind = array();
132
        unset($this->seq);
133
        unset($this->in_seq);
134
        unset($this->lcs);
135
136
        // Skip leading common lines.
137
        for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
138
            if ($from_lines[$skip] != $to_lines[$skip])
139
                break;
140
            $this->xchanged[$skip] = $this->ychanged[$skip] = false;
141
        }
142
        // Skip trailing common lines.
143
        $xi = $n_from;
144
        $yi = $n_to;
145
        for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
146
            if ($from_lines[$xi] != $to_lines[$yi])
147
                break;
148
            $this->xchanged[$xi] = $this->ychanged[$yi] = false;
149
        }
150
151
        // Ignore lines which do not exist in both files.
152
        for ($xi = $skip; $xi < $n_from - $endskip; $xi++)
153
            $xhash[$from_lines[$xi]] = 1;
154
        for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
155
            $line = $to_lines[$yi];
156
            if (($this->ychanged[$yi] = empty($xhash[$line])))
157
                continue;
158
            $yhash[$line] = 1;
159
            $this->yv[] = $line;
160
            $this->yind[] = $yi;
161
        }
162
        for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
163
            $line = $from_lines[$xi];
164
            if (($this->xchanged[$xi] = empty($yhash[$line])))
165
                continue;
166
            $this->xv[] = $line;
167
            $this->xind[] = $xi;
168
        }
169
170
        // Find the LCS.
171
        $this->_compareseq(0, count($this->xv), 0, count($this->yv));
172
173
        // Merge edits when possible
174
        $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
175
        $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
176
177
        // Compute the edit operations.
178
        $edits = array();
179
        $xi = $yi = 0;
180
        while ($xi < $n_from || $yi < $n_to) {
181
            USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
182
            USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
183
184
            // Skip matching "snake".
185
            $copy = array();
186
            while ($xi < $n_from && $yi < $n_to && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
187
                $copy[] = $from_lines[$xi++];
188
                ++$yi;
189
            }
190
            if ($copy)
191
                $edits[] = new _DiffOp_Copy($copy);
192
193
            // Find deletes & adds.
194
            $delete = array();
195
            while ($xi < $n_from && $this->xchanged[$xi])
196
                $delete[] = $from_lines[$xi++];
197
198
            $add = array();
199
            while ($yi < $n_to && $this->ychanged[$yi])
200
                $add[] = $to_lines[$yi++];
201
202
            if ($delete && $add)
203
                $edits[] = new _DiffOp_Change($delete, $add);
204
            elseif ($delete)
205
                $edits[] = new _DiffOp_Delete($delete);
206
            elseif ($add)
207
                $edits[] = new _DiffOp_Add($add);
208
        }
209
        return $edits;
210
    }
211
212
213
    /**
214
     * Divide the Largest Common Subsequence (LCS) of the sequences
215
     * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
216
     * sized segments.
217
     *
218
     * Returns (LCS, PTS).  LCS is the length of the LCS. PTS is an
219
     * array of NCHUNKS+1 (X, Y) indexes giving the diving points between
220
     * sub sequences.  The first sub-sequence is contained in [X0, X1),
221
     * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on.  Note
222
     * that (X0, Y0) == (XOFF, YOFF) and
223
     * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).
224
     *
225
     * This function assumes that the first lines of the specified portions
226
     * of the two files do not match, and likewise that the last lines do not
227
     * match.  The caller must trim matching lines from the beginning and end
228
     * of the portions it is going to specify.
229
     *
230
     * @param integer $xoff
231
     * @param integer $xlim
232
     * @param integer $yoff
233
     * @param integer $ylim
234
     * @param integer $nchunks
235
     *
236
     * @return array
237
     */
238
    function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) {
239
        $flip = false;
240
241
        if ($xlim - $xoff > $ylim - $yoff) {
242
            // Things seems faster (I'm not sure I understand why)
243
            // when the shortest sequence in X.
244
            $flip = true;
245
            list ($xoff, $xlim, $yoff, $ylim) = array($yoff, $ylim, $xoff, $xlim);
246
        }
247
248
        if ($flip)
249
            for ($i = $ylim - 1; $i >= $yoff; $i--)
250
                $ymatches[$this->xv[$i]][] = $i;
251
        else
252
            for ($i = $ylim - 1; $i >= $yoff; $i--)
253
                $ymatches[$this->yv[$i]][] = $i;
254
255
        $this->lcs = 0;
256
        $this->seq[0]= $yoff - 1;
257
        $this->in_seq = array();
258
        $ymids[0] = array();
259
260
        $numer = $xlim - $xoff + $nchunks - 1;
261
        $x = $xoff;
262
        for ($chunk = 0; $chunk < $nchunks; $chunk++) {
263
            if ($chunk > 0)
264
                for ($i = 0; $i <= $this->lcs; $i++)
265
                    $ymids[$i][$chunk-1] = $this->seq[$i];
266
267
            $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
268
            for ( ; $x < $x1; $x++) {
269
                $line = $flip ? $this->yv[$x] : $this->xv[$x];
270
                if (empty($ymatches[$line]))
271
                    continue;
272
                $matches = $ymatches[$line];
273
                $switch = false;
274
                foreach ($matches as $y) {
275
                    if ($switch && $y > $this->seq[$k-1]) {
276
                        USE_ASSERTS && assert($y < $this->seq[$k]);
277
                        // Optimization: this is a common case:
278
                        //  next match is just replacing previous match.
279
                        $this->in_seq[$this->seq[$k]] = false;
280
                        $this->seq[$k] = $y;
281
                        $this->in_seq[$y] = 1;
282
                    }
283
                    else if (empty($this->in_seq[$y])) {
284
                        $k = $this->_lcs_pos($y);
285
                        USE_ASSERTS && assert($k > 0);
286
                        $ymids[$k] = $ymids[$k-1];
287
                        $switch = true;
288
                    }
289
                }
290
            }
291
        }
292
293
        $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
294
        $ymid = $ymids[$this->lcs];
295
        for ($n = 0; $n < $nchunks - 1; $n++) {
296
            $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);
297
            $y1 = $ymid[$n] + 1;
298
            $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
299
        }
300
        $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
301
302
        return array($this->lcs, $seps);
303
    }
304
305
    function _lcs_pos($ypos) {
306
        $end = $this->lcs;
307
        if ($end == 0 || $ypos > $this->seq[$end]) {
308
            $this->seq[++$this->lcs] = $ypos;
309
            $this->in_seq[$ypos] = 1;
310
            return $this->lcs;
311
        }
312
313
        $beg = 1;
314
        while ($beg < $end) {
315
            $mid = (int)(($beg + $end) / 2);
316
            if ($ypos > $this->seq[$mid])
317
                $beg = $mid + 1;
318
            else
319
                $end = $mid;
320
        }
321
322
        USE_ASSERTS && assert($ypos != $this->seq[$end]);
323
324
        $this->in_seq[$this->seq[$end]] = false;
325
        $this->seq[$end] = $ypos;
326
        $this->in_seq[$ypos] = 1;
327
        return $end;
328
    }
329
330
    /**
331
     * Find LCS of two sequences.
332
     *
333
     * The results are recorded in the vectors $this->{x,y}changed[], by
334
     * storing a 1 in the element for each line that is an insertion
335
     * or deletion (ie. is not in the LCS).
336
     *
337
     * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
338
     *
339
     * Note that XLIM, YLIM are exclusive bounds.
340
     * All line numbers are origin-0 and discarded lines are not counted.
341
     *
342
     * @param integer $xoff
343
     * @param integer $xlim
344
     * @param integer $yoff
345
     * @param integer $ylim
346
     */
347
    function _compareseq($xoff, $xlim, $yoff, $ylim) {
348
        // Slide down the bottom initial diagonal.
349
        while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff]) {
350
            ++$xoff;
351
            ++$yoff;
352
        }
353
354
        // Slide up the top initial diagonal.
355
        while ($xlim > $xoff && $ylim > $yoff && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
356
            --$xlim;
357
            --$ylim;
358
        }
359
360
        if ($xoff == $xlim || $yoff == $ylim)
361
            $lcs = 0;
362
        else {
363
            // This is ad hoc but seems to work well.
364
            //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
365
            //$nchunks = max(2,min(8,(int)$nchunks));
366
            $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
367
            list ($lcs, $seps)
368
                = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
369
        }
370
371
        if ($lcs == 0) {
372
            // X and Y sequences have no common subsequence:
373
            // mark all changed.
374
            while ($yoff < $ylim)
375
                $this->ychanged[$this->yind[$yoff++]] = 1;
376
            while ($xoff < $xlim)
377
                $this->xchanged[$this->xind[$xoff++]] = 1;
378
        }
379
        else {
380
            // Use the partitions to split this problem into subproblems.
381
            reset($seps);
382
            $pt1 = $seps[0];
383
            while ($pt2 = next($seps)) {
384
                $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
385
                $pt1 = $pt2;
386
            }
387
        }
388
    }
389
390
    /**
391
     * Adjust inserts/deletes of identical lines to join changes
392
     * as much as possible.
393
     *
394
     * We do something when a run of changed lines include a
395
     * line at one end and has an excluded, identical line at the other.
396
     * We are free to choose which identical line is included.
397
     * `compareseq' usually chooses the one at the beginning,
398
     * but usually it is cleaner to consider the following identical line
399
     * to be the "change".
400
     *
401
     * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
402
     *
403
     * @param array $lines
404
     * @param array $changed
405
     * @param array $other_changed
406
     */
407
    function _shift_boundaries($lines, &$changed, $other_changed) {
408
        $i = 0;
409
        $j = 0;
410
411
        USE_ASSERTS && assert(count($lines) == count($changed));
412
        $len = count($lines);
413
        $other_len = count($other_changed);
414
415
        while (1) {
416
            /*
417
             * Scan forwards to find beginning of another run of changes.
418
             * Also keep track of the corresponding point in the other file.
419
             *
420
             * Throughout this code, $i and $j are adjusted together so that
421
             * the first $i elements of $changed and the first $j elements
422
             * of $other_changed both contain the same number of zeros
423
             * (unchanged lines).
424
             * Furthermore, $j is always kept so that $j == $other_len or
425
             * $other_changed[$j] == false.
426
             */
427
            while ($j < $other_len && $other_changed[$j])
428
                $j++;
429
430
            while ($i < $len && ! $changed[$i]) {
431
                USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]);
432
                $i++;
433
                $j++;
434
                while ($j < $other_len && $other_changed[$j])
435
                    $j++;
436
            }
437
438
            if ($i == $len)
439
                break;
440
441
            $start = $i;
442
443
            // Find the end of this run of changes.
444
            while (++$i < $len && $changed[$i])
445
                continue;
446
447
            do {
448
                /*
449
                 * Record the length of this run of changes, so that
450
                 * we can later determine whether the run has grown.
451
                 */
452
                $runlength = $i - $start;
453
454
                /*
455
                 * Move the changed region back, so long as the
456
                 * previous unchanged line matches the last changed one.
457
                 * This merges with previous changed regions.
458
                 */
459
                while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
460
                    $changed[--$start] = 1;
461
                    $changed[--$i] = false;
462
                    while ($start > 0 && $changed[$start - 1])
463
                        $start--;
464
                    USE_ASSERTS && assert($j > 0);
465
                    while ($other_changed[--$j])
466
                        continue;
467
                    USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
468
                }
469
470
                /*
471
                 * Set CORRESPONDING to the end of the changed run, at the last
472
                 * point where it corresponds to a changed run in the other file.
473
                 * CORRESPONDING == LEN means no such point has been found.
474
                 */
475
                $corresponding = $j < $other_len ? $i : $len;
476
477
                /*
478
                 * Move the changed region forward, so long as the
479
                 * first changed line matches the following unchanged one.
480
                 * This merges with following changed regions.
481
                 * Do this second, so that if there are no merges,
482
                 * the changed region is moved forward as far as possible.
483
                 */
484
                while ($i < $len && $lines[$start] == $lines[$i]) {
485
                    $changed[$start++] = false;
486
                    $changed[$i++] = 1;
487
                    while ($i < $len && $changed[$i])
488
                        $i++;
489
490
                    USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]);
491
                    $j++;
492
                    if ($j < $other_len && $other_changed[$j]) {
493
                        $corresponding = $i;
494
                        while ($j < $other_len && $other_changed[$j])
495
                            $j++;
496
                    }
497
                }
498
            } while ($runlength != $i - $start);
499
500
            /*
501
             * If possible, move the fully-merged run of changes
502
             * back to a corresponding run in the other file.
503
             */
504
            while ($corresponding < $i) {
505
                $changed[--$start] = 1;
506
                $changed[--$i] = 0;
507
                USE_ASSERTS && assert($j > 0);
508
                while ($other_changed[--$j])
509
                    continue;
510
                USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
511
            }
512
        }
513
    }
514
}
515
516
/**
517
 * Class representing a 'diff' between two sequences of strings.
518
 */
519
class Diff {
520
521
    var $edits;
522
523
    /**
524
     * Constructor.
525
     * Computes diff between sequences of strings.
526
     *
527
     * @param array $from_lines An array of strings.
528
     *                          (Typically these are lines from a file.)
529
     * @param array $to_lines   An array of strings.
530
     */
531
    function __construct($from_lines, $to_lines) {
532
        $eng = new _DiffEngine;
533
        $this->edits = $eng->diff($from_lines, $to_lines);
534
        //$this->_check($from_lines, $to_lines);
535
    }
536
537
    /**
538
     * Compute reversed Diff.
539
     *
540
     * SYNOPSIS:
541
     *
542
     *  $diff = new Diff($lines1, $lines2);
543
     *  $rev = $diff->reverse();
544
     *
545
     * @return Diff  A Diff object representing the inverse of the
546
     *               original diff.
547
     */
548
    function reverse() {
549
        $rev = $this;
550
        $rev->edits = array();
551
        foreach ($this->edits as $edit) {
552
            $rev->edits[] = $edit->reverse();
553
        }
554
        return $rev;
555
    }
556
557
    /**
558
     * Check for empty diff.
559
     *
560
     * @return bool True iff two sequences were identical.
561
     */
562
    function isEmpty() {
563
        foreach ($this->edits as $edit) {
564
            if ($edit->type != 'copy')
565
                return false;
566
        }
567
        return true;
568
    }
569
570
    /**
571
     * Compute the length of the Longest Common Subsequence (LCS).
572
     *
573
     * This is mostly for diagnostic purposed.
574
     *
575
     * @return int The length of the LCS.
576
     */
577
    function lcs() {
578
        $lcs = 0;
579
        foreach ($this->edits as $edit) {
580
            if ($edit->type == 'copy')
581
                $lcs += count($edit->orig);
582
        }
583
        return $lcs;
584
    }
585
586
    /**
587
     * Get the original set of lines.
588
     *
589
     * This reconstructs the $from_lines parameter passed to the
590
     * constructor.
591
     *
592
     * @return array The original sequence of strings.
593
     */
594
    function orig() {
595
        $lines = array();
596
597
        foreach ($this->edits as $edit) {
598
            if ($edit->orig)
599
                array_splice($lines, count($lines), 0, $edit->orig);
600
        }
601
        return $lines;
602
    }
603
604
    /**
605
     * Get the closing set of lines.
606
     *
607
     * This reconstructs the $to_lines parameter passed to the
608
     * constructor.
609
     *
610
     * @return array The sequence of strings.
611
     */
612
    function closing() {
613
        $lines = array();
614
615
        foreach ($this->edits as $edit) {
616
            if ($edit->closing)
617
                array_splice($lines, count($lines), 0, $edit->closing);
618
        }
619
        return $lines;
620
    }
621
622
    /**
623
     * Check a Diff for validity.
624
     *
625
     * This is here only for debugging purposes.
626
     *
627
     * @param mixed $from_lines
628
     * @param mixed $to_lines
629
     */
630
    function _check($from_lines, $to_lines) {
631
        if (serialize($from_lines) != serialize($this->orig()))
632
            trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
633
        if (serialize($to_lines) != serialize($this->closing()))
634
            trigger_error("Reconstructed closing doesn't match", E_USER_ERROR);
635
636
        $rev = $this->reverse();
637
        if (serialize($to_lines) != serialize($rev->orig()))
638
            trigger_error("Reversed original doesn't match", E_USER_ERROR);
639
        if (serialize($from_lines) != serialize($rev->closing()))
640
            trigger_error("Reversed closing doesn't match", E_USER_ERROR);
641
642
        $prevtype = 'none';
643
        foreach ($this->edits as $edit) {
644
            if ($prevtype == $edit->type)
645
                trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
646
            $prevtype = $edit->type;
647
        }
648
649
        $lcs = $this->lcs();
650
        trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE);
651
    }
652
}
653
654
/**
655
 * FIXME: bad name.
656
 */
657
class MappedDiff extends Diff {
658
    /**
659
     * Constructor.
660
     *
661
     * Computes diff between sequences of strings.
662
     *
663
     * This can be used to compute things like
664
     * case-insensitve diffs, or diffs which ignore
665
     * changes in white-space.
666
     *
667
     * @param string[] $from_lines         An array of strings.
668
     *                                     (Typically these are lines from a file.)
669
     *
670
     * @param string[] $to_lines           An array of strings.
671
     *
672
     * @param string[] $mapped_from_lines  This array should
673
     *                                     have the same size number of elements as $from_lines.
674
     *                                     The elements in $mapped_from_lines and
675
     *                                     $mapped_to_lines are what is actually compared
676
     *                                     when computing the diff.
677
     *
678
     * @param string[] $mapped_to_lines    This array should
679
     *                                     have the same number of elements as $to_lines.
680
     */
681
    function __construct($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) {
682
683
        assert(count($from_lines) == count($mapped_from_lines));
684
        assert(count($to_lines) == count($mapped_to_lines));
685
686
        parent::__construct($mapped_from_lines, $mapped_to_lines);
687
688
        $xi = $yi = 0;
689
        $ecnt = count($this->edits);
690
        for ($i = 0; $i < $ecnt; $i++) {
691
            $orig = &$this->edits[$i]->orig;
692
            if (is_array($orig)) {
693
                $orig = array_slice($from_lines, $xi, count($orig));
694
                $xi += count($orig);
695
            }
696
697
            $closing = &$this->edits[$i]->closing;
698
            if (is_array($closing)) {
699
                $closing = array_slice($to_lines, $yi, count($closing));
700
                $yi += count($closing);
701
            }
702
        }
703
    }
704
}
705
706
/**
707
 * A class to format Diffs
708
 *
709
 * This class formats the diff in classic diff format.
710
 * It is intended that this class be customized via inheritance,
711
 * to obtain fancier outputs.
712
 */
713
class DiffFormatter {
714
    /**
715
     * Number of leading context "lines" to preserve.
716
     *
717
     * This should be left at zero for this class, but subclasses
718
     * may want to set this to other values.
719
     */
720
    var $leading_context_lines = 0;
721
722
    /**
723
     * Number of trailing context "lines" to preserve.
724
     *
725
     * This should be left at zero for this class, but subclasses
726
     * may want to set this to other values.
727
     */
728
    var $trailing_context_lines = 0;
729
730
    /**
731
     * Format a diff.
732
     *
733
     * @param Diff $diff A Diff object.
734
     * @return string The formatted output.
735
     */
736
    function format($diff) {
737
738
        $xi = $yi = 1;
739
        $x0 = $y0 = 0;
740
        $block = false;
741
        $context = array();
742
743
        $nlead = $this->leading_context_lines;
744
        $ntrail = $this->trailing_context_lines;
745
746
        $this->_start_diff();
747
748
        foreach ($diff->edits as $edit) {
749
            if ($edit->type == 'copy') {
750
                if (is_array($block)) {
751
                    if (count($edit->orig) <= $nlead + $ntrail) {
752
                        $block[] = $edit;
753
                    }
754
                    else{
755
                        if ($ntrail) {
756
                            $context = array_slice($edit->orig, 0, $ntrail);
757
                            $block[] = new _DiffOp_Copy($context);
758
                        }
759
                        $this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block);
760
                        $block = false;
761
                    }
762
                }
763
                $context = $edit->orig;
764
            }
765
            else {
766
                if (! is_array($block)) {
767
                    $context = array_slice($context, count($context) - $nlead);
768
                    $x0 = $xi - count($context);
769
                    $y0 = $yi - count($context);
770
                    $block = array();
771
                    if ($context)
772
                        $block[] = new _DiffOp_Copy($context);
773
                }
774
                $block[] = $edit;
775
            }
776
777
            if ($edit->orig)
778
                $xi += count($edit->orig);
779
            if ($edit->closing)
780
                $yi += count($edit->closing);
781
        }
782
783
        if (is_array($block))
784
            $this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block);
785
786
        return $this->_end_diff();
787
    }
788
789
    /**
790
     * @param int $xbeg
791
     * @param int $xlen
792
     * @param int $ybeg
793
     * @param int $ylen
794
     * @param array $edits
795
     */
796
    function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
797
        $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
798
        foreach ($edits as $edit) {
799
            if ($edit->type == 'copy')
800
                $this->_context($edit->orig);
801
            elseif ($edit->type == 'add')
802
                $this->_added($edit->closing);
803
            elseif ($edit->type == 'delete')
804
                $this->_deleted($edit->orig);
805
            elseif ($edit->type == 'change')
806
                $this->_changed($edit->orig, $edit->closing);
807
            else
808
                trigger_error("Unknown edit type", E_USER_ERROR);
809
        }
810
        $this->_end_block();
811
    }
812
813
    function _start_diff() {
814
        ob_start();
815
    }
816
817
    function _end_diff() {
818
        $val = ob_get_contents();
819
        ob_end_clean();
820
        return $val;
821
    }
822
823
    /**
824
     * @param int $xbeg
825
     * @param int $xlen
826
     * @param int $ybeg
827
     * @param int $ylen
828
     * @return string
829
     */
830
    function _block_header($xbeg, $xlen, $ybeg, $ylen) {
831
        if ($xlen > 1)
832
            $xbeg .= "," . ($xbeg + $xlen - 1);
833
        if ($ylen > 1)
834
            $ybeg .= "," . ($ybeg + $ylen - 1);
835
836
        return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
837
    }
838
839
    /**
840
     * @param string $header
841
     */
842
    function _start_block($header) {
843
        echo $header;
844
    }
845
846
    function _end_block() {
847
    }
848
849
    function _lines($lines, $prefix = ' ') {
850
        foreach ($lines as $line)
851
            echo "$prefix ".$this->_escape($line)."\n";
852
    }
853
854
    function _context($lines) {
855
        $this->_lines($lines);
856
    }
857
858
    function _added($lines) {
859
        $this->_lines($lines, ">");
860
    }
861
    function _deleted($lines) {
862
        $this->_lines($lines, "<");
863
    }
864
865
    function _changed($orig, $closing) {
866
        $this->_deleted($orig);
867
        echo "---\n";
868
        $this->_added($closing);
869
    }
870
871
    /**
872
     * Escape string
873
     *
874
     * Override this method within other formatters if escaping required.
875
     * Base class requires $str to be returned WITHOUT escaping.
876
     *
877
     * @param $str string Text string to escape
878
     * @return string The escaped string.
879
     */
880
    function _escape($str){
881
        return $str;
882
    }
883
}
884
885
/**
886
 * Utilityclass for styling HTML formatted diffs
887
 *
888
 * Depends on global var $DIFF_INLINESTYLES, if true some minimal predefined
889
 * inline styles are used. Useful for HTML mails and RSS feeds
890
 *
891
 * @author Andreas Gohr <[email protected]>
892
 */
893
class HTMLDiff {
894
    /**
895
     * Holds the style names and basic CSS
896
     */
897
    static public $styles = array(
898
            'diff-addedline'    => 'background-color: #ddffdd;',
899
            'diff-deletedline'  => 'background-color: #ffdddd;',
900
            'diff-context'      => 'background-color: #f5f5f5;',
901
            'diff-mark'         => 'color: #ff0000;',
902
        );
903
904
    /**
905
     * Return a class or style parameter
906
     *
907
     * @param string $classname
908
     *
909
     * @return string
910
     */
911
    static function css($classname){
912
        global $DIFF_INLINESTYLES;
913
914
        if($DIFF_INLINESTYLES){
915
            if(!isset(self::$styles[$classname])) return '';
916
            return 'style="'.self::$styles[$classname].'"';
917
        }else{
918
            return 'class="'.$classname.'"';
919
        }
920
    }
921
}
922
923
/**
924
 *  Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
925
 *
926
 */
927
928
define('NBSP', "\xC2\xA0");     // utf-8 non-breaking space.
929
930
class _HWLDF_WordAccumulator {
931
932
    function __construct() {
933
        $this->_lines = array();
934
        $this->_line = '';
935
        $this->_group = '';
936
        $this->_tag = '';
937
    }
938
939
    function _flushGroup($new_tag) {
940
        if ($this->_group !== '') {
941
            if ($this->_tag == 'mark')
942
                $this->_line .= '<strong '.HTMLDiff::css('diff-mark').'>'.$this->_escape($this->_group).'</strong>';
943
            elseif ($this->_tag == 'add')
944
                $this->_line .= '<span '.HTMLDiff::css('diff-addedline').'>'.$this->_escape($this->_group).'</span>';
945
            elseif ($this->_tag == 'del')
946
                $this->_line .= '<span '.HTMLDiff::css('diff-deletedline').'><del>'.$this->_escape($this->_group).'</del></span>';
947
            else
948
                $this->_line .= $this->_escape($this->_group);
949
        }
950
        $this->_group = '';
951
        $this->_tag = $new_tag;
952
    }
953
954
    /**
955
     * @param string $new_tag
956
     */
957
    function _flushLine($new_tag) {
958
        $this->_flushGroup($new_tag);
959
        if ($this->_line != '')
960
            $this->_lines[] = $this->_line;
961
        $this->_line = '';
962
    }
963
964
    function addWords($words, $tag = '') {
965
        if ($tag != $this->_tag)
966
            $this->_flushGroup($tag);
967
968
        foreach ($words as $word) {
969
            // new-line should only come as first char of word.
970
            if ($word == '')
971
                continue;
972
            if ($word[0] == "\n") {
973
                $this->_group .= NBSP;
974
                $this->_flushLine($tag);
975
                $word = substr($word, 1);
976
            }
977
            assert(!strstr($word, "\n"));
978
            $this->_group .= $word;
979
        }
980
    }
981
982
    function getLines() {
983
        $this->_flushLine('~done');
984
        return $this->_lines;
985
    }
986
987
    function _escape($str){
988
        return hsc($str);
989
    }
990
}
991
992
class WordLevelDiff extends MappedDiff {
993
994
    function __construct($orig_lines, $closing_lines) {
995
        list ($orig_words, $orig_stripped) = $this->_split($orig_lines);
996
        list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
997
998
        parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped);
999
    }
1000
1001
    function _split($lines) {
1002
        if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu',
1003
             implode("\n", $lines), $m)) {
1004
            return array(array(''), array(''));
1005
        }
1006
        return array($m[0], $m[1]);
1007
    }
1008
1009
    function orig() {
1010
        $orig = new _HWLDF_WordAccumulator;
1011
1012
        foreach ($this->edits as $edit) {
1013
            if ($edit->type == 'copy')
1014
                $orig->addWords($edit->orig);
1015
            elseif ($edit->orig)
1016
                $orig->addWords($edit->orig, 'mark');
1017
        }
1018
        return $orig->getLines();
1019
    }
1020
1021
    function closing() {
1022
        $closing = new _HWLDF_WordAccumulator;
1023
1024
        foreach ($this->edits as $edit) {
1025
            if ($edit->type == 'copy')
1026
                $closing->addWords($edit->closing);
1027
            elseif ($edit->closing)
1028
                $closing->addWords($edit->closing, 'mark');
1029
        }
1030
        return $closing->getLines();
1031
    }
1032
}
1033
1034
class InlineWordLevelDiff extends MappedDiff {
1035
1036
    function __construct($orig_lines, $closing_lines) {
1037
        list ($orig_words, $orig_stripped) = $this->_split($orig_lines);
1038
        list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
1039
1040
        parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped);
1041
    }
1042
1043
    function _split($lines) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1044
        if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu',
1045
             implode("\n", $lines), $m)) {
1046
            return array(array(''), array(''));
1047
        }
1048
        return array($m[0], $m[1]);
1049
    }
1050
1051
    function inline() {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1052
        $orig = new _HWLDF_WordAccumulator;
1053
        foreach ($this->edits as $edit) {
1054
            if ($edit->type == 'copy')
1055
                $orig->addWords($edit->closing);
1056
            elseif ($edit->type == 'change'){
1057
                $orig->addWords($edit->orig, 'del');
1058
                $orig->addWords($edit->closing, 'add');
1059
            } elseif ($edit->type == 'delete')
1060
                $orig->addWords($edit->orig, 'del');
1061
            elseif ($edit->type == 'add')
1062
                $orig->addWords($edit->closing, 'add');
1063
            elseif ($edit->orig)
1064
                $orig->addWords($edit->orig, 'del');
1065
        }
1066
        return $orig->getLines();
1067
    }
1068
}
1069
1070
/**
1071
 * "Unified" diff formatter.
1072
 *
1073
 * This class formats the diff in classic "unified diff" format.
1074
 *
1075
 * NOTE: output is plain text and unsafe for use in HTML without escaping.
1076
 */
1077
class UnifiedDiffFormatter extends DiffFormatter {
1078
1079
    function __construct($context_lines = 4) {
1080
        $this->leading_context_lines = $context_lines;
1081
        $this->trailing_context_lines = $context_lines;
1082
    }
1083
1084
    function _block_header($xbeg, $xlen, $ybeg, $ylen) {
1085
        if ($xlen != 1)
1086
            $xbeg .= "," . $xlen;
1087
        if ($ylen != 1)
1088
            $ybeg .= "," . $ylen;
1089
        return "@@ -$xbeg +$ybeg @@\n";
1090
    }
1091
1092
    function _added($lines) {
1093
        $this->_lines($lines, "+");
1094
    }
1095
    function _deleted($lines) {
1096
        $this->_lines($lines, "-");
1097
    }
1098
    function _changed($orig, $final) {
1099
        $this->_deleted($orig);
1100
        $this->_added($final);
1101
    }
1102
}
1103
1104
/**
1105
 *  Wikipedia Table style diff formatter.
1106
 *
1107
 */
1108
class TableDiffFormatter extends DiffFormatter {
1109
    var $colspan = 2;
1110
1111
    function __construct() {
1112
        $this->leading_context_lines = 2;
1113
        $this->trailing_context_lines = 2;
1114
    }
1115
1116
    /**
1117
     * @param Diff $diff
1118
     * @return string
1119
     */
1120
    function format($diff) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1121
        // Preserve whitespaces by converting some to non-breaking spaces.
1122
        // Do not convert all of them to allow word-wrap.
1123
        $val = parent::format($diff);
1124
        $val = str_replace('  ','&#160; ', $val);
1125
        $val = preg_replace('/ (?=<)|(?<=[ >]) /', '&#160;', $val);
1126
        return $val;
1127
    }
1128
1129
    function _pre($text){
1130
        $text = htmlspecialchars($text);
1131
        return $text;
1132
    }
1133
1134
    function _block_header($xbeg, $xlen, $ybeg, $ylen) {
1135
        global $lang;
1136
        $l1 = $lang['line'].' '.$xbeg;
1137
        $l2 = $lang['line'].' '.$ybeg;
1138
        $r = '<tr><td '.HTMLDiff::css('diff-blockheader').' colspan="'.$this->colspan.'">'.$l1.":</td>\n".
1139
             '<td '.HTMLDiff::css('diff-blockheader').' colspan="'.$this->colspan.'">'.$l2.":</td>\n".
1140
             "</tr>\n";
1141
        return $r;
1142
    }
1143
1144
    function _start_block($header) {
1145
        print($header);
1146
    }
1147
1148
    function _end_block() {
1149
    }
1150
1151
    function _lines($lines, $prefix=' ', $color="white") {
1152
    }
1153
1154
    function addedLine($line,$escaped=false) {
1155
        if (!$escaped){
1156
            $line = $this->_escape($line);
1157
        }
1158
        return '<td '.HTMLDiff::css('diff-lineheader').'>+</td>'.
1159
               '<td '.HTMLDiff::css('diff-addedline').'>' .  $line.'</td>';
1160
    }
1161
1162
    function deletedLine($line,$escaped=false) {
1163
        if (!$escaped){
1164
            $line = $this->_escape($line);
1165
        }
1166
        return '<td '.HTMLDiff::css('diff-lineheader').'>-</td>'.
1167
               '<td '.HTMLDiff::css('diff-deletedline').'>' .  $line.'</td>';
1168
    }
1169
1170
    function emptyLine() {
1171
        return '<td colspan="'.$this->colspan.'">&#160;</td>';
1172
    }
1173
1174
    function contextLine($line) {
1175
        return '<td '.HTMLDiff::css('diff-lineheader').'>&#160;</td>'.
1176
               '<td '.HTMLDiff::css('diff-context').'>'.$this->_escape($line).'</td>';
1177
    }
1178
1179
    function _added($lines) {
1180
        $this->_addedLines($lines,false);
1181
    }
1182
1183
    function _addedLines($lines,$escaped=false){
1184
        foreach ($lines as $line) {
1185
            print('<tr>' . $this->emptyLine() . $this->addedLine($line,$escaped) . "</tr>\n");
1186
        }
1187
    }
1188
1189
    function _deleted($lines) {
1190
        foreach ($lines as $line) {
1191
            print('<tr>' . $this->deletedLine($line) . $this->emptyLine() . "</tr>\n");
1192
        }
1193
    }
1194
1195
    function _context($lines) {
1196
        foreach ($lines as $line) {
1197
            print('<tr>' . $this->contextLine($line) .  $this->contextLine($line) . "</tr>\n");
1198
        }
1199
    }
1200
1201
    function _changed($orig, $closing) {
1202
        $diff = new WordLevelDiff($orig, $closing);  // this escapes the diff data
1203
        $del = $diff->orig();
1204
        $add = $diff->closing();
1205
1206
        while ($line = array_shift($del)) {
1207
            $aline = array_shift($add);
1208
            print('<tr>' . $this->deletedLine($line,true) . $this->addedLine($aline,true) . "</tr>\n");
1209
        }
1210
        $this->_addedLines($add,true); # If any leftovers
1211
    }
1212
1213
    function _escape($str) {
1214
        return hsc($str);
1215
    }
1216
}
1217
1218
/**
1219
 *  Inline style diff formatter.
1220
 *
1221
 */
1222
class InlineDiffFormatter extends DiffFormatter {
1223
    var $colspan = 2;
1224
1225
    function __construct() {
1226
        $this->leading_context_lines = 2;
1227
        $this->trailing_context_lines = 2;
1228
    }
1229
1230
    /**
1231
     * @param Diff $diff
1232
     * @return string
1233
     */
1234
    function format($diff) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1235
        // Preserve whitespaces by converting some to non-breaking spaces.
1236
        // Do not convert all of them to allow word-wrap.
1237
        $val = parent::format($diff);
1238
        $val = str_replace('  ','&#160; ', $val);
1239
        $val = preg_replace('/ (?=<)|(?<=[ >]) /', '&#160;', $val);
1240
        return $val;
1241
    }
1242
1243
    function _pre($text){
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1244
        $text = htmlspecialchars($text);
1245
        return $text;
1246
    }
1247
1248
    function _block_header($xbeg, $xlen, $ybeg, $ylen) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1249
        global $lang;
1250
        if ($xlen != 1)
1251
            $xbeg .= "," . $xlen;
1252
        if ($ylen != 1)
1253
            $ybeg .= "," . $ylen;
1254
        $r = '<tr><td colspan="'.$this->colspan.'" '.HTMLDiff::css('diff-blockheader').'>@@ '.$lang['line']." -$xbeg +$ybeg @@";
1255
        $r .= ' <span '.HTMLDiff::css('diff-deletedline').'><del>'.$lang['deleted'].'</del></span>';
1256
        $r .= ' <span '.HTMLDiff::css('diff-addedline').'>'.$lang['created'].'</span>';
1257
        $r .= "</td></tr>\n";
1258
        return $r;
1259
    }
1260
1261
    function _start_block($header) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1262
        print($header."\n");
1263
    }
1264
1265
    function _end_block() {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1266
    }
1267
1268
    function _lines($lines, $prefix=' ', $color="white") {
0 ignored issues
show
The parameter $color is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1269
    }
1270
1271
    function _added($lines) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1272
        foreach ($lines as $line) {
1273
            print('<tr><td '.HTMLDiff::css('diff-lineheader').'>&#160;</td><td '.HTMLDiff::css('diff-addedline').'>'. $this->_escape($line) . "</td></tr>\n");
1274
        }
1275
    }
1276
1277
    function _deleted($lines) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1278
        foreach ($lines as $line) {
1279
            print('<tr><td '.HTMLDiff::css('diff-lineheader').'>&#160;</td><td '.HTMLDiff::css('diff-deletedline').'><del>' . $this->_escape($line) . "</del></td></tr>\n");
1280
        }
1281
    }
1282
1283
    function _context($lines) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1284
        foreach ($lines as $line) {
1285
            print('<tr><td '.HTMLDiff::css('diff-lineheader').'>&#160;</td><td '.HTMLDiff::css('diff-context').'>'. $this->_escape($line) ."</td></tr>\n");
1286
        }
1287
    }
1288
1289
    function _changed($orig, $closing) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1290
        $diff = new InlineWordLevelDiff($orig, $closing);  // this escapes the diff data
1291
        $add = $diff->inline();
1292
1293
        foreach ($add as $line)
1294
            print('<tr><td '.HTMLDiff::css('diff-lineheader').'>&#160;</td><td>'.$line."</td></tr>\n");
1295
    }
1296
1297
    function _escape($str) {
1298
        return hsc($str);
1299
    }
1300
}
1301
1302
/**
1303
 * A class for computing three way diffs.
1304
 *
1305
 * @author  Geoffrey T. Dairiki <[email protected]>
1306
 */
1307
class Diff3 extends Diff {
1308
1309
    /**
1310
     * Conflict counter.
1311
     *
1312
     * @var integer
1313
     */
1314
    var $_conflictingBlocks = 0;
1315
1316
    /**
1317
     * Computes diff between 3 sequences of strings.
1318
     *
1319
     * @param array $orig    The original lines to use.
1320
     * @param array $final1  The first version to compare to.
1321
     * @param array $final2  The second version to compare to.
1322
     */
1323
    function __construct($orig, $final1, $final2) {
1324
        $engine = new _DiffEngine();
1325
1326
        $this->_edits = $this->_diff3($engine->diff($orig, $final1),
1327
                                      $engine->diff($orig, $final2));
1328
    }
1329
1330
    /**
1331
     * Returns the merged lines
1332
     *
1333
     * @param string $label1  label for first version
1334
     * @param string $label2  label for second version
1335
     * @param string $label3  separator between versions
1336
     * @return array          lines of the merged text
1337
     */
1338
    function mergedOutput($label1='<<<<<<<',$label2='>>>>>>>',$label3='=======') {
1339
        $lines = array();
1340
        foreach ($this->_edits as $edit) {
1341
            if ($edit->isConflict()) {
1342
                /* FIXME: this should probably be moved somewhere else. */
1343
                $lines = array_merge($lines,
1344
                                     array($label1),
1345
                                     $edit->final1,
1346
                                     array($label3),
1347
                                     $edit->final2,
1348
                                     array($label2));
1349
                $this->_conflictingBlocks++;
1350
            } else {
1351
                $lines = array_merge($lines, $edit->merged());
1352
            }
1353
        }
1354
1355
        return $lines;
1356
    }
1357
1358
    /**
1359
     * @access private
1360
     *
1361
     * @param array $edits1
1362
     * @param array $edits2
1363
     *
1364
     * @return array
1365
     */
1366
    function _diff3($edits1, $edits2) {
1367
        $edits = array();
1368
        $bb = new _Diff3_BlockBuilder();
1369
1370
        $e1 = current($edits1);
1371
        $e2 = current($edits2);
1372
        while ($e1 || $e2) {
1373
            if ($e1 && $e2 && is_a($e1, '_DiffOp_copy') && is_a($e2, '_DiffOp_copy')) {
1374
                /* We have copy blocks from both diffs. This is the (only)
1375
                 * time we want to emit a diff3 copy block.  Flush current
1376
                 * diff3 diff block, if any. */
1377
                if ($edit = $bb->finish()) {
1378
                    $edits[] = $edit;
1379
                }
1380
1381
                $ncopy = min($e1->norig(), $e2->norig());
1382
                assert($ncopy > 0);
1383
                $edits[] = new _Diff3_Op_copy(array_slice($e1->orig, 0, $ncopy));
1384
1385
                if ($e1->norig() > $ncopy) {
1386
                    array_splice($e1->orig, 0, $ncopy);
1387
                    array_splice($e1->closing, 0, $ncopy);
1388
                } else {
1389
                    $e1 = next($edits1);
1390
                }
1391
1392
                if ($e2->norig() > $ncopy) {
1393
                    array_splice($e2->orig, 0, $ncopy);
1394
                    array_splice($e2->closing, 0, $ncopy);
1395
                } else {
1396
                    $e2 = next($edits2);
1397
                }
1398
            } else {
1399
                if ($e1 && $e2) {
1400
                    if ($e1->orig && $e2->orig) {
1401
                        $norig = min($e1->norig(), $e2->norig());
1402
                        $orig = array_splice($e1->orig, 0, $norig);
1403
                        array_splice($e2->orig, 0, $norig);
1404
                        $bb->input($orig);
1405
                    }
1406
1407
                    if (is_a($e1, '_DiffOp_copy')) {
1408
                        $bb->out1(array_splice($e1->closing, 0, $norig));
1409
                    }
1410
1411
                    if (is_a($e2, '_DiffOp_copy')) {
1412
                        $bb->out2(array_splice($e2->closing, 0, $norig));
1413
                    }
1414
                }
1415
1416
                if ($e1 && ! $e1->orig) {
1417
                    $bb->out1($e1->closing);
1418
                    $e1 = next($edits1);
1419
                }
1420
                if ($e2 && ! $e2->orig) {
1421
                    $bb->out2($e2->closing);
1422
                    $e2 = next($edits2);
1423
                }
1424
            }
1425
        }
1426
1427
        if ($edit = $bb->finish()) {
1428
            $edits[] = $edit;
1429
        }
1430
1431
        return $edits;
1432
    }
1433
}
1434
1435
/**
1436
 * @author  Geoffrey T. Dairiki <[email protected]>
1437
 *
1438
 * @access private
1439
 */
1440
class _Diff3_Op {
1441
1442
    function __construct($orig = false, $final1 = false, $final2 = false) {
1443
        $this->orig = $orig ? $orig : array();
1444
        $this->final1 = $final1 ? $final1 : array();
1445
        $this->final2 = $final2 ? $final2 : array();
1446
    }
1447
1448
    function merged() {
1449
        if (!isset($this->_merged)) {
1450
            if ($this->final1 === $this->final2) {
1451
                $this->_merged = &$this->final1;
1452
            } elseif ($this->final1 === $this->orig) {
1453
                $this->_merged = &$this->final2;
1454
            } elseif ($this->final2 === $this->orig) {
1455
                $this->_merged = &$this->final1;
1456
            } else {
1457
                $this->_merged = false;
1458
            }
1459
        }
1460
1461
        return $this->_merged;
1462
    }
1463
1464
    function isConflict() {
1465
        return $this->merged() === false;
1466
    }
1467
1468
}
1469
1470
/**
1471
 * @author  Geoffrey T. Dairiki <[email protected]>
1472
 *
1473
 * @access private
1474
 */
1475
class _Diff3_Op_copy extends _Diff3_Op {
1476
1477
    function __construct($lines = false) {
1478
        $this->orig = $lines ? $lines : array();
1479
        $this->final1 = &$this->orig;
1480
        $this->final2 = &$this->orig;
1481
    }
1482
1483
    function merged() {
1484
        return $this->orig;
1485
    }
1486
1487
    function isConflict() {
1488
        return false;
1489
    }
1490
}
1491
1492
/**
1493
 * @author  Geoffrey T. Dairiki <[email protected]>
1494
 *
1495
 * @access private
1496
 */
1497
class _Diff3_BlockBuilder {
1498
1499
    function __construct() {
1500
        $this->_init();
1501
    }
1502
1503
    function input($lines) {
1504
        if ($lines) {
1505
            $this->_append($this->orig, $lines);
1506
        }
1507
    }
1508
1509
    function out1($lines) {
1510
        if ($lines) {
1511
            $this->_append($this->final1, $lines);
1512
        }
1513
    }
1514
1515
    function out2($lines) {
1516
        if ($lines) {
1517
            $this->_append($this->final2, $lines);
1518
        }
1519
    }
1520
1521
    function isEmpty() {
1522
        return !$this->orig && !$this->final1 && !$this->final2;
1523
    }
1524
1525
    function finish() {
1526
        if ($this->isEmpty()) {
1527
            return false;
1528
        } else {
1529
            $edit = new _Diff3_Op($this->orig, $this->final1, $this->final2);
1530
            $this->_init();
1531
            return $edit;
1532
        }
1533
    }
1534
1535
    function _init() {
1536
        $this->orig = $this->final1 = $this->final2 = array();
1537
    }
1538
1539
    function _append(&$array, $lines) {
1540
        array_splice($array, sizeof($array), 0, $lines);
1541
    }
1542
}
1543
1544
//Setup VIM: ex: et ts=4 :
1545