Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 334
0.00% covered (danger)
0.00%
0 / 48
CRAP
0.00% covered (danger)
0.00%
0 / 11
diff
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 11
1892
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 get_diff
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 count_added_lines
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 count_deleted_lines
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 reverse
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 is_empty
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
182
 lcs
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 get_original
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 get_final
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 trim_newlines
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 _check
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
mapped_diff
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
diff_op
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 3
30
0.00% covered (danger)
0.00%
0 / 1
 reverse
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 norig
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 nfinal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
diff_op_copy
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 2
12
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 reverse
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
diff_op_delete
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 2
6
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 reverse
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
diff_op_add
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 2
6
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 reverse
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
diff_op_change
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 2
6
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 reverse
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
diff3
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 8
1722
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 get_num_conflicts
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 get_conflicts_content
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 merged_output
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 merged_new_output
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 merged_orig_output
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 get_conflicts
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 _diff3
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
462
diff3_op
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 6
5402
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
20
 merged
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 is_conflict
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 solve_prepare
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
182
 _compare_conflict_seq
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
72
 solve_conflict
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 1
1722
diff3_op_copy
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 3
20
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 merged
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_conflict
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
diff3_block_builder
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 8
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 input
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 out1
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 out2
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 is_empty
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 finish
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 _init
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 _append
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14/**
15* @ignore
16*/
17if (!defined('IN_PHPBB'))
18{
19    exit;
20}
21
22/**
23* Code from pear.php.net, Text_Diff-1.1.0 package
24* http://pear.php.net/package/Text_Diff/
25*
26* Modified by phpBB Limited to meet our coding standards
27* and being able to integrate into phpBB
28*
29* General API for generating and formatting diffs - the differences between
30* two sequences of strings.
31*
32* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
33* Copyright 2004-2008 The Horde Project (http://www.horde.org/)
34*
35* @package diff
36* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
37*/
38class diff
39{
40    /**
41    * Array of changes.
42    * @var array
43    */
44    var $_edits;
45
46    /**
47    * Computes diffs between sequences of strings.
48    *
49    * @param array|string    &$from_content    An array of strings. Typically these are lines from a file.
50    * @param array|string    &$to_content    An array of strings.
51    * @param bool    $preserve_cr    If true, \r is replaced by a new line in the diff output
52    */
53    function __construct(&$from_content, &$to_content, $preserve_cr = true)
54    {
55        $diff_engine = new diff_engine();
56        $this->_edits = $diff_engine->diff($from_content, $to_content, $preserve_cr);
57    }
58
59    /**
60    * Returns the array of differences.
61    */
62    function get_diff()
63    {
64        return $this->_edits;
65    }
66
67    /**
68    * returns the number of new (added) lines in a given diff.
69    *
70    * @since Text_Diff 1.1.0
71    *
72    * @return integer The number of new lines
73    */
74    function count_added_lines()
75    {
76        $count = 0;
77
78        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
79        {
80            $edit = $this->_edits[$i];
81
82            if (is_a($edit, 'diff_op_add') || is_a($edit, 'diff_op_change'))
83            {
84                $count += $edit->nfinal();
85            }
86        }
87        return $count;
88    }
89
90    /**
91    * Returns the number of deleted (removed) lines in a given diff.
92    *
93    * @since Text_Diff 1.1.0
94    *
95    * @return integer The number of deleted lines
96    */
97    function count_deleted_lines()
98    {
99        $count = 0;
100
101        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
102        {
103            $edit = $this->_edits[$i];
104
105            if (is_a($edit, 'diff_op_delete') || is_a($edit, 'diff_op_change'))
106            {
107                $count += $edit->norig();
108            }
109        }
110        return $count;
111    }
112
113    /**
114    * Computes a reversed diff.
115    *
116    * Example:
117    * <code>
118    * $diff = new diff($lines1, $lines2);
119    * $rev = $diff->reverse();
120    * </code>
121    *
122    * @return diff  A Diff object representing the inverse of the original diff.
123    *               Note that we purposely don't return a reference here, since
124    *               this essentially is a clone() method.
125    */
126    function reverse()
127    {
128        if (version_compare(zend_version(), '2', '>'))
129        {
130            $rev = clone($this);
131        }
132        else
133        {
134            $rev = $this;
135        }
136
137        $rev->_edits = array();
138
139        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
140        {
141            $edit = $this->_edits[$i];
142            $rev->_edits[] = $edit->reverse();
143        }
144
145        return $rev;
146    }
147
148    /**
149    * Checks for an empty diff.
150    *
151    * @return boolean  True if two sequences were identical.
152    */
153    function is_empty()
154    {
155        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
156        {
157            $edit = $this->_edits[$i];
158
159            // skip diff_op_copy
160            if (is_a($edit, 'diff_op_copy'))
161            {
162                continue;
163            }
164
165            if (is_a($edit, 'diff_op_delete') || is_a($edit, 'diff_op_add'))
166            {
167                $orig = $edit->orig;
168                $final = $edit->final;
169
170                // We can simplify one case where the array is usually supposed to be empty...
171                if (is_array($orig) && count($orig) == 1 && trim($orig[0]) === '')
172                {
173                    $orig = array();
174                }
175                if (is_array($final) && count($final) == 1 && trim($final[0]) === '')
176                {
177                    $final = array();
178                }
179
180                if (!$orig && !$final)
181                {
182                    continue;
183                }
184
185                return false;
186            }
187
188            return false;
189        }
190
191        return true;
192    }
193
194    /**
195    * Computes the length of the Longest Common Subsequence (LCS).
196    *
197    * This is mostly for diagnostic purposes.
198    *
199    * @return integer  The length of the LCS.
200    */
201    function lcs()
202    {
203        $lcs = 0;
204
205        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
206        {
207            $edit = $this->_edits[$i];
208
209            if (is_a($edit, 'diff_op_copy'))
210            {
211                $lcs += count($edit->orig);
212            }
213        }
214        return $lcs;
215    }
216
217    /**
218    * Gets the original set of lines.
219    *
220    * This reconstructs the $from_lines parameter passed to the constructor.
221    *
222    * @return array  The original sequence of strings.
223    */
224    function get_original()
225    {
226        $lines = array();
227
228        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
229        {
230            $edit = $this->_edits[$i];
231
232            if ($edit->orig)
233            {
234                array_splice($lines, count($lines), 0, $edit->orig);
235            }
236        }
237        return $lines;
238    }
239
240    /**
241    * Gets the final set of lines.
242    *
243    * This reconstructs the $to_lines parameter passed to the constructor.
244    *
245    * @return array  The sequence of strings.
246    */
247    function get_final()
248    {
249        $lines = array();
250
251        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
252        {
253            $edit = $this->_edits[$i];
254
255            if ($edit->final)
256            {
257                array_splice($lines, count($lines), 0, $edit->final);
258            }
259        }
260        return $lines;
261    }
262
263    /**
264    * Removes trailing newlines from a line of text. This is meant to be used with array_walk().
265    *
266    * @param string &$line  The line to trim.
267    * @param integer $key  The index of the line in the array. Not used.
268    */
269    function trim_newlines(&$line, $key)
270    {
271        $line = str_replace(array("\n", "\r"), '', $line);
272    }
273
274    /**
275    * Checks a diff for validity.
276    *
277    * This is here only for debugging purposes.
278    */
279    function _check($from_lines, $to_lines)
280    {
281        if (serialize($from_lines) != serialize($this->get_original()))
282        {
283            trigger_error("[diff] Reconstructed original doesn't match", E_USER_ERROR);
284        }
285
286        if (serialize($to_lines) != serialize($this->get_final()))
287        {
288            trigger_error("[diff] Reconstructed final doesn't match", E_USER_ERROR);
289        }
290
291        $rev = $this->reverse();
292
293        if (serialize($to_lines) != serialize($rev->get_original()))
294        {
295            trigger_error("[diff] Reversed original doesn't match", E_USER_ERROR);
296        }
297
298        if (serialize($from_lines) != serialize($rev->get_final()))
299        {
300            trigger_error("[diff] Reversed final doesn't match", E_USER_ERROR);
301        }
302
303        $prevtype = null;
304
305        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
306        {
307            $edit = $this->_edits[$i];
308
309            if ($prevtype == get_class($edit))
310            {
311                trigger_error("[diff] Edit sequence is non-optimal", E_USER_ERROR);
312            }
313            $prevtype = get_class($edit);
314        }
315
316        return true;
317    }
318}
319
320/**
321* @package diff
322* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
323*/
324class mapped_diff extends diff
325{
326    /**
327    * Computes a diff between sequences of strings.
328    *
329    * This can be used to compute things like case-insensitve diffs, or diffs
330    * which ignore changes in white-space.
331    *
332    * @param array $from_lines         An array of strings.
333    * @param array $to_lines           An array of strings.
334    * @param array $mapped_from_lines  This array should have the same size number of elements as $from_lines.
335    *                                  The elements in $mapped_from_lines and $mapped_to_lines are what is actually
336    *                                  compared when computing the diff.
337    * @param array $mapped_to_lines    This array should have the same number of elements as $to_lines.
338    */
339    function __construct(&$from_lines, &$to_lines, &$mapped_from_lines, &$mapped_to_lines)
340    {
341        if (count($from_lines) != count($mapped_from_lines) || count($to_lines) != count($mapped_to_lines))
342        {
343            return;
344        }
345
346        parent::__construct($mapped_from_lines, $mapped_to_lines);
347
348        $xi = $yi = 0;
349        for ($i = 0; $i < count($this->_edits); $i++)
350        {
351            $orig = &$this->_edits[$i]->orig;
352            if (is_array($orig))
353            {
354                $orig = array_slice($from_lines, $xi, count($orig));
355                $xi += count($orig);
356            }
357
358            $final = &$this->_edits[$i]->final;
359            if (is_array($final))
360            {
361                $final = array_slice($to_lines, $yi, count($final));
362                $yi += count($final);
363            }
364        }
365    }
366}
367
368/**
369* @package diff
370* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
371*
372* @access private
373*/
374class diff_op
375{
376    var $orig;
377    var $final;
378
379    function &reverse()
380    {
381        trigger_error('[diff] Abstract method', E_USER_ERROR);
382    }
383
384    function norig()
385    {
386        return ($this->orig) ? count($this->orig) : 0;
387    }
388
389    function nfinal()
390    {
391        return ($this->final) ? count($this->final) : 0;
392    }
393}
394
395/**
396* @package diff
397* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
398*
399* @access private
400*/
401class diff_op_copy extends diff_op
402{
403    function __construct($orig, $final = false)
404    {
405        if (!is_array($final))
406        {
407            $final = $orig;
408        }
409        $this->orig = $orig;
410        $this->final = $final;
411    }
412
413    function &reverse()
414    {
415        $reverse = new diff_op_copy($this->final, $this->orig);
416        return $reverse;
417    }
418}
419
420/**
421* @package diff
422* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
423*
424* @access private
425*/
426class diff_op_delete extends diff_op
427{
428    function __construct($lines)
429    {
430        $this->orig = $lines;
431        $this->final = false;
432    }
433
434    function &reverse()
435    {
436        $reverse = new diff_op_add($this->orig);
437        return $reverse;
438    }
439}
440
441/**
442* @package diff
443* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
444*
445* @access private
446*/
447class diff_op_add extends diff_op
448{
449    function __construct($lines)
450    {
451        $this->final = $lines;
452        $this->orig = false;
453    }
454
455    function &reverse()
456    {
457        $reverse = new diff_op_delete($this->final);
458        return $reverse;
459    }
460}
461
462/**
463* @package diff
464* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
465*
466* @access private
467*/
468class diff_op_change extends diff_op
469{
470    function __construct($orig, $final)
471    {
472        $this->orig = $orig;
473        $this->final = $final;
474    }
475
476    function &reverse()
477    {
478        $reverse = new diff_op_change($this->final, $this->orig);
479        return $reverse;
480    }
481}
482
483
484/**
485* A class for computing three way diffs.
486*
487* @package diff
488* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
489*/
490class diff3 extends diff
491{
492    /**
493    * Conflict counter.
494    * @var integer
495    */
496    var $_conflicting_blocks = 0;
497
498    /**
499    * Computes diff between 3 sequences of strings.
500    *
501    * @param array|string &$orig        The original lines to use.
502    * @param array|string &$final1        The first version to compare to.
503    * @param array|string &$final2        The second version to compare to.
504    * @param bool $preserve_cr    If true, \r\n and bare \r are replaced by a new line
505    *                            in the diff output
506    */
507    function __construct(&$orig, &$final1, &$final2, $preserve_cr = true)
508    {
509        $diff_engine = new diff_engine();
510
511        $diff_1 = $diff_engine->diff($orig, $final1, $preserve_cr);
512        $diff_2 = $diff_engine->diff($orig, $final2, $preserve_cr);
513
514        unset($diff_engine);
515
516        $this->_edits = $this->_diff3($diff_1, $diff_2);
517    }
518
519    /**
520    * Return number of conflicts
521    */
522    function get_num_conflicts()
523    {
524        $conflicts = 0;
525
526        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
527        {
528            $edit = $this->_edits[$i];
529
530            if ($edit->is_conflict())
531            {
532                $conflicts++;
533            }
534        }
535
536        return $conflicts;
537    }
538
539    /**
540    * Get conflicts content for download. This is generally a merged file, but preserving conflicts and adding explanations to it.
541    * A user could then go through this file, search for the conflicts and changes the code accordingly.
542    *
543    * @param string $label1 the cvs file version/label from the original set of lines
544    * @param string $label2 the cvs file version/label from the new set of lines
545    * @param string $label_sep the explanation between label1 and label2 - more of a helper for the user
546    *
547    * @return array the merged output
548    */
549    function get_conflicts_content($label1 = 'CURRENT_FILE', $label2 = 'NEW_FILE', $label_sep = 'DIFF_SEP_EXPLAIN')
550    {
551        global $user;
552
553        $label1 = (!empty($user->lang[$label1])) ? $user->lang[$label1] : $label1;
554        $label2 = (!empty($user->lang[$label2])) ? $user->lang[$label2] : $label2;
555        $label_sep = (!empty($user->lang[$label_sep])) ? $user->lang[$label_sep] : $label_sep;
556
557        $lines = array();
558
559        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
560        {
561            $edit = $this->_edits[$i];
562
563            if ($edit->is_conflict())
564            {
565                // Start conflict label
566                $label_start    = array('<<<<<<< ' . $label1);
567                $label_mid        = array('======= ' . $label_sep);
568                $label_end        = array('>>>>>>> ' . $label2);
569
570                $lines = array_merge($lines, $label_start, $edit->final1, $label_mid, $edit->final2, $label_end);
571                $this->_conflicting_blocks++;
572            }
573            else
574            {
575                $lines = array_merge($lines, $edit->merged());
576            }
577        }
578
579        return $lines;
580    }
581
582    /**
583    * Return merged output (used by the renderer)
584    *
585    * @return array the merged output
586    */
587    function merged_output()
588    {
589        return $this->get_conflicts_content();
590    }
591
592    /**
593    * Merge the output and use the new file code for conflicts
594    */
595    function merged_new_output()
596    {
597        $lines = array();
598
599        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
600        {
601            $edit = $this->_edits[$i];
602
603            if ($edit->is_conflict())
604            {
605                $lines = array_merge($lines, $edit->final2);
606            }
607            else
608            {
609                $lines = array_merge($lines, $edit->merged());
610            }
611        }
612
613        return $lines;
614    }
615
616    /**
617    * Merge the output and use the original file code for conflicts
618    */
619    function merged_orig_output()
620    {
621        $lines = array();
622
623        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
624        {
625            $edit = $this->_edits[$i];
626
627            if ($edit->is_conflict())
628            {
629                $lines = array_merge($lines, $edit->final1);
630            }
631            else
632            {
633                $lines = array_merge($lines, $edit->merged());
634            }
635        }
636
637        return $lines;
638    }
639
640    /**
641    * Get conflicting block(s)
642    */
643    function get_conflicts()
644    {
645        $conflicts = array();
646
647        for ($i = 0, $size = count($this->_edits); $i < $size; $i++)
648        {
649            $edit = $this->_edits[$i];
650
651            if ($edit->is_conflict())
652            {
653                $conflicts[] = array($edit->final1, $edit->final2);
654            }
655        }
656
657        return $conflicts;
658    }
659
660    /**
661    * @access private
662    */
663    function _diff3(&$edits1, &$edits2)
664    {
665        $edits = array();
666        $bb = new diff3_block_builder();
667
668        $e1 = current($edits1);
669        $e2 = current($edits2);
670
671        while ($e1 || $e2)
672        {
673            if ($e1 && $e2 && is_a($e1, 'diff_op_copy') && is_a($e2, 'diff_op_copy'))
674            {
675                // We have copy blocks from both diffs. This is the (only) time we want to emit a diff3 copy block.
676                // Flush current diff3 diff block, if any.
677                if ($edit = $bb->finish())
678                {
679                    $edits[] = $edit;
680                }
681
682                $ncopy = min($e1->norig(), $e2->norig());
683                $edits[] = new diff3_op_copy(array_slice($e1->orig, 0, $ncopy));
684
685                if ($e1->norig() > $ncopy)
686                {
687                    array_splice($e1->orig, 0, $ncopy);
688                    array_splice($e1->final, 0, $ncopy);
689                }
690                else
691                {
692                    $e1 = next($edits1);
693                }
694
695                if ($e2->norig() > $ncopy)
696                {
697                    array_splice($e2->orig, 0, $ncopy);
698                    array_splice($e2->final, 0, $ncopy);
699                }
700                else
701                {
702                    $e2 = next($edits2);
703                }
704            }
705            else
706            {
707                if ($e1 && $e2)
708                {
709                    if ($e1->orig && $e2->orig)
710                    {
711                        $norig = min($e1->norig(), $e2->norig());
712                        $orig = array_splice($e1->orig, 0, $norig);
713                        array_splice($e2->orig, 0, $norig);
714                        $bb->input($orig);
715                    }
716                    else
717                    {
718                        $norig = 0;
719                    }
720
721                    if (is_a($e1, 'diff_op_copy'))
722                    {
723                        $bb->out1(array_splice($e1->final, 0, $norig));
724                    }
725
726                    if (is_a($e2, 'diff_op_copy'))
727                    {
728                        $bb->out2(array_splice($e2->final, 0, $norig));
729                    }
730                }
731
732                if ($e1 && ! $e1->orig)
733                {
734                    $bb->out1($e1->final);
735                    $e1 = next($edits1);
736                }
737
738                if ($e2 && ! $e2->orig)
739                {
740                    $bb->out2($e2->final);
741                    $e2 = next($edits2);
742                }
743            }
744        }
745
746        if ($edit = $bb->finish())
747        {
748            $edits[] = $edit;
749        }
750
751        return $edits;
752    }
753}
754
755/**
756* @package diff
757* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
758*
759* @access private
760*/
761class diff3_op
762{
763    /**
764     * @var array|mixed
765     */
766    protected $orig;
767
768    /**
769     * @var array|mixed
770     */
771    protected $final1;
772
773    /**
774     * @var array|mixed
775     */
776    protected $final2;
777
778    /**
779     * @var false
780     */
781    protected $_merged;
782
783    function __construct($orig = false, $final1 = false, $final2 = false)
784    {
785        $this->orig = $orig ?: array();
786        $this->final1 = $final1 ?: array();
787        $this->final2 = $final2 ?: array();
788    }
789
790    function merged()
791    {
792        if (!isset($this->_merged))
793        {
794            // Prepare the arrays before we compare them. ;)
795            $this->solve_prepare();
796
797            if ($this->final1 === $this->final2)
798            {
799                $this->_merged = &$this->final1;
800            }
801            else if ($this->final1 === $this->orig)
802            {
803                $this->_merged = &$this->final2;
804            }
805            else if ($this->final2 === $this->orig)
806            {
807                $this->_merged = &$this->final1;
808            }
809            else
810            {
811                // The following tries to aggressively solve conflicts...
812                $this->_merged = false;
813                $this->solve_conflict();
814            }
815        }
816
817        return $this->_merged;
818    }
819
820    function is_conflict()
821    {
822        return ($this->merged() === false) ? true : false;
823    }
824
825    /**
826    * Function to prepare the arrays for comparing - we want to skip over newline changes
827    * @author acydburn
828    */
829    function solve_prepare()
830    {
831        // We can simplify one case where the array is usually supposed to be empty...
832        if (count($this->orig) == 1 && trim($this->orig[0]) === '') $this->orig = array();
833        if (count($this->final1) == 1 && trim($this->final1[0]) === '') $this->final1 = array();
834        if (count($this->final2) == 1 && trim($this->final2[0]) === '') $this->final2 = array();
835
836        // Now we only can have the case where the only difference between arrays are newlines, so compare all cases
837
838        // First, some strings we can compare...
839        $orig = $final1 = $final2 = '';
840
841        foreach ($this->orig as $null => $line) $orig .= trim($line);
842        foreach ($this->final1 as $null => $line) $final1 .= trim($line);
843        foreach ($this->final2 as $null => $line) $final2 .= trim($line);
844
845        // final1 === final2
846        if ($final1 === $final2)
847        {
848            // We preserve the part which will be used in the merge later
849            $this->final2 = $this->final1;
850        }
851        // final1 === orig
852        else if ($final1 === $orig)
853        {
854            // Here it does not really matter what we choose, but we will use the new code
855            $this->orig = $this->final1;
856        }
857        // final2 === orig
858        else if ($final2 === $orig)
859        {
860            // Here it does not really matter too (final1 will be used), but we will use the new code
861            $this->orig = $this->final2;
862        }
863    }
864
865    /**
866    * Find code portions from $orig in $final1 and use $final2 as merged instance if provided
867    * @author acydburn
868    */
869    function _compare_conflict_seq($orig, $final1, $final2 = false)
870    {
871        $result = array('merge_found' => false, 'merge' => array());
872
873        $_orig = &$this->$orig;
874        $_final1 = &$this->$final1;
875
876        // Ok, we basically search for $orig in $final1
877        $compare_seq = count($_orig);
878
879        // Go through the conflict code
880        for ($i = 0, $j = 0, $size = count($_final1); $i < $size; $i++, $j = $i)
881        {
882            $line = $_final1[$i];
883            $skip = 0;
884
885            for ($x = 0; $x < $compare_seq; $x++)
886            {
887                // Try to skip all matching lines
888                if (trim($line) === trim($_orig[$x]))
889                {
890                    $line = (++$j < $size) ? $_final1[$j] : $line;
891                    $skip++;
892                }
893            }
894
895            if ($skip === $compare_seq)
896            {
897                $result['merge_found'] = true;
898
899                if ($final2 !== false)
900                {
901                    $result['merge'] = array_merge($result['merge'], $this->$final2);
902                }
903                $i += ($skip - 1);
904            }
905            else if ($final2 !== false)
906            {
907                $result['merge'][] = $line;
908            }
909        }
910
911        return $result;
912    }
913
914    /**
915    * Tries to solve conflicts aggressively based on typical "assumptions"
916    * @author acydburn
917    */
918    function solve_conflict()
919    {
920        $this->_merged = false;
921
922        // CASE ONE: orig changed into final2, but modified/unknown code in final1.
923        // IF orig is found "as is" in final1 we replace the code directly in final1 and populate this as final2/merge
924        if (count($this->orig) && count($this->final2))
925        {
926            $result = $this->_compare_conflict_seq('orig', 'final1', 'final2');
927
928            if ($result['merge_found'])
929            {
930                $this->final2 = $result['merge'];
931                $this->_merged = &$this->final2;
932                return;
933            }
934
935            $result = $this->_compare_conflict_seq('final2', 'final1');
936
937            if ($result['merge_found'])
938            {
939                $this->_merged = &$this->final1;
940                return;
941            }
942
943            // Try to solve $Id$ issues. ;)
944            if (count($this->orig) == 1 && count($this->final1) == 1 && count($this->final2) == 1)
945            {
946                $match = '#^' . preg_quote('* @version $Id: ', '#') . '[a-z\._\- ]+[0-9]+ [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9\:Z]+ [a-z0-9_\- ]+\$$#';
947
948                if (preg_match($match, $this->orig[0]) && preg_match($match, $this->final1[0]) && preg_match($match, $this->final2[0]))
949                {
950                    $this->_merged = &$this->final2;
951                    return;
952                }
953            }
954
955            $second_run = false;
956
957            // Try to solve issues where the only reason why the above did not work is a newline being removed in the final1 code but exist in the orig/final2 code
958            if (trim($this->orig[0]) === '' && trim($this->final2[0]) === '')
959            {
960                unset($this->orig[0], $this->final2[0]);
961                $this->orig = array_values($this->orig);
962                $this->final2 = array_values($this->final2);
963
964                $second_run = true;
965            }
966
967            // The same is true for a line at the end. ;)
968            if (count($this->orig) && count($this->final2) && count($this->orig) === count($this->final2) && trim($this->orig[count($this->orig)-1]) === '' && trim($this->final2[count($this->final2)-1]) === '')
969            {
970                unset($this->orig[count($this->orig)-1], $this->final2[count($this->final2)-1]);
971                $this->orig = array_values($this->orig);
972                $this->final2 = array_values($this->final2);
973
974                $second_run = true;
975            }
976
977            if ($second_run)
978            {
979                $result = $this->_compare_conflict_seq('orig', 'final1', 'final2');
980
981                if ($result['merge_found'])
982                {
983                    $this->final2 = $result['merge'];
984                    $this->_merged = &$this->final2;
985                    return;
986                }
987
988                $result = $this->_compare_conflict_seq('final2', 'final1');
989
990                if ($result['merge_found'])
991                {
992                    $this->_merged = &$this->final1;
993                    return;
994                }
995            }
996
997            return;
998        }
999
1000        // CASE TWO: Added lines from orig to final2 but final1 had added lines too. Just merge them.
1001        if (!count($this->orig) && $this->final1 !== $this->final2 && count($this->final1) && count($this->final2))
1002        {
1003            $result = $this->_compare_conflict_seq('final2', 'final1');
1004
1005            if ($result['merge_found'])
1006            {
1007                $this->final2 = $this->final1;
1008                $this->_merged = &$this->final1;
1009            }
1010            else
1011            {
1012                $result = $this->_compare_conflict_seq('final1', 'final2');
1013
1014                if (!$result['merge_found'])
1015                {
1016                    $this->final2 = array_merge($this->final1, $this->final2);
1017                    $this->_merged = &$this->final2;
1018                }
1019                else
1020                {
1021                    $this->final2 = $this->final1;
1022                    $this->_merged = &$this->final1;
1023                }
1024            }
1025
1026            return;
1027        }
1028
1029        // CASE THREE: Removed lines (orig has the to-remove line(s), but final1 has additional lines which does not need to be removed). Just remove orig from final1 and then use final1 as final2/merge
1030        if (!count($this->final2) && count($this->orig) && count($this->final1) && $this->orig !== $this->final1)
1031        {
1032            $result = $this->_compare_conflict_seq('orig', 'final1');
1033
1034            if (!$result['merge_found'])
1035            {
1036                return;
1037            }
1038
1039            // First of all, try to find the code in orig in final1. ;)
1040            $compare_seq = count($this->orig);
1041            $begin = $end = -1;
1042            $j = 0;
1043
1044            for ($i = 0, $size = count($this->final1); $i < $size; $i++)
1045            {
1046                $line = $this->final1[$i];
1047
1048                if (trim($line) === trim($this->orig[$j]))
1049                {
1050                    // Mark begin
1051                    if ($begin === -1)
1052                    {
1053                        $begin = $i;
1054                    }
1055
1056                    // End is always $i, the last found line
1057                    $end = $i;
1058
1059                    if (isset($this->orig[$j+1]))
1060                    {
1061                        $j++;
1062                    }
1063                }
1064            }
1065
1066            if ($begin !== -1 && $begin + ($compare_seq - 1) == $end)
1067            {
1068                foreach ($this->final1 as $i => $line)
1069                {
1070                    if ($i < $begin || $i > $end)
1071                    {
1072                        $merged[] = $line;
1073                    }
1074                }
1075
1076                $this->final2 = $merged;
1077                $this->_merged = &$this->final2;
1078            }
1079
1080            return;
1081        }
1082    }
1083}
1084
1085/**
1086* @package diff
1087* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
1088*
1089* @access private
1090*/
1091class diff3_op_copy extends diff3_op
1092{
1093    function __construct($lines = false)
1094    {
1095        $this->orig = $lines ?: array();
1096        $this->final1 = &$this->orig;
1097        $this->final2 = &$this->orig;
1098    }
1099
1100    function merged()
1101    {
1102        return $this->orig;
1103    }
1104
1105    function is_conflict()
1106    {
1107        return false;
1108    }
1109}
1110
1111/**
1112* @package diff
1113* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
1114*
1115* @access private
1116*/
1117class diff3_block_builder
1118{
1119    /**
1120     * @var array
1121     */
1122    protected $orig;
1123
1124    /**
1125     * @var array
1126     */
1127    protected $final1;
1128
1129    /**
1130     * @var array
1131     */
1132    protected $final2;
1133
1134    function __construct()
1135    {
1136        $this->_init();
1137    }
1138
1139    function input($lines)
1140    {
1141        if ($lines)
1142        {
1143            $this->_append($this->orig, $lines);
1144        }
1145    }
1146
1147    function out1($lines)
1148    {
1149        if ($lines)
1150        {
1151            $this->_append($this->final1, $lines);
1152        }
1153    }
1154
1155    function out2($lines)
1156    {
1157        if ($lines)
1158        {
1159            $this->_append($this->final2, $lines);
1160        }
1161    }
1162
1163    function is_empty()
1164    {
1165        return !$this->orig && !$this->final1 && !$this->final2;
1166    }
1167
1168    function finish()
1169    {
1170        if ($this->is_empty())
1171        {
1172            return false;
1173        }
1174        else
1175        {
1176            $edit = new diff3_op($this->orig, $this->final1, $this->final2);
1177            $this->_init();
1178            return $edit;
1179        }
1180    }
1181
1182    function _init()
1183    {
1184        $this->orig = $this->final1 = $this->final2 = array();
1185    }
1186
1187    function _append(&$array, $lines)
1188    {
1189        array_splice($array, count($array), 0, $lines);
1190    }
1191}