Passed
Push — master ( 49e3d0...4782d7 )
by Josh
01:10
created

AbstractDiff::getDiffCache()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 14
ccs 0
cts 8
cp 0
rs 9.4285
cc 3
eloc 7
nc 3
nop 0
crap 12
1
<?php
2
3
namespace Caxy\HtmlDiff;
4
5
/**
6
 * Class AbstractDiff
7
 * @package Caxy\HtmlDiff
8
 */
9
abstract class AbstractDiff
10
{
11
    /**
12
     * @var array
13
     *
14
     * @deprecated since 0.1.0
15
     */
16
    public static $defaultSpecialCaseTags = array('strong', 'b', 'i', 'big', 'small', 'u', 'sub', 'sup', 'strike', 's', 'p');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 125 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
17
    /**
18
     * @var array
19
     *
20
     * @deprecated since 0.1.0
21
     */
22
    public static $defaultSpecialCaseChars = array('.', ',', '(', ')', '\'');
23
    /**
24
     * @var bool
25
     *
26
     * @deprecated since 0.1.0
27
     */
28
    public static $defaultGroupDiffs = true;
29
30
    /**
31
     * @var HtmlDiffConfig
32
     */
33
    protected $config;
34
35
    /**
36
     * @var string
37
     */
38
    protected $content;
39
    /**
40
     * @var string
41
     */
42
    protected $oldText;
43
    /**
44
     * @var string
45
     */
46
    protected $newText;
47
    /**
48
     * @var array
49
     */
50
    protected $oldWords = array();
51
    /**
52
     * @var array
53
     */
54
    protected $newWords = array();
55
56
    /**
57
     * @var DiffCache[]
58
     */
59
    private $diffCaches = array();
60
61
    /**
62
     * AbstractDiff constructor.
63
     *
64
     * @param string     $oldText
65
     * @param string     $newText
66
     * @param string     $encoding
67
     * @param null|array $specialCaseTags
68
     * @param null|bool  $groupDiffs
69
     */
70 11
    public function __construct($oldText, $newText, $encoding = 'UTF-8', $specialCaseTags = null, $groupDiffs = null)
71
    {
72 11
        mb_substitute_character(0x20);
73
74 11
        $this->config = HtmlDiffConfig::create()->setEncoding($encoding);
75
76 11
        if ($specialCaseTags !== null) {
77 11
            $this->config->setSpecialCaseTags($specialCaseTags);
78 11
        }
79
80 11
        if ($groupDiffs !== null) {
81
            $this->config->setGroupDiffs($groupDiffs);
82
        }
83
84 11
        $this->oldText = $this->purifyHtml(trim($oldText));
85 11
        $this->newText = $this->purifyHtml(trim($newText));
86 11
        $this->content = '';
87 11
    }
88
89
    /**
90
     * @return bool|string
91
     */
92
    abstract public function build();
93
94
    /**
95
     * @return DiffCache|null
96
     */
97
    protected function getDiffCache()
98
    {
99
        if (!$this->hasDiffCache()) {
100
            return null;
101
        }
102
103
        $hash = spl_object_hash($this->getConfig()->getCacheProvider());
104
105
        if (!array_key_exists($hash, $this->diffCaches)) {
106
            $this->diffCaches[$hash] = new DiffCache($this->getConfig()->getCacheProvider());
0 ignored issues
show
Bug introduced by
It seems like $this->getConfig()->getCacheProvider() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
107
        }
108
109
        return $this->diffCaches[$hash];
110
    }
111
112
    /**
113
     * @return bool
114
     */
115 11
    protected function hasDiffCache()
116
    {
117 11
        return null !== $this->getConfig()->getCacheProvider();
118
    }
119
120
    /**
121
     * @return HtmlDiffConfig
122
     */
123 11
    public function getConfig()
124
    {
125 11
        return $this->config;
126
    }
127
128
    /**
129
     * @param HtmlDiffConfig $config
130
     *
131
     * @return AbstractDiff
132
     */
133 7
    public function setConfig(HtmlDiffConfig $config)
134
    {
135 7
        $this->config = $config;
136
137 7
        return $this;
138
    }
139
140
    /**
141
     * @return int
142
     *
143
     * @deprecated since 0.1.0
144
     */
145
    public function getMatchThreshold()
146
    {
147
        return $this->config->getMatchThreshold();
148
    }
149
150
    /**
151
     * @param int $matchThreshold
152
     *
153
     * @return AbstractDiff
154
     *
155
     * @deprecated since 0.1.0
156
     */
157
    public function setMatchThreshold($matchThreshold)
158
    {
159
        $this->config->setMatchThreshold($matchThreshold);
160
161
        return $this;
162
    }
163
164
    /**
165
     * @param array $chars
166
     *
167
     * @deprecated since 0.1.0
168
     */
169
    public function setSpecialCaseChars(array $chars)
170
    {
171
        $this->config->setSpecialCaseChars($chars);
172
    }
173
174
    /**
175
     * @return array|null
176
     *
177
     * @deprecated since 0.1.0
178
     */
179
    public function getSpecialCaseChars()
180
    {
181
        return $this->config->getSpecialCaseChars();
182
    }
183
184
    /**
185
     * @param string $char
186
     *
187
     * @deprecated since 0.1.0
188
     */
189
    public function addSpecialCaseChar($char)
190
    {
191
        $this->config->addSpecialCaseChar($char);
192
    }
193
194
    /**
195
     * @param string $char
196
     *
197
     * @deprecated since 0.1.0
198
     */
199
    public function removeSpecialCaseChar($char)
200
    {
201
        $this->config->removeSpecialCaseChar($char);
202
    }
203
204
    /**
205
     * @param array $tags
206
     *
207
     * @deprecated since 0.1.0
208
     */
209
    public function setSpecialCaseTags(array $tags = array())
210
    {
211
        $this->config->setSpecialCaseChars($tags);
212
    }
213
214
    /**
215
     * @param string $tag
216
     *
217
     * @deprecated since 0.1.0
218
     */
219
    public function addSpecialCaseTag($tag)
220
    {
221
        $this->config->addSpecialCaseTag($tag);
222
    }
223
224
    /**
225
     * @param string $tag
226
     *
227
     * @deprecated since 0.1.0
228
     */
229
    public function removeSpecialCaseTag($tag)
230
    {
231
        $this->config->removeSpecialCaseTag($tag);
232
    }
233
234
    /**
235
     * @return array|null
236
     *
237
     * @deprecated since 0.1.0
238
     */
239
    public function getSpecialCaseTags()
240
    {
241
        return $this->config->getSpecialCaseTags();
242
    }
243
244
    /**
245
     * @return string
246
     */
247
    public function getOldHtml()
248
    {
249
        return $this->oldText;
250
    }
251
252
    /**
253
     * @return string
254
     */
255
    public function getNewHtml()
256
    {
257
        return $this->newText;
258
    }
259
260
    /**
261
     * @return string
262
     */
263
    public function getDifference()
264
    {
265
        return $this->content;
266
    }
267
268
    /**
269
     * @param bool $boolean
270
     *
271
     * @return $this
272
     *
273
     * @deprecated since 0.1.0
274
     */
275
    public function setGroupDiffs($boolean)
276
    {
277
        $this->config->setGroupDiffs($boolean);
278
279
        return $this;
280
    }
281
282
    /**
283
     * @return bool
284
     *
285
     * @deprecated since 0.1.0
286
     */
287
    public function isGroupDiffs()
288
    {
289
        return $this->config->isGroupDiffs();
290
    }
291
292
    /**
293
     * @param string $tag
294
     *
295
     * @return string
296
     */
297
    protected function getOpeningTag($tag)
298
    {
299
        return "/<".$tag."[^>]*/i";
300
    }
301
302
    /**
303
     * @param string $tag
304
     *
305
     * @return string
306
     */
307
    protected function getClosingTag($tag)
308
    {
309
        return "</".$tag.">";
310
    }
311
312
    /**
313
     * @param string $str
314
     * @param string $start
315
     * @param string $end
316
     *
317
     * @return string
318
     */
319
    protected function getStringBetween($str, $start, $end)
320
    {
321
        $expStr = explode( $start, $str, 2 );
322
        if ( count( $expStr ) > 1 ) {
323
            $expStr = explode( $end, $expStr[ 1 ] );
324
            if ( count( $expStr ) > 1 ) {
325
                array_pop( $expStr );
326
327
                return implode( $end, $expStr );
328
            }
329
        }
330
331
        return '';
332
    }
333
334
    /**
335
     * @param string $html
336
     *
337
     * @return string
338
     */
339 11
    protected function purifyHtml($html)
340
    {
341 11
        if ( class_exists( 'Tidy' ) && false ) {
342
            $config = array( 'output-xhtml'   => true, 'indent' => false );
343
            $tidy = new tidy();
344
            $tidy->parseString( $html, $config, 'utf8' );
345
            $html = (string) $tidy;
346
347
            return $this->getStringBetween( $html, '<body>' );
0 ignored issues
show
Bug introduced by
The call to getStringBetween() misses a required argument $end.

This check looks for function calls that miss required arguments.

Loading history...
348
        }
349
350 11
        return $html;
351
    }
352
353 11
    protected function splitInputsToWords()
354
    {
355 11
        $this->oldWords = $this->convertHtmlToListOfWords( $this->explode( $this->oldText ) );
356 11
        $this->newWords = $this->convertHtmlToListOfWords( $this->explode( $this->newText ) );
357 11
    }
358
359
    /**
360
     * @param string $text
361
     *
362
     * @return bool
363
     */
364 11
    protected function isPartOfWord($text)
365
    {
366 11
        return ctype_alnum(str_replace($this->config->getSpecialCaseChars(), '', $text));
367
    }
368
369
    /**
370
     * @param array $characterString
371
     *
372
     * @return array
373
     */
374 11
    protected function convertHtmlToListOfWords($characterString)
375
    {
376 11
        $mode = 'character';
377 11
        $current_word = '';
378 11
        $words = array();
379 11
        foreach ($characterString as $i => $character) {
380
            switch ($mode) {
381 11
                case 'character':
382 11
                if ( $this->isStartOfTag( $character ) ) {
383 11
                    if ($current_word != '') {
384 10
                        $words[] = $current_word;
385 10
                    }
386 11
                    $current_word = "<";
387 11
                    $mode = 'tag';
388 11
                } elseif (preg_match("/\s/", $character)) {
389 11
                    if ($current_word !== '') {
390 11
                        $words[] = $current_word;
391 11
                    }
392 11
                    $current_word = preg_replace('/\s+/S', ' ', $character);
393 11
                    $mode = 'whitespace';
394 11
                } else {
395
                    if (
396 11
                        (ctype_alnum($character) && (strlen($current_word) == 0 || $this->isPartOfWord($current_word))) ||
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
397 11
                        (in_array($character, $this->config->getSpecialCaseChars()) && isset($characterString[$i+1]) && $this->isPartOfWord($characterString[$i+1]))
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 164 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
398 11
                    ) {
399 11
                        $current_word .= $character;
400 11
                    } else {
401 11
                        $words[] = $current_word;
402 11
                        $current_word = $character;
403
                    }
404
                }
405 11
                break;
406 11
                case 'tag' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
407 11
                if ( $this->isEndOfTag( $character ) ) {
408 11
                    $current_word .= ">";
409 11
                    $words[] = $current_word;
410 11
                    $current_word = "";
411
412 11
                    if ( !preg_match('[^\s]', $character ) ) {
413 11
                        $mode = 'whitespace';
414 11
                    } else {
415
                        $mode = 'character';
416
                    }
417 11
                } else {
418 11
                    $current_word .= $character;
419
                }
420 11
                break;
421 11
                case 'whitespace':
422 11
                if ( $this->isStartOfTag( $character ) ) {
423 11
                    if ($current_word !== '') {
424 11
                        $words[] = $current_word;
425 11
                    }
426 11
                    $current_word = "<";
427 11
                    $mode = 'tag';
428 11
                } elseif ( preg_match( "/\s/", $character ) ) {
429 10
                    $current_word .= $character;
430 10
                    $current_word = preg_replace('/\s+/S', ' ', $current_word);
431 10
                } else {
432 11
                    if ($current_word != '') {
433 11
                        $words[] = $current_word;
434 11
                    }
435 11
                    $current_word = $character;
436 11
                    $mode = 'character';
437
                }
438 11
                break;
439
                default:
440
                break;
441
            }
442 11
        }
443 11
        if ($current_word != '') {
444
            $words[] = $current_word;
445
        }
446
447 11
        return $words;
448
    }
449
450
    /**
451
     * @param string $val
452
     *
453
     * @return bool
454
     */
455 11
    protected function isStartOfTag($val)
456
    {
457 11
        return $val == "<";
458
    }
459
460
    /**
461
     * @param string $val
462
     *
463
     * @return bool
464
     */
465 11
    protected function isEndOfTag($val)
466
    {
467 11
        return $val == ">";
468
    }
469
470
    /**
471
     * @param string $value
472
     *
473
     * @return bool
474
     */
475
    protected function isWhiteSpace($value)
476
    {
477
        return !preg_match( '[^\s]', $value );
478
    }
479
480
    /**
481
     * @param string $value
482
     *
483
     * @return array
484
     */
485 11
    protected function explode($value)
486
    {
487
        // as suggested by @onassar
488 11
        return preg_split( '//u', $value );
489
    }
490
}
491