85545b3969020fab5eec201dca12c3f9605acfe2
[yaffs-website] / vendor / caxy / php-htmldiff / lib / Caxy / HtmlDiff / AbstractDiff.php
1 <?php
2
3 namespace Caxy\HtmlDiff;
4
5 /**
6  * Class AbstractDiff.
7  */
8 abstract class AbstractDiff
9 {
10     /**
11      * @var array
12      *
13      * @deprecated since 0.1.0
14      */
15     public static $defaultSpecialCaseTags = array('strong', 'b', 'i', 'big', 'small', 'u', 'sub', 'sup', 'strike', 's', 'p');
16
17     /**
18      * @var array
19      *
20      * @deprecated since 0.1.0
21      */
22     public static $defaultSpecialCaseChars = array('.', ',', '(', ')', '\'');
23
24     /**
25      * @var bool
26      *
27      * @deprecated since 0.1.0
28      */
29     public static $defaultGroupDiffs = true;
30
31     /**
32      * @var HtmlDiffConfig
33      */
34     protected $config;
35
36     /**
37      * @var string
38      */
39     protected $content;
40
41     /**
42      * @var string
43      */
44     protected $oldText;
45
46     /**
47      * @var string
48      */
49     protected $newText;
50
51     /**
52      * @var array
53      */
54     protected $oldWords = array();
55
56     /**
57      * @var array
58      */
59     protected $newWords = array();
60
61     /**
62      * @var DiffCache[]
63      */
64     protected $diffCaches = array();
65
66     /**
67      * @var \HTMLPurifier
68      */
69     protected $purifier;
70
71     /**
72      * @var \HTMLPurifier_Config|null
73      */
74     protected $purifierConfig = null;
75
76     /**
77      * @see array_slice_cached();
78      * @var bool
79      */
80     protected $resetCache = false;
81
82     /**
83      * AbstractDiff constructor.
84      *
85      * @param string     $oldText
86      * @param string     $newText
87      * @param string     $encoding
88      * @param null|array $specialCaseTags
89      * @param null|bool  $groupDiffs
90      */
91     public function __construct($oldText, $newText, $encoding = 'UTF-8', $specialCaseTags = null, $groupDiffs = null)
92     {
93         mb_substitute_character(0x20);
94
95         $this->setConfig(HtmlDiffConfig::create()->setEncoding($encoding));
96
97         if ($specialCaseTags !== null) {
98             $this->config->setSpecialCaseTags($specialCaseTags);
99         }
100
101         if ($groupDiffs !== null) {
102             $this->config->setGroupDiffs($groupDiffs);
103         }
104
105         $this->oldText = $oldText;
106         $this->newText = $newText;
107         $this->content = '';
108     }
109
110     /**
111      * @return bool|string
112      */
113     abstract public function build();
114
115     /**
116      * Initializes HTMLPurifier with cache location.
117      *
118      * @param null|string $defaultPurifierSerializerCache
119      */
120     public function initPurifier($defaultPurifierSerializerCache = null)
121     {
122         if (null !== $this->purifierConfig) {
123             $HTMLPurifierConfig  = $this->purifierConfig;
124         } else {
125             $HTMLPurifierConfig = \HTMLPurifier_Config::createDefault();
126         }
127
128         // Cache.SerializerPath defaults to Null and sets
129         // the location to inside the vendor HTMLPurifier library
130         // under the DefinitionCache/Serializer folder.
131         if (!is_null($defaultPurifierSerializerCache)) {
132             $HTMLPurifierConfig->set('Cache.SerializerPath', $defaultPurifierSerializerCache);
133         }
134
135         $this->purifier = new \HTMLPurifier($HTMLPurifierConfig);
136     }
137
138     /**
139      * Prepare (purify) the HTML
140      *
141      * @return void
142      */
143     protected function prepare()
144     {
145         $this->initPurifier($this->config->getPurifierCacheLocation());
146
147         $this->oldText = $this->purifyHtml($this->oldText);
148         $this->newText = $this->purifyHtml($this->newText);
149     }
150
151     /**
152      * @return DiffCache|null
153      */
154     protected function getDiffCache()
155     {
156         if (!$this->hasDiffCache()) {
157             return null;
158         }
159
160         $hash = spl_object_hash($this->getConfig()->getCacheProvider());
161
162         if (!array_key_exists($hash, $this->diffCaches)) {
163             $this->diffCaches[$hash] = new DiffCache($this->getConfig()->getCacheProvider());
164         }
165
166         return $this->diffCaches[$hash];
167     }
168
169     /**
170      * @return bool
171      */
172     protected function hasDiffCache()
173     {
174         return null !== $this->getConfig()->getCacheProvider();
175     }
176
177     /**
178      * @return HtmlDiffConfig
179      */
180     public function getConfig()
181     {
182         return $this->config;
183     }
184
185     /**
186      * @param HtmlDiffConfig $config
187      *
188      * @return AbstractDiff
189      */
190     public function setConfig(HtmlDiffConfig $config)
191     {
192         $this->config = $config;
193
194         return $this;
195     }
196
197     /**
198      * @return int
199      *
200      * @deprecated since 0.1.0
201      */
202     public function getMatchThreshold()
203     {
204         return $this->config->getMatchThreshold();
205     }
206
207     /**
208      * @param int $matchThreshold
209      *
210      * @return AbstractDiff
211      *
212      * @deprecated since 0.1.0
213      */
214     public function setMatchThreshold($matchThreshold)
215     {
216         $this->config->setMatchThreshold($matchThreshold);
217
218         return $this;
219     }
220
221     /**
222      * @param array $chars
223      *
224      * @deprecated since 0.1.0
225      */
226     public function setSpecialCaseChars(array $chars)
227     {
228         $this->config->setSpecialCaseChars($chars);
229     }
230
231     /**
232      * @return array|null
233      *
234      * @deprecated since 0.1.0
235      */
236     public function getSpecialCaseChars()
237     {
238         return $this->config->getSpecialCaseChars();
239     }
240
241     /**
242      * @param string $char
243      *
244      * @deprecated since 0.1.0
245      */
246     public function addSpecialCaseChar($char)
247     {
248         $this->config->addSpecialCaseChar($char);
249     }
250
251     /**
252      * @param string $char
253      *
254      * @deprecated since 0.1.0
255      */
256     public function removeSpecialCaseChar($char)
257     {
258         $this->config->removeSpecialCaseChar($char);
259     }
260
261     /**
262      * @param array $tags
263      *
264      * @deprecated since 0.1.0
265      */
266     public function setSpecialCaseTags(array $tags = array())
267     {
268         $this->config->setSpecialCaseChars($tags);
269     }
270
271     /**
272      * @param string $tag
273      *
274      * @deprecated since 0.1.0
275      */
276     public function addSpecialCaseTag($tag)
277     {
278         $this->config->addSpecialCaseTag($tag);
279     }
280
281     /**
282      * @param string $tag
283      *
284      * @deprecated since 0.1.0
285      */
286     public function removeSpecialCaseTag($tag)
287     {
288         $this->config->removeSpecialCaseTag($tag);
289     }
290
291     /**
292      * @return array|null
293      *
294      * @deprecated since 0.1.0
295      */
296     public function getSpecialCaseTags()
297     {
298         return $this->config->getSpecialCaseTags();
299     }
300
301     /**
302      * @return string
303      */
304     public function getOldHtml()
305     {
306         return $this->oldText;
307     }
308
309     /**
310      * @return string
311      */
312     public function getNewHtml()
313     {
314         return $this->newText;
315     }
316
317     /**
318      * @return string
319      */
320     public function getDifference()
321     {
322         return $this->content;
323     }
324
325     /**
326      * Clears the diff content.
327      *
328      * @return void
329      */
330     public function clearContent()
331     {
332         $this->content = null;
333     }
334
335     /**
336      * @param bool $boolean
337      *
338      * @return $this
339      *
340      * @deprecated since 0.1.0
341      */
342     public function setGroupDiffs($boolean)
343     {
344         $this->config->setGroupDiffs($boolean);
345
346         return $this;
347     }
348
349     /**
350      * @return bool
351      *
352      * @deprecated since 0.1.0
353      */
354     public function isGroupDiffs()
355     {
356         return $this->config->isGroupDiffs();
357     }
358
359     /**
360      * @param \HTMLPurifier_Config $config
361      */
362     public function setHTMLPurifierConfig(\HTMLPurifier_Config $config)
363     {
364         $this->purifierConfig = $config;
365     }
366
367     /**
368      * @param string $tag
369      *
370      * @return string
371      */
372     protected function getOpeningTag($tag)
373     {
374         return '/<'.$tag.'[^>]*/i';
375     }
376
377     /**
378      * @param string $tag
379      *
380      * @return string
381      */
382     protected function getClosingTag($tag)
383     {
384         return '</'.$tag.'>';
385     }
386
387     /**
388      * @param string $str
389      * @param string $start
390      * @param string $end
391      *
392      * @return string
393      */
394     protected function getStringBetween($str, $start, $end)
395     {
396         $expStr = explode($start, $str, 2);
397         if (count($expStr) > 1) {
398             $expStr = explode($end, $expStr[ 1 ]);
399             if (count($expStr) > 1) {
400                 array_pop($expStr);
401
402                 return implode($end, $expStr);
403             }
404         }
405
406         return '';
407     }
408
409     /**
410      * @param string $html
411      *
412      * @return string
413      */
414     protected function purifyHtml($html)
415     {
416         if (class_exists('Tidy') && false) {
417             $config = array('output-xhtml' => true, 'indent' => false);
418             $tidy = new tidy();
419             $tidy->parseString($html, $config, 'utf8');
420             $html = (string) $tidy;
421
422             return $this->getStringBetween($html, '<body>');
423         }
424
425         return $this->purifier->purify($html);
426     }
427
428     protected function splitInputsToWords()
429     {
430         $this->setOldWords($this->convertHtmlToListOfWords($this->explode($this->oldText)));
431         $this->setNewWords($this->convertHtmlToListOfWords($this->explode($this->newText)));
432     }
433
434     /**
435      * @param array $oldWords
436      */
437     protected function setOldWords(array $oldWords)
438     {
439         $this->resetCache = true;
440         $this->oldWords   = $oldWords;
441     }
442
443     /**
444      * @param array $newWords
445      */
446     protected function setNewWords(array $newWords)
447     {
448         $this->resetCache = true;
449         $this->newWords   = $newWords;
450     }
451
452     /**
453      * @param string $text
454      *
455      * @return bool
456      */
457     protected function isPartOfWord($text)
458     {
459         return ctype_alnum(str_replace($this->config->getSpecialCaseChars(), '', $text));
460     }
461
462     /**
463      * @param array $characterString
464      *
465      * @return array
466      */
467     protected function convertHtmlToListOfWords($characterString)
468     {
469         $mode = 'character';
470         $current_word = '';
471         $words = array();
472         foreach ($characterString as $i => $character) {
473             switch ($mode) {
474                 case 'character':
475                 if ($this->isStartOfTag($character)) {
476                     if ($current_word != '') {
477                         $words[] = $current_word;
478                     }
479
480                     $current_word = '<';
481                     $mode = 'tag';
482                 } elseif (preg_match("/\s/", $character)) {
483                     if ($current_word !== '') {
484                         $words[] = $current_word;
485                     }
486                     $current_word = preg_replace('/\s+/S', ' ', $character);
487                     $mode = 'whitespace';
488                 } else {
489                     if (
490                         (ctype_alnum($character) && (strlen($current_word) == 0 || $this->isPartOfWord($current_word))) ||
491                         (in_array($character, $this->config->getSpecialCaseChars()) && isset($characterString[$i + 1]) && $this->isPartOfWord($characterString[$i + 1]))
492                     ) {
493                         $current_word .= $character;
494                     } else {
495                         $words[] = $current_word;
496                         $current_word = $character;
497                     }
498                 }
499                 break;
500                 case 'tag' :
501                 if ($this->isEndOfTag($character)) {
502                     $current_word .= '>';
503                     $words[] = $current_word;
504                     $current_word = '';
505
506                     if (!preg_match('[^\s]', $character)) {
507                         $mode = 'whitespace';
508                     } else {
509                         $mode = 'character';
510                     }
511                 } else {
512                     $current_word .= $character;
513                 }
514                 break;
515                 case 'whitespace':
516                 if ($this->isStartOfTag($character)) {
517                     if ($current_word !== '') {
518                         $words[] = $current_word;
519                     }
520                     $current_word = '<';
521                     $mode = 'tag';
522                 } elseif (preg_match("/\s/", $character)) {
523                     $current_word .= $character;
524                     $current_word = preg_replace('/\s+/S', ' ', $current_word);
525                 } else {
526                     if ($current_word != '') {
527                         $words[] = $current_word;
528                     }
529                     $current_word = $character;
530                     $mode = 'character';
531                 }
532                 break;
533                 default:
534                 break;
535             }
536         }
537         if ($current_word != '') {
538             $words[] = $current_word;
539         }
540
541         return $words;
542     }
543
544     /**
545      * @param string $val
546      *
547      * @return bool
548      */
549     protected function isStartOfTag($val)
550     {
551         return $val == '<';
552     }
553
554     /**
555      * @param string $val
556      *
557      * @return bool
558      */
559     protected function isEndOfTag($val)
560     {
561         return $val == '>';
562     }
563
564     /**
565      * @param string $value
566      *
567      * @return bool
568      */
569     protected function isWhiteSpace($value)
570     {
571         return !preg_match('[^\s]', $value);
572     }
573
574     /**
575      * @param string $value
576      *
577      * @return array
578      */
579     protected function explode($value)
580     {
581         // as suggested by @onassar
582         return preg_split('//u', $value);
583     }
584 }