interleave_changed_lines()   F
last analyzed

Complexity

Conditions 20
Paths 324

Size

Total Lines 79
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 48
nc 324
nop 2
dl 0
loc 79
rs 3.8611
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Diff API: WP_Text_Diff_Renderer_Table class
4
 *
5
 * @package WordPress
6
 * @subpackage Diff
7
 * @since 4.7.0
8
 */
9
10
/**
11
 * Table renderer to display the diff lines.
12
 *
13
 * @since 2.6.0
14
 * @uses Text_Diff_Renderer Extends
15
 */
16
class WP_Text_Diff_Renderer_Table extends Text_Diff_Renderer {
17
18
	/**
19
	 * @see Text_Diff_Renderer::_leading_context_lines
20
	 * @var int
21
	 * @access public
22
	 * @since 2.6.0
23
	 */
24
	public $_leading_context_lines  = 10000;
25
26
	/**
27
	 * @see Text_Diff_Renderer::_trailing_context_lines
28
	 * @var int
29
	 * @access public
30
	 * @since 2.6.0
31
	 */
32
	public $_trailing_context_lines = 10000;
33
34
	/**
35
	 * Threshold for when a diff should be saved or omitted.
36
	 *
37
	 * @var float
38
	 * @access protected
39
	 * @since 2.6.0
40
	 */
41
	protected $_diff_threshold = 0.6;
42
43
	/**
44
	 * Inline display helper object name.
45
	 *
46
	 * @var string
47
	 * @access protected
48
	 * @since 2.6.0
49
	 */
50
	protected $inline_diff_renderer = 'WP_Text_Diff_Renderer_inline';
51
52
	/**
53
	 * Should we show the split view or not
54
	 *
55
	 * @var string
56
	 * @access protected
57
	 * @since 3.6.0
58
	 */
59
	protected $_show_split_view = true;
60
61
	protected $compat_fields = array( '_show_split_view', 'inline_diff_renderer', '_diff_threshold' );
62
63
	/**
64
	 * Constructor - Call parent constructor with params array.
65
	 *
66
	 * This will set class properties based on the key value pairs in the array.
67
	 *
68
	 * @since 2.6.0
69
	 *
70
	 * @param array $params
71
	 */
72
	public function __construct( $params = array() ) {
73
		parent::__construct( $params );
74
		if ( isset( $params[ 'show_split_view' ] ) )
75
			$this->_show_split_view = $params[ 'show_split_view' ];
76
	}
77
78
	/**
79
	 * @ignore
80
	 *
81
	 * @param string $header
82
	 * @return string
83
	 */
84
	public function _startBlock( $header ) {
85
		return '';
86
	}
87
88
	/**
89
	 * @ignore
90
	 *
91
	 * @param array $lines
92
	 * @param string $prefix
93
	 */
94
	public function _lines( $lines, $prefix=' ' ) {
95
	}
96
97
	/**
98
	 * @ignore
99
	 *
100
	 * @param string $line HTML-escape the value.
101
	 * @return string
102
	 */
103
	public function addedLine( $line ) {
104
		return "<td class='diff-addedline'>{$line}</td>";
105
106
	}
107
108
	/**
109
	 * @ignore
110
	 *
111
	 * @param string $line HTML-escape the value.
112
	 * @return string
113
	 */
114
	public function deletedLine( $line ) {
115
		return "<td class='diff-deletedline'>{$line}</td>";
116
	}
117
118
	/**
119
	 * @ignore
120
	 *
121
	 * @param string $line HTML-escape the value.
122
	 * @return string
123
	 */
124
	public function contextLine( $line ) {
125
		return "<td class='diff-context'>{$line}</td>";
126
	}
127
128
	/**
129
	 * @ignore
130
	 *
131
	 * @return string
132
	 */
133
	public function emptyLine() {
134
		return '<td>&nbsp;</td>';
135
	}
136
137
	/**
138
	 * @ignore
139
	 * @access public
140
	 *
141
	 * @param array $lines
142
	 * @param bool $encode
143
	 * @return string
144
	 */
145 View Code Duplication
	public function _added( $lines, $encode = true ) {
146
		$r = '';
147
		foreach ($lines as $line) {
148
			if ( $encode ) {
149
				$processed_line = htmlspecialchars( $line );
150
151
				/**
152
				 * Contextually filters a diffed line.
153
				 *
154
				 * Filters TextDiff processing of diffed line. By default, diffs are processed with
155
				 * htmlspecialchars. Use this filter to remove or change the processing. Passes a context
156
				 * indicating if the line is added, deleted or unchanged.
157
				 *
158
				 * @since 4.1.0
159
				 *
160
				 * @param String $processed_line The processed diffed line.
161
				 * @param String $line           The unprocessed diffed line.
162
		 		 * @param string null            The line context. Values are 'added', 'deleted' or 'unchanged'.
163
				 */
164
				$line = apply_filters( 'process_text_diff_html', $processed_line, $line, 'added' );
165
			}
166
167
			if ( $this->_show_split_view ) {
168
				$r .= '<tr>' . $this->emptyLine() . $this->emptyLine() . $this->addedLine( $line ) . "</tr>\n";
169
			} else {
170
				$r .= '<tr>' . $this->addedLine( $line ) . "</tr>\n";
171
			}
172
		}
173
		return $r;
174
	}
175
176
	/**
177
	 * @ignore
178
	 * @access public
179
	 *
180
	 * @param array $lines
181
	 * @param bool $encode
182
	 * @return string
183
	 */
184 View Code Duplication
	public function _deleted( $lines, $encode = true ) {
185
		$r = '';
186
		foreach ($lines as $line) {
187
			if ( $encode ) {
188
				$processed_line = htmlspecialchars( $line );
189
190
				/** This filter is documented in wp-includes/wp-diff.php */
191
				$line = apply_filters( 'process_text_diff_html', $processed_line, $line, 'deleted' );
192
			}
193
			if ( $this->_show_split_view ) {
194
				$r .= '<tr>' . $this->deletedLine( $line ) . $this->emptyLine() . $this->emptyLine() . "</tr>\n";
195
			} else {
196
				$r .= '<tr>' . $this->deletedLine( $line ) . "</tr>\n";
197
			}
198
199
		}
200
		return $r;
201
	}
202
203
	/**
204
	 * @ignore
205
	 * @access public
206
	 *
207
	 * @param array $lines
208
	 * @param bool $encode
209
	 * @return string
210
	 */
211 View Code Duplication
	public function _context( $lines, $encode = true ) {
212
		$r = '';
213
		foreach ($lines as $line) {
214
			if ( $encode ) {
215
				$processed_line = htmlspecialchars( $line );
216
217
				/** This filter is documented in wp-includes/wp-diff.php */
218
				$line = apply_filters( 'process_text_diff_html', $processed_line, $line, 'unchanged' );
219
			}
220
			if (  $this->_show_split_view ) {
221
				$r .= '<tr>' . $this->contextLine( $line ) . $this->emptyLine() . $this->contextLine( $line )  . "</tr>\n";
222
			} else {
223
				$r .= '<tr>' . $this->contextLine( $line ) . "</tr>\n";
224
			}
225
		}
226
		return $r;
227
	}
228
229
	/**
230
	 * Process changed lines to do word-by-word diffs for extra highlighting.
231
	 *
232
	 * (TRAC style) sometimes these lines can actually be deleted or added rows.
233
	 * We do additional processing to figure that out
234
	 *
235
	 * @access public
236
	 * @since 2.6.0
237
	 *
238
	 * @param array $orig
239
	 * @param array $final
240
	 * @return string
241
	 */
242
	public function _changed( $orig, $final ) {
243
		$r = '';
244
245
		// Does the aforementioned additional processing
246
		// *_matches tell what rows are "the same" in orig and final. Those pairs will be diffed to get word changes
247
		//	match is numeric: an index in other column
248
		//	match is 'X': no match. It is a new row
249
		// *_rows are column vectors for the orig column and the final column.
250
		//	row >= 0: an indix of the $orig or $final array
251
		//	row  < 0: a blank row for that column
252
		list($orig_matches, $final_matches, $orig_rows, $final_rows) = $this->interleave_changed_lines( $orig, $final );
0 ignored issues
show
Unused Code introduced by
The assignment to $final_matches is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
253
254
		// These will hold the word changes as determined by an inline diff
255
		$orig_diffs  = array();
256
		$final_diffs = array();
257
258
		// Compute word diffs for each matched pair using the inline diff
259
		foreach ( $orig_matches as $o => $f ) {
260
			if ( is_numeric($o) && is_numeric($f) ) {
261
				$text_diff = new Text_Diff( 'auto', array( array($orig[$o]), array($final[$f]) ) );
262
				$renderer = new $this->inline_diff_renderer;
263
				$diff = $renderer->render( $text_diff );
264
265
				// If they're too different, don't include any <ins> or <dels>
266
				if ( preg_match_all( '!(<ins>.*?</ins>|<del>.*?</del>)!', $diff, $diff_matches ) ) {
267
					// length of all text between <ins> or <del>
268
					$stripped_matches = strlen(strip_tags( join(' ', $diff_matches[0]) ));
269
					// since we count lengith of text between <ins> or <del> (instead of picking just one),
270
					//	we double the length of chars not in those tags.
271
					$stripped_diff = strlen(strip_tags( $diff )) * 2 - $stripped_matches;
272
					$diff_ratio = $stripped_matches / $stripped_diff;
273
					if ( $diff_ratio > $this->_diff_threshold )
274
						continue; // Too different. Don't save diffs.
275
				}
276
277
				// Un-inline the diffs by removing del or ins
278
				$orig_diffs[$o]  = preg_replace( '|<ins>.*?</ins>|', '', $diff );
279
				$final_diffs[$f] = preg_replace( '|<del>.*?</del>|', '', $diff );
280
			}
281
		}
282
283
		foreach ( array_keys($orig_rows) as $row ) {
284
			// Both columns have blanks. Ignore them.
285
			if ( $orig_rows[$row] < 0 && $final_rows[$row] < 0 )
286
				continue;
287
288
			// If we have a word based diff, use it. Otherwise, use the normal line.
289
			if ( isset( $orig_diffs[$orig_rows[$row]] ) )
290
				$orig_line = $orig_diffs[$orig_rows[$row]];
291
			elseif ( isset( $orig[$orig_rows[$row]] ) )
292
				$orig_line = htmlspecialchars($orig[$orig_rows[$row]]);
293
			else
294
				$orig_line = '';
295
296
			if ( isset( $final_diffs[$final_rows[$row]] ) )
297
				$final_line = $final_diffs[$final_rows[$row]];
298
			elseif ( isset( $final[$final_rows[$row]] ) )
299
				$final_line = htmlspecialchars($final[$final_rows[$row]]);
300
			else
301
				$final_line = '';
302
303
			if ( $orig_rows[$row] < 0 ) { // Orig is blank. This is really an added row.
304
				$r .= $this->_added( array($final_line), false );
305
			} elseif ( $final_rows[$row] < 0 ) { // Final is blank. This is really a deleted row.
306
				$r .= $this->_deleted( array($orig_line), false );
307
			} else { // A true changed row.
308
				if ( $this->_show_split_view ) {
309
					$r .= '<tr>' . $this->deletedLine( $orig_line ) . $this->emptyLine() . $this->addedLine( $final_line ) . "</tr>\n";
310
				} else {
311
					$r .= '<tr>' . $this->deletedLine( $orig_line ) . "</tr><tr>" . $this->addedLine( $final_line ) . "</tr>\n";
312
				}
313
			}
314
		}
315
316
		return $r;
317
	}
318
319
	/**
320
	 * Takes changed blocks and matches which rows in orig turned into which rows in final.
321
	 *
322
	 * Returns
323
	 *	*_matches ( which rows match with which )
324
	 *	*_rows ( order of rows in each column interleaved with blank rows as
325
	 *		necessary )
326
	 *
327
	 * @since 2.6.0
328
	 *
329
	 * @param array $orig
330
	 * @param array $final
331
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
332
	 */
333
	public function interleave_changed_lines( $orig, $final ) {
334
335
		// Contains all pairwise string comparisons. Keys are such that this need only be a one dimensional array.
336
		$matches = array();
337
		foreach ( array_keys($orig) as $o ) {
338
			foreach ( array_keys($final) as $f ) {
339
				$matches["$o,$f"] = $this->compute_string_distance( $orig[$o], $final[$f] );
340
			}
341
		}
342
		asort($matches); // Order by string distance.
343
344
		$orig_matches  = array();
345
		$final_matches = array();
346
347
		foreach ( $matches as $keys => $difference ) {
348
			list($o, $f) = explode(',', $keys);
349
			$o = (int) $o;
350
			$f = (int) $f;
351
352
			// Already have better matches for these guys
353
			if ( isset($orig_matches[$o]) && isset($final_matches[$f]) )
354
				continue;
355
356
			// First match for these guys. Must be best match
357
			if ( !isset($orig_matches[$o]) && !isset($final_matches[$f]) ) {
358
				$orig_matches[$o] = $f;
359
				$final_matches[$f] = $o;
360
				continue;
361
			}
362
363
			// Best match of this final is already taken?  Must mean this final is a new row.
364
			if ( isset($orig_matches[$o]) )
365
				$final_matches[$f] = 'x';
366
367
			// Best match of this orig is already taken?  Must mean this orig is a deleted row.
368
			elseif ( isset($final_matches[$f]) )
369
				$orig_matches[$o] = 'x';
370
		}
371
372
		// We read the text in this order
373
		ksort($orig_matches);
374
		ksort($final_matches);
375
376
		// Stores rows and blanks for each column.
377
		$orig_rows = $orig_rows_copy = array_keys($orig_matches);
378
		$final_rows = array_keys($final_matches);
379
380
		// Interleaves rows with blanks to keep matches aligned.
381
		// We may end up with some extraneous blank rows, but we'll just ignore them later.
382
		foreach ( $orig_rows_copy as $orig_row ) {
383
			$final_pos = array_search($orig_matches[$orig_row], $final_rows, true);
384
			$orig_pos = (int) array_search($orig_row, $orig_rows, true);
385
386
			if ( false === $final_pos ) { // This orig is paired with a blank final.
387
				array_splice( $final_rows, $orig_pos, 0, -1 );
388
			} elseif ( $final_pos < $orig_pos ) { // This orig's match is up a ways. Pad final with blank rows.
389
				$diff_pos = $final_pos - $orig_pos;
390
				while ( $diff_pos < 0 )
391
					array_splice( $final_rows, $orig_pos, 0, $diff_pos++ );
392
			} elseif ( $final_pos > $orig_pos ) { // This orig's match is down a ways. Pad orig with blank rows.
393
				$diff_pos = $orig_pos - $final_pos;
394
				while ( $diff_pos < 0 )
395
					array_splice( $orig_rows, $orig_pos, 0, $diff_pos++ );
396
			}
397
		}
398
399
		// Pad the ends with blank rows if the columns aren't the same length
400
		$diff_count = count($orig_rows) - count($final_rows);
401
		if ( $diff_count < 0 ) {
402
			while ( $diff_count < 0 )
403
				array_push($orig_rows, $diff_count++);
404
		} elseif ( $diff_count > 0 ) {
405
			$diff_count = -1 * $diff_count;
406
			while ( $diff_count < 0 )
407
				array_push($final_rows, $diff_count++);
408
		}
409
410
		return array($orig_matches, $final_matches, $orig_rows, $final_rows);
411
	}
412
413
	/**
414
	 * Computes a number that is intended to reflect the "distance" between two strings.
415
	 *
416
	 * @since 2.6.0
417
	 *
418
	 * @param string $string1
419
	 * @param string $string2
420
	 * @return int
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...
421
	 */
422
	public function compute_string_distance( $string1, $string2 ) {
423
		// Vectors containing character frequency for all chars in each string
424
		$chars1 = count_chars($string1);
425
		$chars2 = count_chars($string2);
426
427
		// L1-norm of difference vector.
428
		$difference = array_sum( array_map( array($this, 'difference'), $chars1, $chars2 ) );
429
430
		// $string1 has zero length? Odd. Give huge penalty by not dividing.
431
		if ( !$string1 )
432
			return $difference;
433
434
		// Return distance per character (of string1).
435
		return $difference / strlen($string1);
436
	}
437
438
	/**
439
	 * @ignore
440
	 * @since 2.6.0
441
	 *
442
	 * @param int $a
443
	 * @param int $b
444
	 * @return int
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...
445
	 */
446
	public function difference( $a, $b ) {
447
		return abs( $a - $b );
448
	}
449
450
	/**
451
	 * Make private properties readable for backward compatibility.
452
	 *
453
	 * @since 4.0.0
454
	 * @access public
455
	 *
456
	 * @param string $name Property to get.
457
	 * @return mixed Property.
458
	 */
459
	public function __get( $name ) {
460
		if ( in_array( $name, $this->compat_fields ) ) {
461
			return $this->$name;
462
		}
463
	}
464
465
	/**
466
	 * Make private properties settable for backward compatibility.
467
	 *
468
	 * @since 4.0.0
469
	 * @access public
470
	 *
471
	 * @param string $name  Property to check if set.
472
	 * @param mixed  $value Property value.
473
	 * @return mixed Newly-set property.
474
	 */
475
	public function __set( $name, $value ) {
476
		if ( in_array( $name, $this->compat_fields ) ) {
477
			return $this->$name = $value;
478
		}
479
	}
480
481
	/**
482
	 * Make private properties checkable for backward compatibility.
483
	 *
484
	 * @since 4.0.0
485
	 * @access public
486
	 *
487
	 * @param string $name Property to check if set.
488
	 * @return bool Whether the property is set.
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

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...
489
	 */
490
	public function __isset( $name ) {
491
		if ( in_array( $name, $this->compat_fields ) ) {
492
			return isset( $this->$name );
493
		}
494
	}
495
496
	/**
497
	 * Make private properties un-settable for backward compatibility.
498
	 *
499
	 * @since 4.0.0
500
	 * @access public
501
	 *
502
	 * @param string $name Property to unset.
503
	 */
504
	public function __unset( $name ) {
505
		if ( in_array( $name, $this->compat_fields ) ) {
506
			unset( $this->$name );
507
		}
508
	}
509
}
510