WordPress 4.3
[autoinstalls/wordpress.git] / wp-includes / Text / Diff.php
1 <?php
2 /**
3  * General API for generating and formatting diffs - the differences between
4  * two sequences of strings.
5  *
6  * The original PHP version of this code was written by Geoffrey T. Dairiki
7  * <dairiki@dairiki.org>, and is used/adapted with his permission.
8  *
9  * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
10  * Copyright 2004-2010 The Horde Project (http://www.horde.org/)
11  *
12  * See the enclosed file COPYING for license information (LGPL). If you did
13  * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
14  *
15  * @package Text_Diff
16  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
17  */
18 class Text_Diff {
19
20     /**
21      * Array of changes.
22      *
23      * @var array
24      */
25     var $_edits;
26
27     /**
28      * Computes diffs between sequences of strings.
29      *
30      * @param string $engine     Name of the diffing engine to use.  'auto'
31      *                           will automatically select the best.
32      * @param array $params      Parameters to pass to the diffing engine.
33      *                           Normally an array of two arrays, each
34      *                           containing the lines from a file.
35      */
36     function __construct( $engine, $params )
37     {
38         // Backward compatibility workaround.
39         if (!is_string($engine)) {
40             $params = array($engine, $params);
41             $engine = 'auto';
42         }
43
44         if ($engine == 'auto') {
45             $engine = extension_loaded('xdiff') ? 'xdiff' : 'native';
46         } else {
47             $engine = basename($engine);
48         }
49
50         // WP #7391
51         require_once dirname(__FILE__).'/Diff/Engine/' . $engine . '.php';
52         $class = 'Text_Diff_Engine_' . $engine;
53         $diff_engine = new $class();
54
55         $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params);
56     }
57
58         /**
59          * PHP4 constructor.
60          */
61         public function Text_Diff( $engine, $params ) {
62                 self::__construct( $engine, $params );
63         }
64
65     /**
66      * Returns the array of differences.
67      */
68     function getDiff()
69     {
70         return $this->_edits;
71     }
72
73     /**
74      * returns the number of new (added) lines in a given diff.
75      *
76      * @since Text_Diff 1.1.0
77      *
78      * @return integer The number of new lines
79      */
80     function countAddedLines()
81     {
82         $count = 0;
83         foreach ($this->_edits as $edit) {
84             if (is_a($edit, 'Text_Diff_Op_add') ||
85                 is_a($edit, 'Text_Diff_Op_change')) {
86                 $count += $edit->nfinal();
87             }
88         }
89         return $count;
90     }
91
92     /**
93      * Returns the number of deleted (removed) lines in a given diff.
94      *
95      * @since Text_Diff 1.1.0
96      *
97      * @return integer The number of deleted lines
98      */
99     function countDeletedLines()
100     {
101         $count = 0;
102         foreach ($this->_edits as $edit) {
103             if (is_a($edit, 'Text_Diff_Op_delete') ||
104                 is_a($edit, 'Text_Diff_Op_change')) {
105                 $count += $edit->norig();
106             }
107         }
108         return $count;
109     }
110
111     /**
112      * Computes a reversed diff.
113      *
114      * Example:
115      * <code>
116      * $diff = new Text_Diff($lines1, $lines2);
117      * $rev = $diff->reverse();
118      * </code>
119      *
120      * @return Text_Diff  A Diff object representing the inverse of the
121      *                    original diff.  Note that we purposely don't return a
122      *                    reference here, since this essentially is a clone()
123      *                    method.
124      */
125     function reverse()
126     {
127         if (version_compare(zend_version(), '2', '>')) {
128             $rev = clone($this);
129         } else {
130             $rev = $this;
131         }
132         $rev->_edits = array();
133         foreach ($this->_edits as $edit) {
134             $rev->_edits[] = $edit->reverse();
135         }
136         return $rev;
137     }
138
139     /**
140      * Checks for an empty diff.
141      *
142      * @return boolean  True if two sequences were identical.
143      */
144     function isEmpty()
145     {
146         foreach ($this->_edits as $edit) {
147             if (!is_a($edit, 'Text_Diff_Op_copy')) {
148                 return false;
149             }
150         }
151         return true;
152     }
153
154     /**
155      * Computes the length of the Longest Common Subsequence (LCS).
156      *
157      * This is mostly for diagnostic purposes.
158      *
159      * @return integer  The length of the LCS.
160      */
161     function lcs()
162     {
163         $lcs = 0;
164         foreach ($this->_edits as $edit) {
165             if (is_a($edit, 'Text_Diff_Op_copy')) {
166                 $lcs += count($edit->orig);
167             }
168         }
169         return $lcs;
170     }
171
172     /**
173      * Gets the original set of lines.
174      *
175      * This reconstructs the $from_lines parameter passed to the constructor.
176      *
177      * @return array  The original sequence of strings.
178      */
179     function getOriginal()
180     {
181         $lines = array();
182         foreach ($this->_edits as $edit) {
183             if ($edit->orig) {
184                 array_splice($lines, count($lines), 0, $edit->orig);
185             }
186         }
187         return $lines;
188     }
189
190     /**
191      * Gets the final set of lines.
192      *
193      * This reconstructs the $to_lines parameter passed to the constructor.
194      *
195      * @return array  The sequence of strings.
196      */
197     function getFinal()
198     {
199         $lines = array();
200         foreach ($this->_edits as $edit) {
201             if ($edit->final) {
202                 array_splice($lines, count($lines), 0, $edit->final);
203             }
204         }
205         return $lines;
206     }
207
208     /**
209      * Removes trailing newlines from a line of text. This is meant to be used
210      * with array_walk().
211      *
212      * @param string $line  The line to trim.
213      * @param integer $key  The index of the line in the array. Not used.
214      */
215     static function trimNewlines(&$line, $key)
216     {
217         $line = str_replace(array("\n", "\r"), '', $line);
218     }
219
220     /**
221      * Determines the location of the system temporary directory.
222      *
223      * @static
224      *
225      * @access protected
226      *
227      * @return string  A directory name which can be used for temp files.
228      *                 Returns false if one could not be found.
229      */
230     function _getTempDir()
231     {
232         $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp',
233                                'c:\windows\temp', 'c:\winnt\temp');
234
235         /* Try PHP's upload_tmp_dir directive. */
236         $tmp = ini_get('upload_tmp_dir');
237
238         /* Otherwise, try to determine the TMPDIR environment variable. */
239         if (!strlen($tmp)) {
240             $tmp = getenv('TMPDIR');
241         }
242
243         /* If we still cannot determine a value, then cycle through a list of
244          * preset possibilities. */
245         while (!strlen($tmp) && count($tmp_locations)) {
246             $tmp_check = array_shift($tmp_locations);
247             if (@is_dir($tmp_check)) {
248                 $tmp = $tmp_check;
249             }
250         }
251
252         /* If it is still empty, we have failed, so return false; otherwise
253          * return the directory determined. */
254         return strlen($tmp) ? $tmp : false;
255     }
256
257     /**
258      * Checks a diff for validity.
259      *
260      * This is here only for debugging purposes.
261      */
262     function _check($from_lines, $to_lines)
263     {
264         if (serialize($from_lines) != serialize($this->getOriginal())) {
265             trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
266         }
267         if (serialize($to_lines) != serialize($this->getFinal())) {
268             trigger_error("Reconstructed final doesn't match", E_USER_ERROR);
269         }
270
271         $rev = $this->reverse();
272         if (serialize($to_lines) != serialize($rev->getOriginal())) {
273             trigger_error("Reversed original doesn't match", E_USER_ERROR);
274         }
275         if (serialize($from_lines) != serialize($rev->getFinal())) {
276             trigger_error("Reversed final doesn't match", E_USER_ERROR);
277         }
278
279         $prevtype = null;
280         foreach ($this->_edits as $edit) {
281             if ($prevtype == get_class($edit)) {
282                 trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
283             }
284             $prevtype = get_class($edit);
285         }
286
287         return true;
288     }
289
290 }
291
292 /**
293  * @package Text_Diff
294  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
295  */
296 class Text_MappedDiff extends Text_Diff {
297
298     /**
299      * Computes a diff between sequences of strings.
300      *
301      * This can be used to compute things like case-insensitve diffs, or diffs
302      * which ignore changes in white-space.
303      *
304      * @param array $from_lines         An array of strings.
305      * @param array $to_lines           An array of strings.
306      * @param array $mapped_from_lines  This array should have the same size
307      *                                  number of elements as $from_lines.  The
308      *                                  elements in $mapped_from_lines and
309      *                                  $mapped_to_lines are what is actually
310      *                                  compared when computing the diff.
311      * @param array $mapped_to_lines    This array should have the same number
312      *                                  of elements as $to_lines.
313      */
314     function __construct($from_lines, $to_lines,
315                              $mapped_from_lines, $mapped_to_lines)
316     {
317         assert(count($from_lines) == count($mapped_from_lines));
318         assert(count($to_lines) == count($mapped_to_lines));
319
320         parent::Text_Diff($mapped_from_lines, $mapped_to_lines);
321
322         $xi = $yi = 0;
323         for ($i = 0; $i < count($this->_edits); $i++) {
324             $orig = &$this->_edits[$i]->orig;
325             if (is_array($orig)) {
326                 $orig = array_slice($from_lines, $xi, count($orig));
327                 $xi += count($orig);
328             }
329
330             $final = &$this->_edits[$i]->final;
331             if (is_array($final)) {
332                 $final = array_slice($to_lines, $yi, count($final));
333                 $yi += count($final);
334             }
335         }
336     }
337
338         /**
339          * PHP4 constructor.
340          */
341         public function Text_MappedDiff( $from_lines, $to_lines,
342                              $mapped_from_lines, $mapped_to_lines ) {
343                 self::__construct( $from_lines, $to_lines,
344                              $mapped_from_lines, $mapped_to_lines );
345         }
346
347 }
348
349 /**
350  * @package Text_Diff
351  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
352  *
353  * @access private
354  */
355 class Text_Diff_Op {
356
357     var $orig;
358     var $final;
359
360     function &reverse()
361     {
362         trigger_error('Abstract method', E_USER_ERROR);
363     }
364
365     function norig()
366     {
367         return $this->orig ? count($this->orig) : 0;
368     }
369
370     function nfinal()
371     {
372         return $this->final ? count($this->final) : 0;
373     }
374
375 }
376
377 /**
378  * @package Text_Diff
379  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
380  *
381  * @access private
382  */
383 class Text_Diff_Op_copy extends Text_Diff_Op {
384
385         /**
386          * PHP5 constructor.
387          */
388     function __construct( $orig, $final = false )
389     {
390         if (!is_array($final)) {
391             $final = $orig;
392         }
393         $this->orig = $orig;
394         $this->final = $final;
395     }
396
397         /**
398          * PHP4 constructor.
399          */
400         public function Text_Diff_Op_copy( $orig, $final = false ) {
401                 self::__construct( $orig, $final );
402         }
403
404     function &reverse()
405     {
406         $reverse = new Text_Diff_Op_copy($this->final, $this->orig);
407         return $reverse;
408     }
409
410 }
411
412 /**
413  * @package Text_Diff
414  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
415  *
416  * @access private
417  */
418 class Text_Diff_Op_delete extends Text_Diff_Op {
419
420         /**
421          * PHP5 constructor.
422          */
423         function __construct( $lines )
424     {
425         $this->orig = $lines;
426         $this->final = false;
427     }
428
429         /**
430          * PHP4 constructor.
431          */
432         public function Text_Diff_Op_delete( $lines ) {
433                 self::__construct( $lines );
434         }
435
436     function &reverse()
437     {
438         $reverse = new Text_Diff_Op_add($this->orig);
439         return $reverse;
440     }
441
442 }
443
444 /**
445  * @package Text_Diff
446  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
447  *
448  * @access private
449  */
450 class Text_Diff_Op_add extends Text_Diff_Op {
451
452         /**
453          * PHP5 constructor.
454          */
455     function __construct( $lines )
456     {
457         $this->final = $lines;
458         $this->orig = false;
459     }
460
461         /**
462          * PHP4 constructor.
463          */
464         public function Text_Diff_Op_add( $lines ) {
465                 self::__construct( $lines );
466         }
467
468     function &reverse()
469     {
470         $reverse = new Text_Diff_Op_delete($this->final);
471         return $reverse;
472     }
473
474 }
475
476 /**
477  * @package Text_Diff
478  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
479  *
480  * @access private
481  */
482 class Text_Diff_Op_change extends Text_Diff_Op {
483
484         /**
485          * PHP5 constructor.
486          */
487     function __construct( $orig, $final )
488     {
489         $this->orig = $orig;
490         $this->final = $final;
491     }
492
493         /**
494          * PHP4 constructor.
495          */
496         public function Text_Diff_Op_change( $orig, $final ) {
497                 self::__construct( $orig, $final );
498         }
499
500     function &reverse()
501     {
502         $reverse = new Text_Diff_Op_change($this->final, $this->orig);
503         return $reverse;
504     }
505
506 }