WP_Text_Diff_Renderer_Table

Table renderer to display the diff lines.

Defined (1)

The class is defined in the following location(s).

/wp-includes/class-wp-text-diff-renderer-table.php  
  1. class WP_Text_Diff_Renderer_Table extends Text_Diff_Renderer { 
  2.  
  3. /** 
  4. * @see Text_Diff_Renderer::_leading_context_lines 
  5. * @var int 
  6. * @access public 
  7. * @since 2.6.0 
  8. */ 
  9. public $_leading_context_lines = 10000; 
  10.  
  11. /** 
  12. * @see Text_Diff_Renderer::_trailing_context_lines 
  13. * @var int 
  14. * @access public 
  15. * @since 2.6.0 
  16. */ 
  17. public $_trailing_context_lines = 10000; 
  18.  
  19. /** 
  20. * Threshold for when a diff should be saved or omitted. 
  21. * @var float 
  22. * @access protected 
  23. * @since 2.6.0 
  24. */ 
  25. protected $_diff_threshold = 0.6; 
  26.  
  27. /** 
  28. * Inline display helper object name. 
  29. * @var string 
  30. * @access protected 
  31. * @since 2.6.0 
  32. */ 
  33. protected $inline_diff_renderer = 'WP_Text_Diff_Renderer_inline'; 
  34.  
  35. /** 
  36. * Should we show the split view or not 
  37. * @var string 
  38. * @access protected 
  39. * @since 3.6.0 
  40. */ 
  41. protected $_show_split_view = true; 
  42.  
  43. protected $compat_fields = array( '_show_split_view', 'inline_diff_renderer', '_diff_threshold' ); 
  44.  
  45. /** 
  46. * Constructor - Call parent constructor with params array. 
  47. * This will set class properties based on the key value pairs in the array. 
  48. * @since 2.6.0 
  49. * @param array $params 
  50. */ 
  51. public function __construct( $params = array() ) { 
  52. parent::__construct( $params ); 
  53. if ( isset( $params[ 'show_split_view' ] ) ) 
  54. $this->_show_split_view = $params[ 'show_split_view' ]; 
  55.  
  56. /** 
  57. * @ignore 
  58. * @param string $header 
  59. * @return string 
  60. */ 
  61. public function _startBlock( $header ) { 
  62. return ''; 
  63.  
  64. /** 
  65. * @ignore 
  66. * @param array $lines 
  67. * @param string $prefix 
  68. */ 
  69. public function _lines( $lines, $prefix=' ' ) { 
  70.  
  71. /** 
  72. * @ignore 
  73. * @param string $line HTML-escape the value. 
  74. * @return string 
  75. */ 
  76. public function addedLine( $line ) { 
  77. return "<td class='diff-addedline'>{$line}</td>"; 
  78.  
  79.  
  80. /** 
  81. * @ignore 
  82. * @param string $line HTML-escape the value. 
  83. * @return string 
  84. */ 
  85. public function deletedLine( $line ) { 
  86. return "<td class='diff-deletedline'>{$line}</td>"; 
  87.  
  88. /** 
  89. * @ignore 
  90. * @param string $line HTML-escape the value. 
  91. * @return string 
  92. */ 
  93. public function contextLine( $line ) { 
  94. return "<td class='diff-context'>{$line}</td>"; 
  95.  
  96. /** 
  97. * @ignore 
  98. * @return string 
  99. */ 
  100. public function emptyLine() { 
  101. return '<td> </td>'; 
  102.  
  103. /** 
  104. * @ignore 
  105. * @access public 
  106. * @param array $lines 
  107. * @param bool $encode 
  108. * @return string 
  109. */ 
  110. public function _added( $lines, $encode = true ) { 
  111. $r = ''; 
  112. foreach ($lines as $line) { 
  113. if ( $encode ) { 
  114. $processed_line = htmlspecialchars( $line ); 
  115.  
  116. /** 
  117. * Contextually filters a diffed line. 
  118. * Filters TextDiff processing of diffed line. By default, diffs are processed with 
  119. * htmlspecialchars. Use this filter to remove or change the processing. Passes a context 
  120. * indicating if the line is added, deleted or unchanged. 
  121. * @since 4.1.0 
  122. * @param String $processed_line The processed diffed line. 
  123. * @param String $line The unprocessed diffed line. 
  124. * @param string null The line context. Values are 'added', 'deleted' or 'unchanged'. 
  125. */ 
  126. $line = apply_filters( 'process_text_diff_html', $processed_line, $line, 'added' ); 
  127.  
  128. if ( $this->_show_split_view ) { 
  129. $r .= '<tr>' . $this->emptyLine() . $this->emptyLine() . $this->addedLine( $line ) . "</tr>\n"; 
  130. } else { 
  131. $r .= '<tr>' . $this->addedLine( $line ) . "</tr>\n"; 
  132. return $r; 
  133.  
  134. /** 
  135. * @ignore 
  136. * @access public 
  137. * @param array $lines 
  138. * @param bool $encode 
  139. * @return string 
  140. */ 
  141. public function _deleted( $lines, $encode = true ) { 
  142. $r = ''; 
  143. foreach ($lines as $line) { 
  144. if ( $encode ) { 
  145. $processed_line = htmlspecialchars( $line ); 
  146.  
  147. /** This filter is documented in wp-includes/wp-diff.php */ 
  148. $line = apply_filters( 'process_text_diff_html', $processed_line, $line, 'deleted' ); 
  149. if ( $this->_show_split_view ) { 
  150. $r .= '<tr>' . $this->deletedLine( $line ) . $this->emptyLine() . $this->emptyLine() . "</tr>\n"; 
  151. } else { 
  152. $r .= '<tr>' . $this->deletedLine( $line ) . "</tr>\n"; 
  153.  
  154. return $r; 
  155.  
  156. /** 
  157. * @ignore 
  158. * @access public 
  159. * @param array $lines 
  160. * @param bool $encode 
  161. * @return string 
  162. */ 
  163. public function _context( $lines, $encode = true ) { 
  164. $r = ''; 
  165. foreach ($lines as $line) { 
  166. if ( $encode ) { 
  167. $processed_line = htmlspecialchars( $line ); 
  168.  
  169. /** This filter is documented in wp-includes/wp-diff.php */ 
  170. $line = apply_filters( 'process_text_diff_html', $processed_line, $line, 'unchanged' ); 
  171. if ( $this->_show_split_view ) { 
  172. $r .= '<tr>' . $this->contextLine( $line ) . $this->emptyLine() . $this->contextLine( $line ) . "</tr>\n"; 
  173. } else { 
  174. $r .= '<tr>' . $this->contextLine( $line ) . "</tr>\n"; 
  175. return $r; 
  176.  
  177. /** 
  178. * Process changed lines to do word-by-word diffs for extra highlighting. 
  179. * (TRAC style) sometimes these lines can actually be deleted or added rows. 
  180. * We do additional processing to figure that out 
  181. * @access public 
  182. * @since 2.6.0 
  183. * @param array $orig 
  184. * @param array $final 
  185. * @return string 
  186. */ 
  187. public function _changed( $orig, $final ) { 
  188. $r = ''; 
  189.  
  190. // Does the aforementioned additional processing 
  191. // *_matches tell what rows are "the same" in orig and final. Those pairs will be diffed to get word changes 
  192. // match is numeric: an index in other column 
  193. // match is 'X': no match. It is a new row 
  194. // *_rows are column vectors for the orig column and the final column. 
  195. // row >= 0: an indix of the $orig or $final array 
  196. // row < 0: a blank row for that column 
  197. list($orig_matches, $final_matches, $orig_rows, $final_rows) = $this->interleave_changed_lines( $orig, $final ); 
  198.  
  199. // These will hold the word changes as determined by an inline diff 
  200. $orig_diffs = array(); 
  201. $final_diffs = array(); 
  202.  
  203. // Compute word diffs for each matched pair using the inline diff 
  204. foreach ( $orig_matches as $o => $f ) { 
  205. if ( is_numeric($o) && is_numeric($f) ) { 
  206. $text_diff = new Text_Diff( 'auto', array( array($orig[$o]), array($final[$f]) ) ); 
  207. $renderer = new $this->inline_diff_renderer; 
  208. $diff = $renderer->render( $text_diff ); 
  209.  
  210. // If they're too different, don't include any <ins> or <dels> 
  211. if ( preg_match_all( '!(<ins>.*?</ins>|<del>.*?</del>)!', $diff, $diff_matches ) ) { 
  212. // length of all text between <ins> or <del> 
  213. $stripped_matches = strlen(strip_tags( join(' ', $diff_matches[0]) )); 
  214. // since we count lengith of text between <ins> or <del> (instead of picking just one),  
  215. // we double the length of chars not in those tags. 
  216. $stripped_diff = strlen(strip_tags( $diff )) * 2 - $stripped_matches; 
  217. $diff_ratio = $stripped_matches / $stripped_diff; 
  218. if ( $diff_ratio > $this->_diff_threshold ) 
  219. continue; // Too different. Don't save diffs. 
  220.  
  221. // Un-inline the diffs by removing del or ins 
  222. $orig_diffs[$o] = preg_replace( '|<ins>.*?</ins>|', '', $diff ); 
  223. $final_diffs[$f] = preg_replace( '|<del>.*?</del>|', '', $diff ); 
  224.  
  225. foreach ( array_keys($orig_rows) as $row ) { 
  226. // Both columns have blanks. Ignore them. 
  227. if ( $orig_rows[$row] < 0 && $final_rows[$row] < 0 ) 
  228. continue; 
  229.  
  230. // If we have a word based diff, use it. Otherwise, use the normal line. 
  231. if ( isset( $orig_diffs[$orig_rows[$row]] ) ) 
  232. $orig_line = $orig_diffs[$orig_rows[$row]]; 
  233. elseif ( isset( $orig[$orig_rows[$row]] ) ) 
  234. $orig_line = htmlspecialchars($orig[$orig_rows[$row]]); 
  235. else 
  236. $orig_line = ''; 
  237.  
  238. if ( isset( $final_diffs[$final_rows[$row]] ) ) 
  239. $final_line = $final_diffs[$final_rows[$row]]; 
  240. elseif ( isset( $final[$final_rows[$row]] ) ) 
  241. $final_line = htmlspecialchars($final[$final_rows[$row]]); 
  242. else 
  243. $final_line = ''; 
  244.  
  245. if ( $orig_rows[$row] < 0 ) { // Orig is blank. This is really an added row. 
  246. $r .= $this->_added( array($final_line), false ); 
  247. } elseif ( $final_rows[$row] < 0 ) { // Final is blank. This is really a deleted row. 
  248. $r .= $this->_deleted( array($orig_line), false ); 
  249. } else { // A true changed row. 
  250. if ( $this->_show_split_view ) { 
  251. $r .= '<tr>' . $this->deletedLine( $orig_line ) . $this->emptyLine() . $this->addedLine( $final_line ) . "</tr>\n"; 
  252. } else { 
  253. $r .= '<tr>' . $this->deletedLine( $orig_line ) . "</tr><tr>" . $this->addedLine( $final_line ) . "</tr>\n"; 
  254.  
  255. return $r; 
  256.  
  257. /** 
  258. * Takes changed blocks and matches which rows in orig turned into which rows in final. 
  259. * Returns 
  260. * *_matches ( which rows match with which ) 
  261. * *_rows ( order of rows in each column interleaved with blank rows as 
  262. * necessary ) 
  263. * @since 2.6.0 
  264. * @param array $orig 
  265. * @param array $final 
  266. * @return array 
  267. */ 
  268. public function interleave_changed_lines( $orig, $final ) { 
  269.  
  270. // Contains all pairwise string comparisons. Keys are such that this need only be a one dimensional array. 
  271. $matches = array(); 
  272. foreach ( array_keys($orig) as $o ) { 
  273. foreach ( array_keys($final) as $f ) { 
  274. $matches["$o, $f"] = $this->compute_string_distance( $orig[$o], $final[$f] ); 
  275. asort($matches); // Order by string distance. 
  276.  
  277. $orig_matches = array(); 
  278. $final_matches = array(); 
  279.  
  280. foreach ( $matches as $keys => $difference ) { 
  281. list($o, $f) = explode(', ', $keys); 
  282. $o = (int) $o; 
  283. $f = (int) $f; 
  284.  
  285. // Already have better matches for these guys 
  286. if ( isset($orig_matches[$o]) && isset($final_matches[$f]) ) 
  287. continue; 
  288.  
  289. // First match for these guys. Must be best match 
  290. if ( !isset($orig_matches[$o]) && !isset($final_matches[$f]) ) { 
  291. $orig_matches[$o] = $f; 
  292. $final_matches[$f] = $o; 
  293. continue; 
  294.  
  295. // Best match of this final is already taken? Must mean this final is a new row. 
  296. if ( isset($orig_matches[$o]) ) 
  297. $final_matches[$f] = 'x'; 
  298.  
  299. // Best match of this orig is already taken? Must mean this orig is a deleted row. 
  300. elseif ( isset($final_matches[$f]) ) 
  301. $orig_matches[$o] = 'x'; 
  302.  
  303. // We read the text in this order 
  304. ksort($orig_matches); 
  305. ksort($final_matches); 
  306.  
  307. // Stores rows and blanks for each column. 
  308. $orig_rows = $orig_rows_copy = array_keys($orig_matches); 
  309. $final_rows = array_keys($final_matches); 
  310.  
  311. // Interleaves rows with blanks to keep matches aligned. 
  312. // We may end up with some extraneous blank rows, but we'll just ignore them later. 
  313. foreach ( $orig_rows_copy as $orig_row ) { 
  314. $final_pos = array_search($orig_matches[$orig_row], $final_rows, true); 
  315. $orig_pos = (int) array_search($orig_row, $orig_rows, true); 
  316.  
  317. if ( false === $final_pos ) { // This orig is paired with a blank final. 
  318. array_splice( $final_rows, $orig_pos, 0, -1 ); 
  319. } elseif ( $final_pos < $orig_pos ) { // This orig's match is up a ways. Pad final with blank rows. 
  320. $diff_pos = $final_pos - $orig_pos; 
  321. while ( $diff_pos < 0 ) 
  322. array_splice( $final_rows, $orig_pos, 0, $diff_pos++ ); 
  323. } elseif ( $final_pos > $orig_pos ) { // This orig's match is down a ways. Pad orig with blank rows. 
  324. $diff_pos = $orig_pos - $final_pos; 
  325. while ( $diff_pos < 0 ) 
  326. array_splice( $orig_rows, $orig_pos, 0, $diff_pos++ ); 
  327.  
  328. // Pad the ends with blank rows if the columns aren't the same length 
  329. $diff_count = count($orig_rows) - count($final_rows); 
  330. if ( $diff_count < 0 ) { 
  331. while ( $diff_count < 0 ) 
  332. array_push($orig_rows, $diff_count++); 
  333. } elseif ( $diff_count > 0 ) { 
  334. $diff_count = -1 * $diff_count; 
  335. while ( $diff_count < 0 ) 
  336. array_push($final_rows, $diff_count++); 
  337.  
  338. return array($orig_matches, $final_matches, $orig_rows, $final_rows); 
  339.  
  340. /** 
  341. * Computes a number that is intended to reflect the "distance" between two strings. 
  342. * @since 2.6.0 
  343. * @param string $string1 
  344. * @param string $string2 
  345. * @return int 
  346. */ 
  347. public function compute_string_distance( $string1, $string2 ) { 
  348. // Vectors containing character frequency for all chars in each string 
  349. $chars1 = count_chars($string1); 
  350. $chars2 = count_chars($string2); 
  351.  
  352. // L1-norm of difference vector. 
  353. $difference = array_sum( array_map( array($this, 'difference'), $chars1, $chars2 ) ); 
  354.  
  355. // $string1 has zero length? Odd. Give huge penalty by not dividing. 
  356. if ( !$string1 ) 
  357. return $difference; 
  358.  
  359. // Return distance per character (of string1). 
  360. return $difference / strlen($string1); 
  361.  
  362. /** 
  363. * @ignore 
  364. * @since 2.6.0 
  365. * @param int $a 
  366. * @param int $b 
  367. * @return int 
  368. */ 
  369. public function difference( $a, $b ) { 
  370. return abs( $a - $b ); 
  371.  
  372. /** 
  373. * Make private properties readable for backward compatibility. 
  374. * @since 4.0.0 
  375. * @access public 
  376. * @param string $name Property to get. 
  377. * @return mixed Property. 
  378. */ 
  379. public function __get( $name ) { 
  380. if ( in_array( $name, $this->compat_fields ) ) { 
  381. return $this->$name; 
  382.  
  383. /** 
  384. * Make private properties settable for backward compatibility. 
  385. * @since 4.0.0 
  386. * @access public 
  387. * @param string $name Property to check if set. 
  388. * @param mixed $value Property value. 
  389. * @return mixed Newly-set property. 
  390. */ 
  391. public function __set( $name, $value ) { 
  392. if ( in_array( $name, $this->compat_fields ) ) { 
  393. return $this->$name = $value; 
  394.  
  395. /** 
  396. * Make private properties checkable for backward compatibility. 
  397. * @since 4.0.0 
  398. * @access public 
  399. * @param string $name Property to check if set. 
  400. * @return bool Whether the property is set. 
  401. */ 
  402. public function __isset( $name ) { 
  403. if ( in_array( $name, $this->compat_fields ) ) { 
  404. return isset( $this->$name ); 
  405.  
  406. /** 
  407. * Make private properties un-settable for backward compatibility. 
  408. * @since 4.0.0 
  409. * @access public 
  410. * @param string $name Property to unset. 
  411. */ 
  412. public function __unset( $name ) { 
  413. if ( in_array( $name, $this->compat_fields ) ) { 
  414. unset( $this->$name );