These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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(); |
||
157 | $this->diffOrderIndex = array('new' => array(), 'old' => array()); |
||
158 | foreach ($this->list as $type => $list) { |
||
159 | |||
160 | $this->contentIndex[$type] = array(); |
||
161 | $depth = 0; |
||
162 | $parentList = 0; |
||
163 | $position = 0; |
||
164 | $newBlock = true; |
||
165 | $listCount = 0; |
||
166 | $contentCount = 0; |
||
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; |
||
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']); |
||
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])) { |
|
247 | $this->newParentTag = $arrayData[0]; |
||
248 | $this->newListType = $this->getAndStripTag($arrayData[0]); |
||
0 ignored issues
–
show
|
|||
249 | } |
||
250 | |||
251 | View Code Duplication | if ($index == 'old' && $this->isOpeningTag($arrayData[0])) { |
|
252 | $this->oldParentTag = $arrayData[0]; |
||
253 | $this->oldListType = $this->getAndStripTag($arrayData[0]); |
||
0 ignored issues
–
show
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(); |
||
338 | $taken = array(); |
||
339 | $takenItems = array(); |
||
340 | $absoluteMatch = 100; |
||
341 | foreach ($bestMatchPercentages as $item => $percentages) { |
||
342 | $highestMatch = -1; |
||
343 | $highestMatchKey = -1; |
||
344 | $takeItemKey = -1; |
||
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
$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
Loading history...
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
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); |
||
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
$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
Loading history...
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
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
$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
Loading history...
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
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; |
||
396 | $takenItems[] = $takenItemKey; |
||
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; |
||
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
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
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
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...
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
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...
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']); |
||
469 | $newList = $this->childLists['new'][$match['new']]; |
||
470 | $oldList = $this->getListByMatch($match, 'old'); |
||
0 ignored issues
–
show
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...
|
|||
471 | $this->content .= $this->addListElementToContent($newList, $oldList, $match, $index, 'new'); |
||
0 ignored issues
–
show
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...
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
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
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
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...
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
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...
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
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), |
||
516 | $this->convertListContentArrayToString($newList), |
||
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']) |
||
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(); |
||
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']); |
||
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 |
||
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); |
||
673 | $result = array( |
||
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) |
||
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); |
||
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 |
||
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; |
||
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) |
||
811 | { |
||
812 | $lematches = array(); |
||
813 | $arrayDepth = 0; |
||
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; |
||
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; |
||
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)) { |
||
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)) { |
||
939 | return true; |
||
940 | } |
||
941 | |||
942 | return false; |
||
943 | } |
||
944 | } |
||
945 |
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
will produce issues in the first and second line, while this second example
will produce no issues.