1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8//this script may only be included - so its better to die if called directly.
9if (strpos($_SERVER["SCRIPT_NAME"], basename(__FILE__)) !== false) {
10	header("location: index.php");
11	exit;
12}
13
14require_once(__DIR__ . "/Diff.php");
15require_once(__DIR__ . "/Renderer.php");
16
17/* @brief modif tiki for the renderer lib	*/
18class Tiki_Text_Diff_Renderer extends Text_Diff_Renderer
19{
20	protected function _lines($lines, $prefix = '', $suffix = '', $type = '')
21	{
22//ADD $suffix
23		foreach ($lines as $line) {
24			echo "$prefix$line$suffix\n";
25		}
26	}
27	public function render($diff, $singleEdit = false)
28	{
29		$x0 = $y0 = 0;
30		$xi = $yi = 1;
31		$block = false;
32		$context = [];
33
34		$nlead = $this->_leading_context_lines;
35		$ntrail = $this->_trailing_context_lines;
36
37		$this->_startDiff();
38
39		if (! $singleEdit) {
40			$diff = $diff->getDiff();
41		}
42
43		foreach ($diff as $edit) {
44			if (is_a($edit, 'Text_Diff_Op_copy')) {
45				if (is_array($block)) {
46					if (count($edit->orig) <= $nlead + $ntrail) {
47						$block[] = $edit;
48					} else {
49						if ($ntrail) {
50							$context = array_slice($edit->orig, 0, $ntrail);
51							$block[] = new Text_Diff_Op_copy($context);
52						}
53						$this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block);
54						$block = false;
55					}
56				}
57				$context = $edit->orig;
58			} else {
59				if (! is_array($block)) {
60					//BUG if compare on all the length:                    $context = array_slice($context, count($context) - $nlead);
61					$context = array_slice($context, -$nlead, $nlead);
62					$x0 = $xi - count($context);
63					$y0 = $yi - count($context);
64					$block = [];
65					if ($context) {
66						$block[] = new Text_Diff_Op_copy($context);
67					}
68				}
69				$block[] = $edit;
70			}
71
72			if ($edit->orig) {
73				$xi += count($edit->orig);
74			}
75			if ($edit->final) {
76				$yi += count($edit->final);
77			}
78		}
79
80		if (is_array($block)) {
81			$this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block);
82		}
83
84		return $this->_endDiff();
85	}
86}
87
88function diff2($page1, $page2, $type = 'sidediff')
89{
90	global $tikilib, $prefs;
91	if ($type == 'htmldiff') {
92		//$search = "#(<[^>]+>|\s*[^\s<]+\s*|</[^>]+>)#";
93		$search = "#(<[^>]+>|[,\"':\s]+|[^\s,\"':<]+|</[^>]+>)#";
94		preg_match_all($search, $page1, $out, PREG_PATTERN_ORDER);
95		$page1 = $out[0];
96		preg_match_all($search, $page2, $out, PREG_PATTERN_ORDER);
97		$page2 = $out[0];
98	} else {
99		$page1 = explode("\n", $page1);
100		$page2 = explode("\n", $page2);
101	}
102	$z = new Text_Diff($page1, $page2);
103	if ($z->isEmpty()) {
104		$html = '';
105	} else {
106		$context = 2;
107		$words = 1;
108		if (strstr($type, "-")) {
109			list($type,$opt) = explode("-", $type, 2);
110			if (strstr($opt, "full")) {
111				$context = count($page1);
112			}
113			if (strstr($opt, "char")) {
114				$words = 0;
115			}
116		}
117
118		if ($type == 'unidiff') {
119			require_once('renderer_unified.php');
120			$renderer = new Text_Diff_Renderer_unified($context);
121		} elseif ($type == 'inlinediff') {
122			require_once('renderer_inline.php');
123			$renderer = new Text_Diff_Renderer_inline($context, $words);
124		} elseif ($type == 'sidediff') {
125			require_once('renderer_sidebyside.php');
126			$renderer = new Text_Diff_Renderer_sidebyside($context, $words);
127		} elseif ($type == 'bytes' && $prefs['feature_actionlog_bytes'] == 'y') {
128			require_once('renderer_bytes.php');
129			$renderer = new Text_Diff_Renderer_bytes();
130		} elseif ($type == 'htmldiff') {
131			require_once('renderer_htmldiff.php');
132			$renderer = new Text_Diff_Renderer_htmldiff($context, $words);
133		} else {
134			return "";
135		}
136		$html = $renderer->render($z);
137	}
138	return $html;
139}
140
141/* @brief compute the characters differences between a list of lines
142 * @param $orig array list lines in the original version
143 * @param $final array the same lines in the final version
144 * @param int $words
145 * @param string $function
146 * @return array
147 */
148function diffChar($orig, $final, $words = 0, $function = 'character')
149{
150	$glue = strpos($function, 'inline') !== false ? "<br />" : "\n";
151	if ($words) {
152		preg_match_all("/\w+\s+(?=\w)|\w+|\W/u", implode($glue, $orig), $matches);
153		$line1 = $matches[0];
154		preg_match_all("/\w+\s+(?=\w)|\w+|\W/u", implode($glue, $final), $matches);
155		$line2 = $matches[0];
156	} else {
157		$line1 = preg_split('//u', implode($glue, $orig), -1, PREG_SPLIT_NO_EMPTY);
158		$line2 = preg_split('//u', implode($glue, $final), -1, PREG_SPLIT_NO_EMPTY);
159	}
160	$z = new Text_Diff($line1, $line2);
161	if ($z->isEmpty()) {
162		return [$orig[0], $final[0]];
163	}
164//echo "<pre>";print_r($z);echo "</pre>";
165
166	compileRendererClass($function);
167	  $new = "Text_Diff_Renderer_$function";
168	$renderer = new $new(count($line1));
169	return $renderer->render($z);
170}
171
172function compileRendererClass($function)
173{
174	/*
175	 * The various subclasses of Text_Diff_Renderer have methods whose signatures are incompatible
176	 * with those of their parents. This raises some warnings which don't matter in production settings.
177	 *
178	 * But when running phpunit tests, this causes some failures, because we have configured phpunit
179	 * to report warnings as failures.
180	 *
181	 * Making the methods compatible with each other would be very involved, and might introduce some
182	 * actual bugs. So instead, temporarily disable warning reporting, just for the compilation of
183	 * this file.
184	 */
185	global $old_error_reporting_level;
186	if (defined('TIKI_IN_TEST')) {
187		$old_error_reporting_level = error_reporting(E_ERROR | E_PARSE);
188	}
189
190	require_once("renderer_$function.php");
191
192	if (defined('TIKI_IN_TEST')) {
193		error_reporting($old_error_reporting_level);
194	}
195}
196
197/**
198 * Find mentions
199 *
200 * @param $lines
201 * @param $state
202 * @return array
203 */
204function findMentions($lines, $state)
205{
206	$allMatches = [] ;
207
208	if (isset($lines) && is_array($lines)) {
209		foreach (array_filter($lines) as $line) {
210			preg_match_all("/(?:^|\s)@(\w+)/i", $line, $matches);
211			foreach ($matches[0] as $match) {
212				$allMatches[] = [
213					'state' => $state,
214					'mention' => trim($match)
215				];
216			}
217		}
218	}
219
220	return $allMatches;
221}
222
223/**
224 * Find mentions on change content
225 *
226 * @param $edit
227 * @return array
228 */
229function findMentionsOnChange($edit)
230{
231	$allMatches = [];
232
233	if ((isset($edit->orig) && is_array($edit->orig)) && (isset($edit->final) && is_array($edit->final))) {
234		if (empty($edit->orig[0])) {
235			$mentions = findMentions($edit->final, 'new');
236			foreach ($mentions as $m) {
237				$allMatches[] = $m;
238			}
239		} else {
240			require_once('renderer_inline.php');
241			$renderer = new Text_Diff_Renderer_inline(1);
242			$html = $renderer->render([$edit], true);
243
244			// remove unnecessary content
245			$html = preg_replace("#<tr class=\"diffheader\">(.*?)</tr>#", "", $html);
246			$html = preg_replace("#<span class='diffinldel'>(.*?)</span>#", "", $html);
247			$html = str_replace(["<tr class='diffbody'>", "</tr>", "<td colspan='3'>", "</td>"], "", $html);
248			$html = str_replace(["<span class='diffadded'>", "</span>"], "<ins>", $html);
249			$finalContent = explode('<ins>', $html);
250
251			$index = 0;
252			foreach ($finalContent as $key => $value) {
253				if (($index % 2) == 1) {
254					// new mention
255					$charToAdd = '';
256					$previousMention = $finalContent[$key - 1];
257					if (! empty($previousMention)) {
258						$lastChar = substr($previousMention, -1);
259						if ($lastChar == '@') {
260							$charToAdd = '@';
261						}
262					}
263
264					$mentions = findMentions([$charToAdd . $value], 'new');
265				} else {
266					// old mention
267					$mentions = findMentions([$value], 'old');
268				}
269
270				foreach ($mentions as $m) {
271					$allMatches[] = $m;
272				}
273				$index++;
274			}
275		}
276	}
277
278	return $allMatches;
279}
280