1<?php
2/**
3 * Parses unified or context diffs output from eg. the diff utility.
4 *
5 * Example:
6 * <code>
7 * $patch = file_get_contents('example.patch');
8 * $diff = new Horde_Text_Diff('string', array($patch));
9 * $renderer = new Horde_Text_Diff_Renderer_inline();
10 * echo $renderer->render($diff);
11 * </code>
12 *
13 * Copyright 2005 Örjan Persson <o@42mm.org>
14 * Copyright 2005-2017 Horde LLC (http://www.horde.org/)
15 *
16 * See the enclosed file COPYING for license information (LGPL). If you did
17 * not receive this file, see http://www.horde.org/licenses/lgpl21.
18 *
19 * @author  Örjan Persson <o@42mm.org>
20 * @package Text_Diff
21 */
22
23// Disallow direct access to this file for security reasons
24if(!defined("IN_MYBB"))
25{
26	die("Direct initialization of this file is not allowed.<br /><br />Please make sure IN_MYBB is defined.");
27}
28
29class Horde_Text_Diff_Engine_String
30{
31    /**
32     * Parses a unified or context diff.
33     *
34     * First param contains the whole diff and the second can be used to force
35     * a specific diff type. If the second parameter is 'autodetect', the
36     * diff will be examined to find out which type of diff this is.
37     *
38     * @param string $diff  The diff content.
39     * @param string $mode  The diff mode of the content in $diff. One of
40     *                      'context', 'unified', or 'autodetect'.
41     *
42     * @return array  List of all diff operations.
43     * @throws Horde_Text_Diff_Exception
44     */
45    public function diff($diff, $mode = 'autodetect')
46    {
47        // Detect line breaks.
48        $lnbr = "\n";
49        if (strpos($diff, "\r\n") !== false) {
50            $lnbr = "\r\n";
51        } elseif (strpos($diff, "\r") !== false) {
52            $lnbr = "\r";
53        }
54
55        // Make sure we have a line break at the EOF.
56        if (substr($diff, -strlen($lnbr)) != $lnbr) {
57            $diff .= $lnbr;
58        }
59
60        if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') {
61            throw new Horde_Text_Diff_Exception('Type of diff is unsupported');
62        }
63
64        if ($mode == 'autodetect') {
65            $context = strpos($diff, '***');
66            $unified = strpos($diff, '---');
67            if ($context === $unified) {
68                throw new Horde_Text_Diff_Exception('Type of diff could not be detected');
69            } elseif ($context === false || $unified === false) {
70                $mode = $context !== false ? 'context' : 'unified';
71            } else {
72                $mode = $context < $unified ? 'context' : 'unified';
73            }
74        }
75
76        // Split by new line and remove the diff header, if there is one.
77        $diff = explode($lnbr, $diff);
78        if (($mode == 'context' && strpos($diff[0], '***') === 0) ||
79            ($mode == 'unified' && strpos($diff[0], '---') === 0)) {
80            array_shift($diff);
81            array_shift($diff);
82        }
83
84        if ($mode == 'context') {
85            return $this->parseContextDiff($diff);
86        } else {
87            return $this->parseUnifiedDiff($diff);
88        }
89    }
90
91    /**
92     * Parses an array containing the unified diff.
93     *
94     * @param array $diff  Array of lines.
95     *
96     * @return array  List of all diff operations.
97     */
98    public function parseUnifiedDiff($diff)
99    {
100        $edits = array();
101        $end = count($diff) - 1;
102        for ($i = 0; $i < $end;) {
103            $diff1 = array();
104            switch (substr($diff[$i], 0, 1)) {
105            case ' ':
106                do {
107                    $diff1[] = substr($diff[$i], 1);
108                } while (++$i < $end && substr($diff[$i], 0, 1) == ' ');
109                $edits[] = new Horde_Text_Diff_Op_Copy($diff1);
110                break;
111
112            case '+':
113                // get all new lines
114                do {
115                    $diff1[] = substr($diff[$i], 1);
116                } while (++$i < $end && substr($diff[$i], 0, 1) == '+');
117                $edits[] = new Horde_Text_Diff_Op_Add($diff1);
118                break;
119
120            case '-':
121                // get changed or removed lines
122                $diff2 = array();
123                do {
124                    $diff1[] = substr($diff[$i], 1);
125                } while (++$i < $end && substr($diff[$i], 0, 1) == '-');
126
127                while ($i < $end && substr($diff[$i], 0, 1) == '+') {
128                    $diff2[] = substr($diff[$i++], 1);
129                }
130                if (count($diff2) == 0) {
131                    $edits[] = new Horde_Text_Diff_Op_Delete($diff1);
132                } else {
133                    $edits[] = new Horde_Text_Diff_Op_Change($diff1, $diff2);
134                }
135                break;
136
137            default:
138                $i++;
139                break;
140            }
141        }
142
143        return $edits;
144    }
145
146    /**
147     * Parses an array containing the context diff.
148     *
149     * @param array $diff  Array of lines.
150     *
151     * @return array  List of all diff operations.
152     */
153    public function parseContextDiff(&$diff)
154    {
155        $edits = array();
156        $i = $max_i = $j = $max_j = 0;
157        $end = count($diff) - 1;
158        while ($i < $end && $j < $end) {
159            while ($i >= $max_i && $j >= $max_j) {
160                // Find the boundaries of the diff output of the two files
161                for ($i = $j;
162                     $i < $end && substr($diff[$i], 0, 3) == '***';
163                     $i++);
164                for ($max_i = $i;
165                     $max_i < $end && substr($diff[$max_i], 0, 3) != '---';
166                     $max_i++);
167                for ($j = $max_i;
168                     $j < $end && substr($diff[$j], 0, 3) == '---';
169                     $j++);
170                for ($max_j = $j;
171                     $max_j < $end && substr($diff[$max_j], 0, 3) != '***';
172                     $max_j++);
173            }
174
175            // find what hasn't been changed
176            $array = array();
177            while ($i < $max_i &&
178                   $j < $max_j &&
179                   strcmp($diff[$i], $diff[$j]) == 0) {
180                $array[] = substr($diff[$i], 2);
181                $i++;
182                $j++;
183            }
184
185            while ($i < $max_i && ($max_j-$j) <= 1) {
186                if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') {
187                    break;
188                }
189                $array[] = substr($diff[$i++], 2);
190            }
191
192            while ($j < $max_j && ($max_i-$i) <= 1) {
193                if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') {
194                    break;
195                }
196                $array[] = substr($diff[$j++], 2);
197            }
198            if (count($array) > 0) {
199                $edits[] = new Horde_Text_Diff_Op_Copy($array);
200            }
201
202            if ($i < $max_i) {
203                $diff1 = array();
204                switch (substr($diff[$i], 0, 1)) {
205                case '!':
206                    $diff2 = array();
207                    do {
208                        $diff1[] = substr($diff[$i], 2);
209                        if ($j < $max_j && substr($diff[$j], 0, 1) == '!') {
210                            $diff2[] = substr($diff[$j++], 2);
211                        }
212                    } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!');
213                    $edits[] = new Horde_Text_Diff_Op_Change($diff1, $diff2);
214                    break;
215
216                case '+':
217                    do {
218                        $diff1[] = substr($diff[$i], 2);
219                    } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+');
220                    $edits[] = new Horde_Text_Diff_Op_Add($diff1);
221                    break;
222
223                case '-':
224                    do {
225                        $diff1[] = substr($diff[$i], 2);
226                    } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-');
227                    $edits[] = new Horde_Text_Diff_Op_Delete($diff1);
228                    break;
229                }
230            }
231
232            if ($j < $max_j) {
233                $diff2 = array();
234                switch (substr($diff[$j], 0, 1)) {
235                case '+':
236                    do {
237                        $diff2[] = substr($diff[$j++], 2);
238                    } while ($j < $max_j && substr($diff[$j], 0, 1) == '+');
239                    $edits[] = new Horde_Text_Diff_Op_Add($diff2);
240                    break;
241
242                case '-':
243                    do {
244                        $diff2[] = substr($diff[$j++], 2);
245                    } while ($j < $max_j && substr($diff[$j], 0, 1) == '-');
246                    $edits[] = new Horde_Text_Diff_Op_Delete($diff2);
247                    break;
248                }
249            }
250        }
251
252        return $edits;
253    }
254}
255