Completed
Push — master ( c7d60b...084747 )
by Josh
02:55
created

ListDiff   D

Complexity

Total Complexity 133

Size/Duplication

Total Lines 940
Duplicated Lines 1.91 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 23
Bugs 2 Features 2
Metric Value
wmc 133
c 23
b 2
f 2
lcom 1
cbo 1
dl 18
loc 940
rs 4.4444

29 Methods

Rating   Name   Duplication   Size   Complexity  
A build() 0 11 1
B diffListContent() 0 28 1
C indexContent() 0 55 13
A formatThisListContent() 0 16 4
C formatList() 8 24 7
A getAndStripTag() 0 5 1
A matchAndCompareLists() 0 21 1
A compareChildLists() 0 4 1
F createNewOldMatches() 0 106 19
A getArrayColumn() 0 11 3
A buildChildLists() 0 5 1
C diff() 10 52 12
A addListElementToContent() 0 14 1
B addContentElementsToContent() 0 18 6
A getListByMatch() 0 6 2
B getArrayByColumnValue() 0 15 6
B convertListContentArrayToString() 0 24 5
C processPlaceholders() 0 33 7
A checkWordForDiffTag() 0 18 3
A stripNewLine() 0 4 1
B getListContent() 0 18 5
B findEndForIndex() 0 19 5
B indexLists() 0 21 6
A addListTypeWrapper() 0 9 4
A replaceListIsolatedDiffTags() 0 5 1
B getListsContent() 0 29 6
B addStringToArrayByDepth() 0 62 7
A isOpeningListTag() 0 8 2
A isClosingListTag() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ListDiff often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ListDiff, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Caxy\HtmlDiff;
4
5
class ListDiff extends HtmlDiff
6
{
7
    /**
8
     * This is the minimum percentage a list item can match its counterpart in order to be considered a match.
9
     * @var integer
10
     */
11
    protected static $listMatchThreshold = 35;
12
    
13
    /** @var array */
14
    protected $listWords = array();
15
16
    /** @var array */
17
    protected $listTags = array();
18
19
    /** @var array */
20
    protected $listIsolatedDiffTags = array();
21
22
    /** @var array */
23
    protected $isolatedDiffTags = array (
24
        'ol' => '[[REPLACE_ORDERED_LIST]]',
25
        'ul' => '[[REPLACE_UNORDERED_LIST]]',
26
        'dl' => '[[REPLACE_DEFINITION_LIST]]',
27
    );
28
29
    /**
30
     * List (li) placeholder.
31
     * @var string
32
     */
33
    protected static $listPlaceHolder = "[[REPLACE_LIST_ITEM]]";
34
35
    /**
36
     * Holds the type of list this is ol, ul, dl.
37
     * @var string
38
     */
39
    protected $listType;
40
    
41
    /**
42
     * Used to hold what type of list the old list is.
43
     * @var string
44
     */
45
    protected $oldListType;
46
    
47
    /**
48
     * Used to hold what type of list the new list is.
49
     * @var string
50
     */
51
    protected $newListType;
52
53
    /**
54
     * Hold the old/new content of the content of the list.
55
     * @var array
56
     */
57
    protected $list;
58
59
    /**
60
     * Contains the old/new child lists content within this list.
61
     * @var array
62
     */
63
    protected $childLists;
64
65
    /**
66
     * Contains the old/new text strings that match
67
     * @var array
68
     */
69
    protected $textMatches;
70
71
    /**
72
     * Contains the indexed start positions of each list within word string.
73
     * @var array
74
     */
75
    protected $listsIndex;
76
    
77
    /**
78
     * Array that holds the index of all content outside of the array. Format is array(index => content).
79
     * @var array
80
     */
81
    protected $contentIndex = array();
82
    
83
    /** 
84
     * Holds the order and data on each list/content block within this list.
85
     * @var array
86
     */
87
    protected $diffOrderIndex = array();
88
    
89
    /**
90
     * This is the opening ol,ul,dl ist tag.
91
     * @var string
92
     */
93
    protected $oldParentTag;
94
    
95
    /**
96
     * This is the opening ol,ul,dl ist tag.
97
     * @var string
98
     */
99
    protected $newParentTag;
100
101
    /**
102
     * We're using the same functions as the parent in build() to get us to the point of
103
     * manipulating the data within this class.
104
     *
105
     * @return string
106
     */
107
    public function build()
108
    {
109
        // Use the parent functions to get the data we need organized.
110
        $this->splitInputsToWords();
111
        $this->replaceIsolatedDiffTags();
112
        $this->indexNewWords();
113
        // Now use the custom functions in this class to use the data and generate our diff.
114
        $this->diffListContent();
115
116
        return $this->content;
117
    }
118
119
    /**
120
     * Calls to the actual custom functions of this class, to diff list content.
121
     */
122
    protected function diffListContent()
123
    {
124
        /* Format the list we're focusing on.
125
         * There will always be one list, though passed as an array with one item.
126
         * Format this to only have the list contents, outside of the array.
127
         */
128
        $this->formatThisListContent();
129
        
130
        /* Build an index of content outside of list tags.
131
         */
132
        $this->indexContent();
133
        
134
        /* In cases where we're dealing with nested lists,
135
         * make sure we use placeholders to replace the nested lists
136
         */
137
        $this->replaceListIsolatedDiffTags();
138
        
139
        /* Build a list of matches we can reference when we diff the contents of the lists.
140
         * This is needed so that we each NEW list node is matched against the best possible OLD list node/
141
         * It helps us determine whether the list was added, removed, or changed.
142
         */
143
        $this->matchAndCompareLists();
144
        
145
        /* Go through the list of matches, content, and diff each.
146
         * Any nested lists would be sent to parent's diffList function, which creates a new listDiff class.
147
         */
148
        $this->diff();
149
    }
150
    
151
    /**
152
     * This function is used to populate both contentIndex and diffOrderIndex arrays for use in the diff function.
153
     */
154
    protected function indexContent()
155
    {
156
        $this->contentIndex = array();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
157
        $this->diffOrderIndex = array('new' => array(), 'old' => array());
158
        foreach ($this->list as $type => $list) {
159
            
160
            $this->contentIndex[$type] = array();
161
            $depth = 0;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 21 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
162
            $parentList = 0;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 16 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
163
            $position = 0;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 18 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
164
            $newBlock = true;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 18 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
165
            $listCount = 0;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 17 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
166
            $contentCount = 0;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 14 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
167
            foreach ($list as $key => $word) {
168
                if (!$parentList && $this->isOpeningListTag($word)) {
169
                    $depth++;
170
                    
171
                    $this->diffOrderIndex[$type][] = array('type' => 'list', 'position' => $listCount, 'index' => $key);
172
                    $listCount++;
173
                    continue;
174
                }
175
                
176
                if (!$parentList && $this->isClosingListTag($word)) {
177
                    $depth--;
178
                    
179
                    if ($depth == 0) {
180
                        $newBlock = true;
181
                    }
182
                    continue;
183
                }
184
                
185
                if ($this->isOpeningIsolatedDiffTag($word)) {
186
                    $parentList++;
187
                }
188
                
189
                if ($this->isClosingIsolatedDiffTag($word)) {
190
                    $parentList--;
191
                }
192
                
193
                if ($depth == 0) {
194
                    if ($newBlock && !array_key_exists($contentCount, $this->contentIndex[$type])) {
195
                        $this->diffOrderIndex[$type][] = array('type' => 'content', 'position' => $contentCount, 'index' => $key);
196
                        
197
                        $position = $contentCount;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 29 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
198
                        $this->contentIndex[$type][$position] = '';
199
                        $contentCount++;
200
                    }
201
                    
202
                    $this->contentIndex[$type][$position] .= $word;
203
                }
204
                
205
                $newBlock = false;
206
            }
207
        }
208
    }
209
210
    /*
211
     * This function is used to remove the wrapped ul, ol, or dl characters from this list
212
     * and sets the listType as ul, ol, or dl, so that we can use it later.
213
     * $list is being set here as well, as an array with the old and new version of this list content.
214
     */
215
    protected function formatThisListContent()
216
    {
217
        $formatArray = array(
218
            array('type' => 'old', 'array' => $this->oldIsolatedDiffTags),
219
            array('type' => 'new', 'array' => $this->newIsolatedDiffTags)
220
        );
221
        
222
        foreach ($formatArray as $item) {
223
            $values = array_values($item['array']);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 20 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
224
            $this->list[$item['type']] = count($values)
225
                ? $this->formatList($values[0], $item['type'])
226
                : array();
227
        }
228
        
229
        $this->listType = $this->newListType ?: $this->oldListType;
230
    }
231
    
232
    /**
233
     * 
234
     * @param array $arrayData
235
     * @param string $index
236
     * @return array
237
     */
238
    protected function formatList(array $arrayData, $index = 'old')
239
    {
240
        $openingTag = $this->getAndStripTag($arrayData[0]);
241
        $closingTag = $this->getAndStripTag($arrayData[count($arrayData) - 1]);
242
        
243
        if (array_key_exists($openingTag, $this->isolatedDiffTags) &&
244
            array_key_exists($closingTag, $this->isolatedDiffTags)
245
        ) {
246 View Code Duplication
            if ($index == 'new' && $this->isOpeningTag($arrayData[0])) {
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...
247
                $this->newParentTag = $arrayData[0];
248
                $this->newListType = $this->getAndStripTag($arrayData[0]);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
249
            }
250
            
251 View Code Duplication
            if ($index == 'old' && $this->isOpeningTag($arrayData[0])) {
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...
252
                $this->oldParentTag = $arrayData[0];
253
                $this->oldListType = $this->getAndStripTag($arrayData[0]);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
254
            }
255
            
256
            array_shift($arrayData);
257
            array_pop($arrayData);
258
        }
259
        
260
        return $arrayData;
261
    }
262
263
    /**
264
     * @param string $tag
265
     * @return string
266
     */
267
    protected function getAndStripTag($tag)
268
    {
269
        $content = explode(' ', preg_replace("/[^A-Za-z0-9 ]/", '', $tag));
270
        return $content[0];
271
    }
272
273
    protected function matchAndCompareLists()
274
    {
275
        /**
276
         * Build the an array (childLists) to hold the contents of the list nodes within this list.
277
         * This only holds the content of each list node.
278
         */
279
        $this->buildChildLists();
280
281
        /**
282
         * Index the list, starting positions, so that we can refer back to it later.
283
         * This is used to see where one list node starts and another ends.
284
         */
285
        $this->indexLists();
286
287
        /**
288
         * Compare the lists and build $textMatches array with the matches.
289
         * Each match is an array of "new" and "old" keys, with the id of the list it matches to.
290
         * Whenever there is no match (in cases where a new list item was added or removed), null is used instead of the id.
291
         */
292
        $this->compareChildLists();
293
    }
294
    
295
    /**
296
     * Creates matches for lists.
297
     */
298
    protected function compareChildLists()
299
    {
300
        $this->createNewOldMatches($this->childLists, $this->textMatches, 'content');
301
    }
302
    
303
    /**
304
     * Abstracted function used to match items in an array.
305
     * This is used primarily for populating lists matches.
306
     * 
307
     * @param array $listArray
308
     * @param array $resultArray
309
     * @param string|null $column
310
     */
311
    protected function createNewOldMatches(&$listArray, &$resultArray, $column = null)
312
    {
313
        // Always compare the new against the old.
314
        // Compare each new string against each old string.
315
        $bestMatchPercentages = array();
316
        
317
        foreach ($listArray['new'] as $thisKey => $thisList) {
318
            $bestMatchPercentages[$thisKey] = array();
319
            foreach ($listArray['old'] as $thatKey => $thatList) {
320
                // Save the percent amount each new list content compares against the old list content.
321
                similar_text(
322
                    $column ? $thisList[$column] : $thisList,
323
                    $column ? $thatList[$column] : $thatList,
324
                    $percentage
325
                );
326
                
327
                $bestMatchPercentages[$thisKey][] = $percentage;
328
            }
329
        }
330
        
331
        // Sort each array by value, highest percent to lowest percent.
332
        foreach ($bestMatchPercentages as &$thisMatch) {
333
            arsort($thisMatch);
334
        }
335
        
336
        // Build matches.
337
        $matches = array();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
338
        $taken = array();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
339
        $takenItems = array();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
340
        $absoluteMatch = 100;
341
        foreach ($bestMatchPercentages as $item => $percentages) {
342
            $highestMatch = -1;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
343
            $highestMatchKey = -1;
344
            $takeItemKey = -1;
0 ignored issues
show
Unused Code introduced by
$takeItemKey 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...
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
345
346
            foreach ($percentages as $key => $percent) {
347
                // Check that the key for the percentage is not already taken and the new percentage is higher.
348
                if (!in_array($key, $taken) && $percent > $highestMatch) {
349
                    // If an absolute match, choose this one.
350
                    if ($percent == $absoluteMatch) {
351
                        $highestMatch = $percent;
0 ignored issues
show
Unused Code introduced by
$highestMatch 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...
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
352
                        $highestMatchKey = $key;
353
                        $takenItemKey = $item;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
354
                        break;
355
                    } else {
356
                        // Get all the other matces for the same $key
357
                        $columns = $this->getArrayColumn($bestMatchPercentages, $key);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
358
                        $thisBestMatches = array_filter(
359
                            $columns,
360
                            function ($v) use ($percent) {
361
                                return $v > $percent;
362
                            }
363
                        );
364
365
                        arsort($thisBestMatches);
366
                        
367
                        /**
368
                         * If the list item does not meet the threshold, it will not be considered a match.
369
                         */
370
                        if ($percent >= self::$listMatchThreshold) {
371
                            // If no greater amounts, use this one.
372
                            if (!count($thisBestMatches)) {
373
                                $highestMatch = $percent;
0 ignored issues
show
Unused Code introduced by
$highestMatch 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...
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
374
                                $highestMatchKey = $key;
375
                                $takenItemKey = $item;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
376
                                break;
377
                            }
378
379
                            // Loop through, comparing only the items that have not already been added.
380
                            foreach ($thisBestMatches as $k => $v) {
381
                                if (in_array($k, $takenItems)) {
382
                                    $highestMatch = $percent;
0 ignored issues
show
Unused Code introduced by
$highestMatch 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...
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
383
                                    $highestMatchKey = $key;
384
                                    $takenItemKey = $item;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
385
                                    break(2);
386
                                }
387
                            }
388
                        }
389
                    }
390
                }
391
            }
392
            
393
            $matches[] = array('new' => $item, 'old' => $highestMatchKey > -1 ? $highestMatchKey : null);
394
            if ($highestMatchKey > -1) {
395
                $taken[] = $highestMatchKey;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
396
                $takenItems[] = $takenItemKey;
0 ignored issues
show
Bug introduced by
The variable $takenItemKey does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
397
            }
398
        }
399
        
400
        
401
402
        /* Checking for removed items. Basically, if a list item from the old lists is removed
403
         * it will not be accounted for, and will disappear in the results altogether.
404
         * Loop through all the old lists, any that has not been added, will be added as:
405
         * array( new => null, old => oldItemId )
406
         */
407
        $matchColumns = $this->getArrayColumn($matches, 'old');
408
        foreach ($listArray['old'] as $thisKey => $thisList) {
409
            if (!in_array($thisKey, $matchColumns)) {
410
                $matches[] = array('new' => null, 'old' => $thisKey);
411
            }
412
        }
413
        
414
        // Save the matches.
415
        $resultArray = $matches;
416
    }
417
    
418
    /**
419
     * This fuction is exactly like array_column. This is added for PHP versions that do not support array_column.
420
     * @param array $targetArray
421
     * @param mixed $key
422
     * @return array
423
     */
424
    protected function getArrayColumn(array $targetArray, $key)
425
    {
426
        $data = array();
427
        foreach ($targetArray as $item) {
428
            if (array_key_exists($key, $item)) {
429
                $data[] = $item[$key];
430
            }
431
        }
432
        
433
        return $data;
434
    }
435
436
    /**
437
     * Build multidimensional array holding the contents of each list node, old and new.
438
     */
439
    protected function buildChildLists()
440
    {
441
        $this->childLists['old'] = $this->getListsContent($this->list['old']);
442
        $this->childLists['new'] = $this->getListsContent($this->list['new']);
443
    }
444
445
    /**
446
     * Diff the actual contents of the lists against their matched counterpart.
447
     * Build the content of the class.
448
     */
449
    protected function diff()
450
    {
451
        // Add the opening parent node from listType. So if ol, <ol>, etc.
452
        $this->content = $this->addListTypeWrapper();
453
        
454
        $oldIndexCount = 0;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
455
        $diffOrderNewKeys = array_keys($this->diffOrderIndex['new']);
456
        foreach ($this->diffOrderIndex['new'] as $key => $index) {
457
            
458
            if ($index['type'] == "list") {
459
                
460
                // Check to see if an old list was deleted.
461
                $oldMatch = $this->getArrayByColumnValue($this->textMatches, 'old', $index['position']);
462 View Code Duplication
                if ($oldMatch && $oldMatch['new'] === null) {
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...
463
                    $newList = '';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
464
                    $oldList = $this->getListByMatch($oldMatch, 'old');
0 ignored issues
show
Bug introduced by
It seems like $oldMatch defined by $this->getArrayByColumnV...d', $index['position']) on line 461 can also be of type boolean; however, Caxy\HtmlDiff\ListDiff::getListByMatch() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
465
                    $this->content .= $this->addListElementToContent($newList, $oldList, $oldMatch, $index, 'old');
0 ignored issues
show
Bug introduced by
It seems like $oldList defined by $this->getListByMatch($oldMatch, 'old') on line 464 can also be of type array; however, Caxy\HtmlDiff\ListDiff::addListElementToContent() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $oldMatch defined by $this->getArrayByColumnV...d', $index['position']) on line 461 can also be of type boolean; however, Caxy\HtmlDiff\ListDiff::addListElementToContent() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
466
                }
467
                
468
                $match = $this->getArrayByColumnValue($this->textMatches, 'new', $index['position']);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 10 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
469
                $newList = $this->childLists['new'][$match['new']];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
470
                $oldList = $this->getListByMatch($match, 'old');
0 ignored issues
show
Bug introduced by
It seems like $match defined by $this->getArrayByColumnV...w', $index['position']) on line 468 can also be of type boolean; however, Caxy\HtmlDiff\ListDiff::getListByMatch() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
471
                $this->content .= $this->addListElementToContent($newList, $oldList, $match, $index, 'new');
0 ignored issues
show
Bug introduced by
It seems like $oldList defined by $this->getListByMatch($match, 'old') on line 470 can also be of type array; however, Caxy\HtmlDiff\ListDiff::addListElementToContent() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $match defined by $this->getArrayByColumnV...w', $index['position']) on line 468 can also be of type boolean; however, Caxy\HtmlDiff\ListDiff::addListElementToContent() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
472
            }
473
            
474
            if ($index['type'] == 'content') {
475
                $this->content .= $this->addContentElementsToContent($oldIndexCount, $index['position']);
476
            }
477
            
478
            $oldIndexCount++;
479
            
480
            if ($key == $diffOrderNewKeys[count($diffOrderNewKeys) - 1]) {
481
                foreach ($this->diffOrderIndex['old'] as $oldKey => $oldIndex) {
482
                    if ($oldKey > $key) {
483
                        if ($oldIndex['type'] == 'list') {
484
                            $oldMatch = $this->getArrayByColumnValue($this->textMatches, 'old', $oldIndex['position']);
485 View Code Duplication
                            if ($oldMatch && $oldMatch['new'] === null) {
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...
486
                                $newList = '';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
487
                                $oldList = $this->getListByMatch($oldMatch, 'old');
0 ignored issues
show
Bug introduced by
It seems like $oldMatch defined by $this->getArrayByColumnV... $oldIndex['position']) on line 484 can also be of type boolean; however, Caxy\HtmlDiff\ListDiff::getListByMatch() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
488
                                $this->content .= $this->addListElementToContent($newList, $oldList, $oldMatch, $oldIndex, 'old');
0 ignored issues
show
Bug introduced by
It seems like $oldList defined by $this->getListByMatch($oldMatch, 'old') on line 487 can also be of type array; however, Caxy\HtmlDiff\ListDiff::addListElementToContent() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $oldMatch defined by $this->getArrayByColumnV... $oldIndex['position']) on line 484 can also be of type boolean; however, Caxy\HtmlDiff\ListDiff::addListElementToContent() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
489
                            }
490
                        } else {
491
                            $this->content .= $this->addContentElementsToContent($oldKey);
492
                        }
493
                    }
494
                }
495
            }
496
        }
497
498
        // Add the closing parent node from listType. So if ol, </ol>, etc.
499
        $this->content .= $this->addListTypeWrapper(false);
500
    }
501
    
502
    /**
503
     * 
504
     * @param string $newList
505
     * @param string $oldList
506
     * @param array $match
507
     * @param array $index
508
     * @return string
509
     */
510
    protected function addListElementToContent($newList, $oldList, array $match, array $index, $type)
511
    {
512
        $content = $this->list[$type][$index['index']];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
513
        $content .= $this->processPlaceholders(
514
            $this->diffElements(
515
                $this->convertListContentArrayToString($oldList),
0 ignored issues
show
Documentation introduced by
$oldList is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
516
                $this->convertListContentArrayToString($newList),
0 ignored issues
show
Documentation introduced by
$newList is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
517
                false
518
            ),
519
            $match
520
        );
521
        $content .= "</li>";
522
        return $content;
523
    }
524
    
525
    /**
526
     * 
527
     * @param integer $oldIndexCount
528
     * @param null|integer $newPosition
529
     * @return string
530
     */
531
    protected function addContentElementsToContent($oldIndexCount, $newPosition = null)
532
    {
533
        $newContent = $newPosition && array_key_exists($newPosition, $this->contentIndex['new'])
0 ignored issues
show
Bug Best Practice introduced by
The expression $newPosition of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
534
            ? $this->contentIndex['new'][$newPosition]
535
            : '';
536
537
        $oldDiffOrderIndexMatch = array_key_exists($oldIndexCount, $this->diffOrderIndex['old'])
538
            ? $this->diffOrderIndex['old'][$oldIndexCount]
539
            : '';
540
541
        $oldContent = $oldDiffOrderIndexMatch && array_key_exists($oldDiffOrderIndexMatch['position'], $this->contentIndex['old'])
542
            ? $this->contentIndex['old'][$oldDiffOrderIndexMatch['position']]
543
            : '';
544
545
        $diffObject = new HtmlDiff($oldContent, $newContent);
546
        $content = $diffObject->build();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
547
        return $content;
548
    }
549
    
550
    /**
551
     * 
552
     * @param array $match
553
     * @param string $type
554
     * @return array|string
555
     */
556
    protected function getListByMatch(array $match, $type = 'new')
557
    {
558
        return array_key_exists($match[$type], $this->childLists[$type])
559
            ? $this->childLists[$type][$match[$type]]
560
            : '';
561
    }
562
    
563
    /**
564
     * This function replaces array_column function in PHP for older versions of php.
565
     * 
566
     * @param array $parentArray
567
     * @param string $column
568
     * @param mixed $value
569
     * @param boolean $allMatches
570
     * @return array|boolean
571
     */
572
    protected function getArrayByColumnValue($parentArray, $column, $value, $allMatches = false)
573
    {
574
        $returnArray = array();
575
        foreach ($parentArray as $array) {
576
            if (array_key_exists($column, $array) && $array[$column] == $value) {
577
                if ($allMatches) {
578
                    $returnArray[] = $array;
579
                } else {
580
                    return $array;
581
                }
582
            }
583
        }
584
        
585
        return $allMatches ? $returnArray : false;
586
    }
587
588
    /**
589
     * Converts the list (li) content arrays to string.
590
     *
591
     * @param array $listContentArray
592
     * @return string
593
     */
594
    protected function convertListContentArrayToString($listContentArray)
595
    {
596
        if (!is_array($listContentArray)) {
597
            return $listContentArray;
598
        }
599
600
        $content = array();
601
602
        $words = explode(" ", $listContentArray['content']);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 11 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
603
        $nestedListCount = 0;
604
        foreach ($words as $word) {
605
            $match = $word == self::$listPlaceHolder;
606
607
            $content[] = $match
608
                ? "<li>" . $this->convertListContentArrayToString($listContentArray['kids'][$nestedListCount]) . "</li>"
609
                : $word;
610
611
            if ($match) {
612
                $nestedListCount++;
613
            }
614
        }
615
616
        return implode(" ", $content);
617
    }
618
619
    /**
620
     * Return the contents of each list node.
621
     * Process any placeholders for nested lists.
622
     *
623
     * @param string $text
624
     * @param array $matches
625
     * @return string
626
     */
627
    protected function processPlaceholders($text, array $matches)
628
    {        
629
        // Prepare return
630
        $returnText = array();
631
        // Save the contents of all list nodes, new and old.
632
        $contentVault = array(
633
            'old' => $this->getListContent('old', $matches),
634
            'new' => $this->getListContent('new', $matches)
635
        );
636
        
637
        $count = 0;
638
        // Loop through the text checking for placeholders. If a nested list is found, create a new ListDiff object for it.
639
        foreach (explode(' ', $text) as $word) {
640
            $preContent = $this->checkWordForDiffTag($this->stripNewLine($word));
641
            
642
            if (in_array(
643
                    is_array($preContent) ? $preContent[1] : $preContent,
644
                    $this->isolatedDiffTags
645
                )
646
            ) {
647
                $oldText = array_key_exists($count, $contentVault['old']) ? implode('', $contentVault['old'][$count]) : '';
648
                $newText = array_key_exists($count, $contentVault['new']) ? implode('', $contentVault['new'][$count]) : '';
649
                $content = $this->diffList($oldText, $newText);
650
                $count++;
651
            } else {
652
                $content = $preContent;
653
            }
654
655
            $returnText[] = is_array($preContent) ? $preContent[0] . $content . $preContent[2] : $content;
656
        }
657
        // Return the result.
658
        return implode(' ', $returnText);
659
    }
660
661
    /**
662
     * Checks to see if a diff tag is in string.
663
     *
664
     * @param string $word
665
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
666
     */
667
    protected function checkWordForDiffTag($word)
668
    {
669
        foreach ($this->isolatedDiffTags as $diffTag) {
670
            if (strpos($word, $diffTag) > -1) {
671
                $position = strpos($word, $diffTag);
672
                $length = strlen($diffTag);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
673
                $result = array(
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
674
                    substr($word, 0, $position),
675
                    $diffTag,
676
                    substr($word, ($position + $length))
677
                );
678
679
                return $result;
680
            }
681
        }
682
683
        return $word;
684
    }
685
686
    /**
687
     * Used to remove new lines.
688
     *
689
     * @param string $text
690
     * @return string
691
     */
692
    protected function stripNewLine($text)
693
    {
694
        return trim(preg_replace('/\s\s+/', ' ', $text));
695
    }
696
697
    /**
698
     * Grab the list content using the listsIndex array.
699
     *
700
     * @param string $indexKey
701
     * @param array $matches
702
     * @return array
703
     */
704
    protected function getListContent($indexKey = 'new', array $matches)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
705
    {        
706
        $bucket = array();
707
708
        if (isset($matches[$indexKey]) && $matches[$indexKey] !== null) {
709
            $start = $this->listsIndex[$indexKey][$matches[$indexKey]];
710
            $stop = $this->findEndForIndex($this->list[$indexKey], $start);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
711
            
712
            for ($x = $start; $x <= $stop; $x++) {
713
                
714
                if (in_array($this->list[$indexKey][$x], $this->isolatedDiffTags)) {
715
                    $bucket[] = $this->listIsolatedDiffTags[$indexKey][$x];
716
                }
717
            }
718
        }
719
        
720
        return $bucket;
721
    }
722
723
    /**
724
     * Finds the end of list within its index.
725
     *
726
     * @param array $index
727
     * @param integer $start
728
     * @return integer
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
729
     */
730
    protected function findEndForIndex(array $index, $start)
731
    {
732
        $array = array_splice($index, $start);
733
        $count = 0;
734
        foreach ($array as $key => $item) {
735
            if ($this->isOpeningListTag($item)) {
736
                $count++;
737
            }
738
739
            if ($this->isClosingListTag($item)) {
740
                $count--;
741
                if ($count === 0) {
742
                    return $start + $key;
743
                }
744
            }
745
        }
746
747
        return $start + count($array);
748
    }
749
750
    /**
751
     * indexLists
752
     *
753
     * Index the list, starting positions, so that we can refer back to it later.
754
     * This is used to see where one list node starts and another ends.
755
     */
756
    protected function indexLists()
757
    {
758
        $this->listsIndex = array();
759
        $count = 0;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 12 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
760
        foreach ($this->list as $type => $list) {
761
            $this->listsIndex[$type] = array();
762
763
            foreach ($list as $key => $listItem) {
764
                if ($this->isOpeningListTag($listItem)) {
765
                    $count++;
766
                    if ($count === 1) {
767
                        $this->listsIndex[$type][] = $key;
768
                    }
769
                }
770
771
                if ($this->isClosingListTag($listItem)) {
772
                    $count--;
773
                }
774
            }
775
        }
776
    }
777
778
    /**
779
     * Adds the opening or closing list html element, based on listType.
780
     *
781
     * @param boolean $opening
782
     * @return string
783
     */
784
    protected function addListTypeWrapper($opening = true)
785
    {
786
        
787
        if ($opening) {
788
            return $this->newParentTag ?: $this->oldParentTag;
789
        } else {
790
            return "<" . (!$opening ? "/" : '') . $this->listType . ">";
791
        }
792
    }
793
794
    /**
795
     * Replace nested list with placeholders.
796
     */
797
    public function replaceListIsolatedDiffTags()
798
    {
799
        $this->listIsolatedDiffTags['old'] = $this->createIsolatedDiffTagPlaceholders($this->list['old']);
800
        $this->listIsolatedDiffTags['new'] = $this->createIsolatedDiffTagPlaceholders($this->list['new']);
801
    }
802
803
    /**
804
     * Grab the contents of a list node.
805
     *
806
     * @param array $contentArray
807
     * @param boolean $stripTags
808
     * @return array
809
     */
810
    protected function getListsContent(array $contentArray, $stripTags = true)
0 ignored issues
show
Unused Code introduced by
The parameter $stripTags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
811
    {
812
        $lematches = array();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
813
        $arrayDepth = 0;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
814
        $nestedCount = array();
815
        foreach ($contentArray as $index => $word) {
816
            
817
            if ($this->isOpeningListTag($word)) {
818
                $arrayDepth++;
819
                if (!array_key_exists($arrayDepth, $nestedCount)) {
820
                    $nestedCount[$arrayDepth] = 1;
821
                } else {
822
                    $nestedCount[$arrayDepth]++;
823
                }
824
                continue;
825
            }
826
827
            if ($this->isClosingListTag($word)) {
828
                $arrayDepth--;
829
                continue;
830
            }
831
832
            if ($arrayDepth > 0) {
833
                $this->addStringToArrayByDepth($word, $lematches, $arrayDepth, 1, $nestedCount);
834
            }
835
        }
836
837
        return $lematches;
838
    }
839
840
    /**
841
     * This function helps build the list content array of a list.
842
     * If a list has another list within it, the inner list is replaced with the list placeholder and the inner list
843
     * content becomes a child of the parent list.
844
     * This goes recursively down.
845
     *
846
     * @param string $word
847
     * @param array $array
848
     * @param integer $targetDepth
849
     * @param integer $thisDepth
850
     * @param array $nestedCount
851
     */
852
    protected function addStringToArrayByDepth($word, array &$array, $targetDepth, $thisDepth, array $nestedCount)
853
    {
854
        // determine what depth we're at
855
        if ($targetDepth == $thisDepth) {
856
            // decide on what to do at this level
857
858
            if (array_key_exists('content', $array)) {
859
                $array['content'] .= $word;
860
            } else {
861
                // if we're on depth 1, add content
862
                if ($nestedCount[$targetDepth] > count($array)) {
863
                    $array[] = array('content' => '', 'kids' => array());
864
                }
865
866
                $array[count($array) - 1]['content'] .= $word;
867
            }
868
869
        } else {
870
871
            // create first kid if not exist
872
            $newArray = array('content' => '', 'kids' => array());
873
874
            if (array_key_exists('kids', $array)) {
875
                if ($nestedCount[$targetDepth] > count($array['kids'])) {
876
                    $array['kids'][] = $newArray;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
877
                    $array['content'] .= self::$listPlaceHolder;
878
                }
879
880
                // continue to the next depth
881
                $thisDepth++;
882
883
                // get last kid and send to next depth
884
885
                $this->addStringToArrayByDepth(
886
                    $word,
887
                    $array['kids'][count($array['kids']) - 1],
888
                    $targetDepth,
889
                    $thisDepth,
890
                    $nestedCount
891
                );
892
893
            } else {
894
895
                if ($nestedCount[$targetDepth] > count($array[count($array) - 1]['kids'])) {
896
                    $array[count($array) - 1]['kids'][] = $newArray;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
897
                    $array[count($array) - 1]['content'] .= self::$listPlaceHolder;
898
                }
899
                // continue to the next depth
900
                $thisDepth++;
901
902
                // get last kid and send to next depth
903
904
                $this->addStringToArrayByDepth(
905
                    $word,
906
                    $array[count($array) - 1]['kids'][count($array[count($array) - 1]['kids']) - 1],
907
                    $targetDepth,
908
                    $thisDepth,
909
                    $nestedCount
910
                );
911
            }
912
        }
913
    }
914
915
    /**
916
     * Checks if text is opening list tag.
917
     *
918
     * @param string $item
919
     * @return boolean
920
     */
921
    protected function isOpeningListTag($item)
922
    {
923
        if (preg_match("#<li[^>]*>\\s*#iU", $item)) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return (bool) preg_match...[^>]*>\\s*#iU', $item);.
Loading history...
924
            return true;
925
        }
926
927
        return false;
928
    }
929
930
    /**
931
     * Check if text is closing list tag.
932
     *
933
     * @param string $item
934
     * @return boolean
935
     */
936
    protected function isClosingListTag($item)
937
    {
938
        if (preg_match("#</li[^>]*>\\s*#iU", $item)) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return (bool) preg_match...[^>]*>\\s*#iU', $item);.
Loading history...
939
            return true;
940
        }
941
942
        return false;
943
    }
944
}
945