Passed
Push — master ( a1ab72...5c013f )
by Josh
58s
created

HtmlDiff::diffElements()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

Changes 9
Bugs 1 Features 0
Metric Value
c 9
b 1
f 0
dl 0
loc 21
ccs 15
cts 15
cp 1
rs 8.7624
cc 5
eloc 13
nc 6
nop 3
crap 5
1
<?php
2
3
namespace Caxy\HtmlDiff;
4
5
class HtmlDiff extends AbstractDiff
6
{
7
    protected $wordIndices;
8
    protected $insertSpaceInReplace = false;
9
    protected $newIsolatedDiffTags;
10
    protected $oldIsolatedDiffTags;
11
    protected $isolatedDiffTags = array (
12
        'ol' => '[[REPLACE_ORDERED_LIST]]',
13
        'ul' => '[[REPLACE_UNORDERED_LIST]]',
14
        'sub' => '[[REPLACE_SUB_SCRIPT]]',
15
        'sup' => '[[REPLACE_SUPER_SCRIPT]]',
16
        'dl' => '[[REPLACE_DEFINITION_LIST]]',
17
        'table' => '[[REPLACE_TABLE]]',
18
        'a' => '[[REPLACE_A]]',
19
    );
20
21
    /**
22
     * @param  boolean  $boolean
23
     * @return HtmlDiff
24
     */
25
    public function setInsertSpaceInReplace($boolean)
26
    {
27
        $this->insertSpaceInReplace = $boolean;
28
29
        return $this;
30
    }
31
32
    /**
33
     * @return boolean
34
     */
35
    public function getInsertSpaceInReplace()
36
    {
37
        return $this->insertSpaceInReplace;
38
    }
39
40 11
    public function build()
41
    {
42 11
        $this->splitInputsToWords();
43 11
        $this->replaceIsolatedDiffTags();
44 11
        $this->indexNewWords();
45
46 11
        $operations = $this->operations();
47 11
        foreach ($operations as $item) {
48 11
            $this->performOperation( $item );
49 11
        }
50
51 11
        return $this->content;
52
    }
53
54 11
    protected function indexNewWords()
55
    {
56 11
        $this->wordIndices = array();
57 11
        foreach ($this->newWords as $i => $word) {
58 11
            if ( $this->isTag( $word ) ) {
59 9
                $word = $this->stripTagAttributes( $word );
60 9
            }
61 11
            if ( isset( $this->wordIndices[ $word ] ) ) {
62 11
                $this->wordIndices[ $word ][] = $i;
63 11
            } else {
64 11
                $this->wordIndices[ $word ] = array( $i );
65
            }
66 11
        }
67 11
    }
68
69 11
    protected function replaceIsolatedDiffTags()
70
    {
71 11
        $this->oldIsolatedDiffTags = $this->createIsolatedDiffTagPlaceholders($this->oldWords);
72 11
        $this->newIsolatedDiffTags = $this->createIsolatedDiffTagPlaceholders($this->newWords);
73 11
    }
74
75 11
    protected function createIsolatedDiffTagPlaceholders(&$words)
76
    {
77 11
        $openIsolatedDiffTags = 0;
78 11
        $isolatedDiffTagIndicies = array();
79 11
        $isolatedDiffTagStart = 0;
80 11
        $currentIsolatedDiffTag = null;
81 11
        foreach ($words as $index => $word) {
82 11
            $openIsolatedDiffTag = $this->isOpeningIsolatedDiffTag($word, $currentIsolatedDiffTag);
83 11
            if ($openIsolatedDiffTag) {
84 10
                if ($openIsolatedDiffTags === 0) {
85 10
                    $isolatedDiffTagStart = $index;
86 10
                }
87 10
                $openIsolatedDiffTags++;
88 10
                $currentIsolatedDiffTag = $openIsolatedDiffTag;
89 11
            } elseif ($openIsolatedDiffTags > 0 && $this->isClosingIsolatedDiffTag($word, $currentIsolatedDiffTag)) {
90 10
                $openIsolatedDiffTags--;
91 10
                if ($openIsolatedDiffTags == 0) {
92 10
                    $isolatedDiffTagIndicies[] = array ('start' => $isolatedDiffTagStart, 'length' => $index - $isolatedDiffTagStart + 1, 'tagType' => $currentIsolatedDiffTag);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 176 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...
93 10
                    $currentIsolatedDiffTag = null;
94 10
                }
95 10
            }
96 11
        }
97 11
        $isolatedDiffTagScript = array();
98 11
        $offset = 0;
99 11
        foreach ($isolatedDiffTagIndicies as $isolatedDiffTagIndex) {
100 10
            $start = $isolatedDiffTagIndex['start'] - $offset;
101 10
            $placeholderString = $this->isolatedDiffTags[$isolatedDiffTagIndex['tagType']];
102 10
            $isolatedDiffTagScript[$start] = array_splice($words, $start, $isolatedDiffTagIndex['length'], $placeholderString);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 127 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...
103 10
            $offset += $isolatedDiffTagIndex['length'] - 1;
104 11
        }
105
106 11
        return $isolatedDiffTagScript;
107
108
    }
109
110 11 View Code Duplication
    protected function isOpeningIsolatedDiffTag($item, $currentIsolatedDiffTag = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
    {
112 11
        $tagsToMatch = $currentIsolatedDiffTag !== null ? array($currentIsolatedDiffTag => $this->isolatedDiffTags[$currentIsolatedDiffTag]) : $this->isolatedDiffTags;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 167 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...
113 11
        foreach ($tagsToMatch as $key => $value) {
114 11
            if (preg_match("#<".$key."[^>]*>\\s*#iU", $item)) {
115 10
                return $key;
116
            }
117 11
        }
118
119 11
        return false;
120
    }
121
122 10 View Code Duplication
    protected function isClosingIsolatedDiffTag($item, $currentIsolatedDiffTag = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
123
    {
124 10
        $tagsToMatch = $currentIsolatedDiffTag !== null ? array($currentIsolatedDiffTag => $this->isolatedDiffTags[$currentIsolatedDiffTag]) : $this->isolatedDiffTags;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 167 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...
125 10
        foreach ($tagsToMatch as $key => $value) {
126 10
            if (preg_match("#</".$key."[^>]*>\\s*#iU", $item)) {
127 10
                return $key;
128
            }
129 10
        }
130
131 10
        return false;
132
    }
133
134 11
    protected function performOperation($operation)
135
    {
136 11
        switch ($operation->action) {
137 11
            case 'equal' :
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...
138 11
            $this->processEqualOperation( $operation );
139 11
            break;
140 9
            case 'delete' :
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...
141 6
            $this->processDeleteOperation( $operation, "diffdel" );
142 6
            break;
143 9
            case 'insert' :
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...
144 9
            $this->processInsertOperation( $operation, "diffins");
145 9
            break;
146 7
            case 'replace':
147 7
            $this->processReplaceOperation( $operation );
148 7
            break;
149
            default:
150
            break;
151 11
        }
152 11
    }
153
154 7
    protected function processReplaceOperation($operation)
155
    {
156 7
        $this->processDeleteOperation( $operation, "diffmod" );
157 7
        $this->processInsertOperation( $operation, "diffmod" );
158 7
    }
159
160 9 View Code Duplication
    protected function processInsertOperation($operation, $cssClass)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
161
    {
162 9
        $text = array();
163 9
        foreach ($this->newWords as $pos => $s) {
164 9
            if ($pos >= $operation->startInNew && $pos < $operation->endInNew) {
165 9
                if (in_array($s, $this->isolatedDiffTags) && isset($this->newIsolatedDiffTags[$pos])) {
166 2
                    foreach ($this->newIsolatedDiffTags[$pos] as $word) {
167 2
                        $text[] = $word;
168 2
                    }
169 2
                } else {
170 9
                    $text[] = $s;
171
                }
172 9
            }
173 9
        }
174 9
        $this->insertTag( "ins", $cssClass, $text );
175 9
    }
176
177 9 View Code Duplication
    protected function processDeleteOperation($operation, $cssClass)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
    {
179 9
        $text = array();
180 9
        foreach ($this->oldWords as $pos => $s) {
181 9
            if ($pos >= $operation->startInOld && $pos < $operation->endInOld) {
182 9
                if (in_array($s, $this->isolatedDiffTags) && isset($this->oldIsolatedDiffTags[$pos])) {
183 5
                    foreach ($this->oldIsolatedDiffTags[$pos] as $word) {
184 5
                        $text[] = $word;
185 5
                    }
186 5
                } else {
187 9
                    $text[] = $s;
188
                }
189 9
            }
190 9
        }
191 9
        $this->insertTag( "del", $cssClass, $text );
192 9
    }
193
194
    /**
195
     * @param Operation $operation
196
     * @param int       $pos
197
     * @param string    $placeholder
198
     * @param bool      $stripWrappingTags
199
     *
200
     * @return string
201
     */
202 8
    protected function diffIsolatedPlaceholder($operation, $pos, $placeholder, $stripWrappingTags = true)
203
    {
204 8
        $oldText = implode("", $this->findIsolatedDiffTagsInOld($operation, $pos));
205 8
        $newText = implode("", $this->newIsolatedDiffTags[$pos]);
206
207 8
        if ($this->isListPlaceholder($placeholder)) {
208 7
            return $this->diffList($oldText, $newText);
209 2
        } elseif ($this->isLinkPlaceholder($placeholder)) {
210 1
            return $this->diffLinks($oldText, $newText);
211
        }
212
213 1
        return $this->diffElements($oldText, $newText, $stripWrappingTags);
214
    }
215
216 2
    protected function diffElements($oldText, $newText, $stripWrappingTags = true)
217
    {
218 2
        $wrapStart = '';
219 2
        $wrapEnd = '';
220
221 2
        if ($stripWrappingTags) {
222 2
            $pattern = '/(^<[^>]+>)|(<\/[^>]+>$)/i';
223 2
            $matches = array();
224
225 2
            if (preg_match_all($pattern, $newText, $matches)) {
226 2
                $wrapStart = isset($matches[0][0]) ? $matches[0][0] : '';
227 2
                $wrapEnd = isset($matches[0][1]) ? $matches[0][1] : '';
228 2
            }
229 2
            $oldText = preg_replace($pattern, '', $oldText);
230 2
            $newText = preg_replace($pattern, '', $newText);
231 2
        }
232
233 2
        $diff = new HtmlDiff($oldText, $newText, $this->encoding, $this->specialCaseTags, $this->groupDiffs);
234
235 2
        return $wrapStart . $diff->build() . $wrapEnd;
236
    }
237
238 7
    protected function diffList($oldText, $newText)
239
    {
240 7
        $diff = new ListDiffNew($oldText, $newText, $this->encoding, $this->specialCaseTags, $this->groupDiffs);
241 7
        $diff->setMatchThreshold($this->matchThreshold);
242
243 7
        return $diff->build();
244
    }
245
246
    /**
247
     * @param string $oldText
248
     * @param string $newText
249
     *
250
     * @return string
251
     */
252 1
    protected function diffLinks($oldText, $newText)
253
    {
254 1
        $oldHref = $this->getAttributeFromTag($oldText, 'href');
255 1
        $newHref = $this->getAttributeFromTag($newText, 'href');
256
257 1
        if ($oldHref != $newHref) {
258 1
            return sprintf(
259 1
                '%s%s',
260 1
                $this->wrapText($oldText, 'del', 'diffmod diff-href'),
261 1
                $this->wrapText($newText, 'ins', 'diffmod diff-href')
262 1
            );
263
        }
264
265 1
        return $this->diffElements($oldText, $newText);
266
    }
267
268 11 View Code Duplication
    protected function processEqualOperation($operation)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
269
    {
270 11
        $result = array();
271 11
        foreach ($this->newWords as $pos => $s) {
272
273 11
            if ($pos >= $operation->startInNew && $pos < $operation->endInNew) {
274 11
                if (in_array($s, $this->isolatedDiffTags) && isset($this->newIsolatedDiffTags[$pos])) {
275
276 8
                    $result[] = $this->diffIsolatedPlaceholder($operation, $pos, $s);
277 8
                } else {
278 11
                    $result[] = $s;
279
                }
280 11
            }
281 11
        }
282 11
        $this->content .= implode( "", $result );
283 11
    }
284
285
    /**
286
     * @param string $text
287
     * @param string $attribute
288
     *
289
     * @return null|string
290
     */
291 1
    protected function getAttributeFromTag($text, $attribute)
292
    {
293 1
        $matches = array();
294 1
        if (preg_match(sprintf('/<a\s+[^>]*%s=([\'"])(.*)\1[^>]*>/i', $attribute), $text, $matches)) {
295 1
            return $matches[2];
296
        }
297
298
        return null;
299
    }
300
301 8
    protected function isListPlaceholder($text)
302
    {
303 8
        return $this->isPlaceholderType($text, array('ol', 'dl', 'ul'));
304
    }
305
306
    /**
307
     * @param string $text
308
     *
309
     * @return bool
310
     */
311 2
    public function isLinkPlaceholder($text)
312
    {
313 2
        return $this->isPlaceholderType($text, 'a');
314
    }
315
316
    /**
317
     * @param string       $text
318
     * @param array|string $types
319
     * @param bool         $strict
320
     *
321
     * @return bool
322
     */
323 8
    protected function isPlaceholderType($text, $types, $strict = true)
324
    {
325 8
        if (!is_array($types)) {
326 2
            $types = array($types);
327 2
        }
328
329 8
        $criteria = array();
330 8
        foreach ($types as $type) {
331 8
            if (isset($this->isolatedDiffTags[$type])) {
332 8
                $criteria[] = $this->isolatedDiffTags[$type];
333 8
            } else {
334
                $criteria[] = $type;
335
            }
336 8
        }
337
338 8
        return in_array($text, $criteria, $strict);
339
    }
340
341 8
    protected function findIsolatedDiffTagsInOld($operation, $posInNew)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
342
    {
343 8
        $offset = $posInNew - $operation->startInNew;
344
345 8
        return $this->oldIsolatedDiffTags[$operation->startInOld + $offset];
346
    }
347
348 9
    protected function insertTag($tag, $cssClass, &$words)
349
    {
350 9
        while (true) {
351 9
            if ( count( $words ) == 0 ) {
352 9
                break;
353
            }
354
355 9
            $nonTags = $this->extractConsecutiveWords( $words, 'noTag' );
356
357 9
            $specialCaseTagInjection = '';
358 9
            $specialCaseTagInjectionIsBefore = false;
359
360 9
            if ( count( $nonTags ) != 0 ) {
361 9
                $text = $this->wrapText( implode( "", $nonTags ), $tag, $cssClass );
362 9
                $this->content .= $text;
363 9
            } else {
364 6
                $firstOrDefault = false;
365 6
                foreach ($this->specialCaseOpeningTags as $x) {
366
                    if ( preg_match( $x, $words[ 0 ] ) ) {
367
                        $firstOrDefault = $x;
368
                        break;
369
                    }
370 6
                }
371 6
                if ($firstOrDefault) {
372
                    $specialCaseTagInjection = '<ins class="mod">';
373
                    if ($tag == "del") {
374
                        unset( $words[ 0 ] );
375
                    }
376 6
                } elseif ( array_search( $words[ 0 ], $this->specialCaseClosingTags ) !== false ) {
377
                    $specialCaseTagInjection = "</ins>";
378
                    $specialCaseTagInjectionIsBefore = true;
379
                    if ($tag == "del") {
380
                        unset( $words[ 0 ] );
381
                    }
382
                }
383
            }
384 9
            if ( count( $words ) == 0 && count( $specialCaseTagInjection ) == 0 ) {
385
                break;
386
            }
387 9
            if ($specialCaseTagInjectionIsBefore) {
388
                $this->content .= $specialCaseTagInjection . implode( "", $this->extractConsecutiveWords( $words, 'tag' ) );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 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...
389
            } else {
390 9
                $workTag = $this->extractConsecutiveWords( $words, 'tag' );
391 9
                if ( isset( $workTag[ 0 ] ) && $this->isOpeningTag( $workTag[ 0 ] ) && !$this->isClosingTag( $workTag[ 0 ] ) ) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 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...
392 8
                    if ( strpos( $workTag[ 0 ], 'class=' ) ) {
393 2
                        $workTag[ 0 ] = str_replace( 'class="', 'class="diffmod ', $workTag[ 0 ] );
394 2
                        $workTag[ 0 ] = str_replace( "class='", 'class="diffmod ', $workTag[ 0 ] );
395 2
                    } else {
396 8
                        $workTag[ 0 ] = str_replace( ">", ' class="diffmod">', $workTag[ 0 ] );
397
                    }
398 8
                }
399 9
                $this->content .= implode( "", $workTag ) . $specialCaseTagInjection;
400
            }
401 9
        }
402 9
    }
403
404 9
    protected function checkCondition($word, $condition)
405
    {
406 9
        return $condition == 'tag' ? $this->isTag( $word ) : !$this->isTag( $word );
407
    }
408
409 10
    protected function wrapText($text, $tagName, $cssClass)
410
    {
411 10
        return sprintf( '<%1$s class="%2$s">%3$s</%1$s>', $tagName, $cssClass, $text );
412
    }
413
414 9
    protected function extractConsecutiveWords(&$words, $condition)
415
    {
416 9
        $indexOfFirstTag = null;
417 9
        $words = array_values($words);
418 9
        foreach ($words as $i => $word) {
419 9
            if ( !$this->checkCondition( $word, $condition ) ) {
420 8
                $indexOfFirstTag = $i;
421 8
                break;
422
            }
423 9
        }
424 9
        if ($indexOfFirstTag !== null) {
425 8
            $items = array();
426 8 View Code Duplication
            foreach ($words as $pos => $s) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
427 8
                if ($pos >= 0 && $pos < $indexOfFirstTag) {
428 8
                    $items[] = $s;
429 8
                }
430 8
            }
431 8
            if ($indexOfFirstTag > 0) {
432 8
                array_splice( $words, 0, $indexOfFirstTag );
433 8
            }
434
435 8
            return $items;
436
        } else {
437 9
            $items = array();
438 9 View Code Duplication
            foreach ($words as $pos => $s) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
439 9
                if ( $pos >= 0 && $pos <= count( $words ) ) {
440 9
                    $items[] = $s;
441 9
                }
442 9
            }
443 9
            array_splice( $words, 0, count( $words ) );
444
445 9
            return $items;
446
        }
447
    }
448
449 11
    protected function isTag($item)
450
    {
451 11
        return $this->isOpeningTag( $item ) || $this->isClosingTag( $item );
452
    }
453
454 11
    protected function isOpeningTag($item)
455
    {
456 11
        return preg_match( "#<[^>]+>\\s*#iU", $item );
457
    }
458
459 11
    protected function isClosingTag($item)
460
    {
461 11
        return preg_match( "#</[^>]+>\\s*#iU", $item );
462
    }
463
464 11
    protected function operations()
465
    {
466 11
        $positionInOld = 0;
467 11
        $positionInNew = 0;
468 11
        $operations = array();
469 11
        $matches = $this->matchingBlocks();
470 11
        $matches[] = new Match( count( $this->oldWords ), count( $this->newWords ), 0 );
471 11
        foreach ($matches as $i => $match) {
472 11
            $matchStartsAtCurrentPositionInOld = ( $positionInOld == $match->startInOld );
473 11
            $matchStartsAtCurrentPositionInNew = ( $positionInNew == $match->startInNew );
474 11
            $action = 'none';
0 ignored issues
show
Unused Code introduced by
$action is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
475
476 11
            if ($matchStartsAtCurrentPositionInOld == false && $matchStartsAtCurrentPositionInNew == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
477 7
                $action = 'replace';
478 11
            } elseif ($matchStartsAtCurrentPositionInOld == true && $matchStartsAtCurrentPositionInNew == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
479 9
                $action = 'insert';
480 11
            } elseif ($matchStartsAtCurrentPositionInOld == false && $matchStartsAtCurrentPositionInNew == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
481 6
                $action = 'delete';
482 6
            } else { // This occurs if the first few words are the same in both versions
483 11
                $action = 'none';
484
            }
485 11
            if ($action != 'none') {
486 9
                $operations[] = new Operation( $action, $positionInOld, $match->startInOld, $positionInNew, $match->startInNew );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 129 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...
487 9
            }
488 11
            if ( count( $match ) != 0 ) {
489 11
                $operations[] = new Operation( 'equal', $match->startInOld, $match->endInOld(), $match->startInNew, $match->endInNew() );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 137 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...
490 11
            }
491 11
            $positionInOld = $match->endInOld();
492 11
            $positionInNew = $match->endInNew();
493 11
        }
494
495 11
        return $operations;
496
    }
497
498 11
    protected function matchingBlocks()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
499
    {
500 11
        $matchingBlocks = array();
501 11
        $this->findMatchingBlocks( 0, count( $this->oldWords ), 0, count( $this->newWords ), $matchingBlocks );
502
503 11
        return $matchingBlocks;
504
    }
505
506 11
    protected function findMatchingBlocks($startInOld, $endInOld, $startInNew, $endInNew, &$matchingBlocks)
507
    {
508 11
        $match = $this->findMatch( $startInOld, $endInOld, $startInNew, $endInNew );
509 11
        if ($match !== null) {
510 11
            if ($startInOld < $match->startInOld && $startInNew < $match->startInNew) {
511 8
                $this->findMatchingBlocks( $startInOld, $match->startInOld, $startInNew, $match->startInNew, $matchingBlocks );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 127 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...
512 8
            }
513 11
            $matchingBlocks[] = $match;
514 11
            if ( $match->endInOld() < $endInOld && $match->endInNew() < $endInNew ) {
515 9
                $this->findMatchingBlocks( $match->endInOld(), $endInOld, $match->endInNew(), $endInNew, $matchingBlocks );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 123 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...
516 9
            }
517 11
        }
518 11
    }
519
520 9
    protected function stripTagAttributes($word)
521
    {
522 9
        $word = explode( ' ', trim( $word, '<>' ) );
523
524 9
        return '<' . $word[ 0 ] . '>';
525
    }
526
527 11
    protected function findMatch($startInOld, $endInOld, $startInNew, $endInNew)
528
    {
529 11
        $bestMatchInOld = $startInOld;
530 11
        $bestMatchInNew = $startInNew;
531 11
        $bestMatchSize = 0;
532 11
        $matchLengthAt = array();
533 11
        for ($indexInOld = $startInOld; $indexInOld < $endInOld; $indexInOld++) {
534 11
            $newMatchLengthAt = array();
535 11
            $index = $this->oldWords[ $indexInOld ];
536 11
            if ( $this->isTag( $index ) ) {
537 9
                $index = $this->stripTagAttributes( $index );
538 9
            }
539 11
            if ( !isset( $this->wordIndices[ $index ] ) ) {
540 9
                $matchLengthAt = $newMatchLengthAt;
541 9
                continue;
542
            }
543 11
            foreach ($this->wordIndices[ $index ] as $indexInNew) {
544 11
                if ($indexInNew < $startInNew) {
545 9
                    continue;
546
                }
547 11
                if ($indexInNew >= $endInNew) {
548 8
                    break;
549
                }
550 11
                $newMatchLength = ( isset( $matchLengthAt[ $indexInNew - 1 ] ) ? $matchLengthAt[ $indexInNew - 1 ] : 0 ) + 1;
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...
551 11
                $newMatchLengthAt[ $indexInNew ] = $newMatchLength;
552 11
                if ($newMatchLength > $bestMatchSize ||
553
                    (
554 11
                        $this->isGroupDiffs() &&
555 11
                        $bestMatchSize > 0 &&
556 11
                        preg_match(
557 11
                            '/^\s+$/',
558 11
                            implode('', array_slice($this->oldWords, $bestMatchInOld, $bestMatchSize))
559 11
                        )
560 11
                    )
561 11
                ) {
562 11
                    $bestMatchInOld = $indexInOld - $newMatchLength + 1;
563 11
                    $bestMatchInNew = $indexInNew - $newMatchLength + 1;
564 11
                    $bestMatchSize = $newMatchLength;
565 11
                }
566 11
            }
567 11
            $matchLengthAt = $newMatchLengthAt;
568 11
        }
569
570
        // Skip match if none found or match consists only of whitespace
571 11
        if ($bestMatchSize != 0 &&
572
            (
573 11
                !$this->isGroupDiffs() ||
574 11
                !preg_match('/^\s+$/', implode('', array_slice($this->oldWords, $bestMatchInOld, $bestMatchSize)))
575 11
            )
576 11
        ) {
577 11
            return new Match($bestMatchInOld, $bestMatchInNew, $bestMatchSize);
578
        }
579
580 7
        return null;
581
    }
582
}
583