/includes/common/formatting.php

  1. <?php 
  2.  
  3. /** 
  4. * bbPress Formatting 
  5. * 
  6. * @package bbPress 
  7. * @subpackage Formatting 
  8. */ 
  9.  
  10. // Exit if accessed directly 
  11. if ( !defined( 'ABSPATH' ) ) exit; 
  12.  
  13. /** Kses **********************************************************************/ 
  14.  
  15. /** 
  16. * Custom allowed tags for forum topics and replies 
  17. * 
  18. * Allows all users to post links, quotes, code, formatting, lists, and images 
  19. * 
  20. * @since bbPress (r4603) 
  21. * 
  22. * @return array Associative array of allowed tags and attributes 
  23. */ 
  24. function bbp_kses_allowed_tags() { 
  25. return apply_filters( 'bbp_kses_allowed_tags', array( 
  26.  
  27. // Links 
  28. 'a' => array( 
  29. 'href' => array(),  
  30. 'title' => array(),  
  31. 'rel' => array(),  
  32. 'target' => array() 
  33. ),  
  34.  
  35. // Quotes 
  36. 'blockquote' => array( 
  37. 'cite' => array() 
  38. ),  
  39.  
  40. // Code 
  41. 'code' => array(),  
  42. 'pre' => array(),  
  43.  
  44. // Formatting 
  45. 'em' => array(),  
  46. 'strong' => array(),  
  47. 'del' => array( 
  48. 'datetime' => true,  
  49. ),  
  50.  
  51. // Lists 
  52. 'ul' => array(),  
  53. 'ol' => array( 
  54. 'start' => true,  
  55. ),  
  56. 'li' => array(),  
  57.  
  58. // Images 
  59. 'img' => array( 
  60. 'src' => true,  
  61. 'border' => true,  
  62. 'alt' => true,  
  63. 'height' => true,  
  64. 'width' => true,  
  65. ) ); 
  66.  
  67. /** 
  68. * Custom kses filter for forum topics and replies, for filtering incoming data 
  69. * 
  70. * @since bbPress (r4603) 
  71. * 
  72. * @param string $data Content to filter, expected to be escaped with slashes 
  73. * @return string Filtered content 
  74. */ 
  75. function bbp_filter_kses( $data = '' ) { 
  76. return addslashes( wp_kses( stripslashes( $data ), bbp_kses_allowed_tags() ) ); 
  77.  
  78. /** 
  79. * Custom kses filter for forum topics and replies, for raw data 
  80. * 
  81. * @since bbPress (r4603) 
  82. * 
  83. * @param string $data Content to filter, expected to not be escaped 
  84. * @return string Filtered content 
  85. */ 
  86. function bbp_kses_data( $data = '' ) { 
  87. return wp_kses( $data , bbp_kses_allowed_tags() ); 
  88.  
  89. /** Formatting ****************************************************************/ 
  90.  
  91. /** 
  92. * Filter the topic or reply content and output code and pre tags 
  93. * 
  94. * @since bbPress (r4641) 
  95. * 
  96. * @param string $content Topic and reply content 
  97. * @return string Partially encodedd content 
  98. */ 
  99. function bbp_code_trick( $content = '' ) { 
  100. $content = str_replace( array( "\r\n", "\r" ), "\n", $content ); 
  101. $content = preg_replace_callback( "|(`)(.*?)`|", 'bbp_encode_callback', $content ); 
  102. $content = preg_replace_callback( "!(^|\n)`(.*?)`!s", 'bbp_encode_callback', $content ); 
  103.  
  104. return $content; 
  105.  
  106. /** 
  107. * When editing a topic or reply, reverse the code trick so the textarea 
  108. * contains the correct editable content. 
  109. * 
  110. * @since bbPress (r4641) 
  111. * 
  112. * @param string $content Topic and reply content 
  113. * @return string Partially encodedd content 
  114. */ 
  115. function bbp_code_trick_reverse( $content = '' ) { 
  116.  
  117. // Setup variables 
  118. $openers = array( '<p>', '<br />' ); 
  119. $content = preg_replace_callback( "!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", 'bbp_decode_callback', $content ); 
  120.  
  121. // Do the do 
  122. $content = str_replace( $openers, '', $content ); 
  123. $content = str_replace( '</p>', "\n", $content ); 
  124. $content = str_replace( '<coded_br />', '<br />', $content ); 
  125. $content = str_replace( '<coded_p>', '<p>', $content ); 
  126. $content = str_replace( '</coded_p>', '</p>', $content ); 
  127.  
  128. return $content; 
  129.  
  130. /** 
  131. * Filter the content and encode any bad HTML tags 
  132. * 
  133. * @since bbPress (r4641) 
  134. * 
  135. * @param string $content Topic and reply content 
  136. * @return string Partially encodedd content 
  137. */ 
  138. function bbp_encode_bad( $content = '' ) { 
  139.  
  140. // Setup variables 
  141. $content = _wp_specialchars( $content, ENT_NOQUOTES ); 
  142. $content = preg_split( '@(`[^`]*`)@m', $content, -1, PREG_SPLIT_NO_EMPTY + PREG_SPLIT_DELIM_CAPTURE ); 
  143. $allowed = bbp_kses_allowed_tags(); 
  144. $empty = array( 
  145. 'br' => true,  
  146. 'hr' => true,  
  147. 'img' => true,  
  148. 'input' => true,  
  149. 'param' => true,  
  150. 'area' => true,  
  151. 'col' => true,  
  152. 'embed' => true 
  153. ); 
  154.  
  155. // Loop through allowed tags and compare for empty and normal tags 
  156. foreach ( $allowed as $tag => $args ) { 
  157. $preg = $args ? "{$tag}(?:\s.*?)?" : $tag; 
  158.  
  159. // Which walker to use based on the tag and arguments 
  160. if ( isset( $empty[$tag] ) ) { 
  161. array_walk( $content, 'bbp_encode_empty_callback', $preg ); 
  162. } else { 
  163. array_walk( $content, 'bbp_encode_normal_callback', $preg ); 
  164.  
  165. // Return the joined content array 
  166. return implode( '', $content ); 
  167.  
  168. /** Code Callbacks ************************************************************/ 
  169.  
  170. /** 
  171. * Callback to encode the tags in topic or reply content 
  172. * 
  173. * @since bbPress (r4641) 
  174. * 
  175. * @param array $matches 
  176. * @return string 
  177. */ 
  178. function bbp_encode_callback( $matches = array() ) { 
  179.  
  180. // Trim inline code, not pre blocks (to prevent removing indentation) 
  181. if ( "`" === $matches[1] ) { 
  182. $content = trim( $matches[2] ); 
  183. } else { 
  184. $content = $matches[2]; 
  185.  
  186. // Do some replacing 
  187. $content = htmlspecialchars( $content, ENT_QUOTES ); 
  188. $content = str_replace( array( "\r\n", "\r" ), "\n", $content ); 
  189. $content = preg_replace( "|\n\n\n+|", "\n\n", $content ); 
  190. $content = str_replace( '&amp;', '&', $content ); 
  191. $content = str_replace( '&lt;', '<', $content ); 
  192. $content = str_replace( '&gt;', '>', $content ); 
  193.  
  194. // Wrap in code tags 
  195. $content = '<code>' . $content . '</code>'; 
  196.  
  197. // Wrap blocks in pre tags 
  198. if ( "`" !== $matches[1] ) { 
  199. $content = "\n<pre>" . $content . "</pre>\n"; 
  200.  
  201. return $content; 
  202.  
  203. /** 
  204. * Callback to decode the tags in topic or reply content 
  205. * 
  206. * @since bbPress (r4641) 
  207. * 
  208. * @param array $matches 
  209. * @todo Experiment with _wp_specialchars() 
  210. * @return string 
  211. */ 
  212. function bbp_decode_callback( $matches = array() ) { 
  213.  
  214. // Setup variables 
  215. $trans_table = array_flip( get_html_translation_table( HTML_ENTITIES ) ); 
  216. $amps = array( '&', '&', '&' ); 
  217. $single = array( ''', ''' ); 
  218. $content = $matches[2]; 
  219. $content = strtr( $content, $trans_table ); 
  220.  
  221. // Do the do 
  222. $content = str_replace( '<br />', '<coded_br />', $content ); 
  223. $content = str_replace( '<p>', '<coded_p>', $content ); 
  224. $content = str_replace( '</p>', '</coded_p>', $content ); 
  225. $content = str_replace( $amps, '&', $content ); 
  226. $content = str_replace( $single, "'", $content ); 
  227.  
  228. // Return content wrapped in code tags 
  229. return '`' . $content . '`'; 
  230.  
  231. /** 
  232. * Callback to replace empty HTML tags in a content string 
  233. * 
  234. * @since bbPress (r4641) 
  235. * 
  236. * @internal Used by bbp_encode_bad() 
  237. * @param string $content 
  238. * @param string $key Not used 
  239. * @param string $preg 
  240. */ 
  241. function bbp_encode_empty_callback( &$content = '', $key = '', $preg = '' ) { 
  242. if ( strpos( $content, '`' ) !== 0 ) { 
  243. $content = preg_replace( "|<({$preg})\s*?/*?>|i", '<$1 />', $content ); 
  244. } 
  245. } 
  246.   
  247. /** 
  248. * Callback to replace normal HTML tags in a content string 
  249. * 
  250. * @since bbPress (r4641) 
  251. * 
  252. * @internal Used by bbp_encode_bad() 
  253. * @param type $content 
  254. * @param type $key 
  255. * @param type $preg 
  256. */ 
  257. function bbp_encode_normal_callback( &$content = '', $key = '', $preg = '') { 
  258. if ( strpos( $content, '`' ) !== 0 ) { 
  259. $content = preg_replace( "|<(/?{$preg})>|i", '<$1>', $content ); 
  260.  
  261. /** No Follow *****************************************************************/ 
  262.  
  263. /** 
  264. * Catches links so rel=nofollow can be added (on output, not save) 
  265. * 
  266. * @since bbPress (r4865) 
  267. * @param string $text Post text 
  268. * @return string $text Text with rel=nofollow added to any links 
  269. */ 
  270. function bbp_rel_nofollow( $text = '' ) { 
  271. return preg_replace_callback( '|<a (.+?)>|i', 'bbp_rel_nofollow_callback', $text ); 
  272.  
  273. /** 
  274. * Adds rel=nofollow to a link 
  275. * 
  276. * @since bbPress (r4865) 
  277. * @param array $matches 
  278. * @return string $text Link with rel=nofollow added 
  279. */ 
  280. function bbp_rel_nofollow_callback( $matches = array() ) { 
  281. $text = $matches[1]; 
  282. $text = str_replace( array( ' rel="nofollow"', " rel='nofollow'" ), '', $text ); 
  283. return "<a $text rel=\"nofollow\">"; 
  284.  
  285. /** Make Clickable ************************************************************/ 
  286.  
  287. /** 
  288. * Convert plaintext URI to HTML links. 
  289. * 
  290. * Converts URI, www and ftp, and email addresses. Finishes by fixing links 
  291. * within links. 
  292. * 
  293. * This custom version of WordPress's make_clickable() skips links inside of 
  294. * pre and code tags. 
  295. * 
  296. * @since bbPress (r4941) 
  297. * 
  298. * @param string $text Content to convert URIs. 
  299. * @return string Content with converted URIs. 
  300. */ 
  301. function bbp_make_clickable( $text ) { 
  302. $r = ''; 
  303. $in_code = false; 
  304. $textarr = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags 
  305.  
  306. foreach ( $textarr as $piece ) { 
  307.  
  308. switch ( $piece ) { 
  309. case '<code>' : 
  310. case '<pre>' : 
  311. $in_code = true; 
  312. break; 
  313. case '</code>' : 
  314. case '</pre>' : 
  315. $in_code = false; 
  316. break; 
  317.  
  318. if ( $in_code || empty( $piece ) || ( $piece[0] === '<' && ! preg_match('|^<\s*[\w]{1, 20}+://|', $piece) ) ) { 
  319. $r .= $piece; 
  320. continue; 
  321.  
  322. // Long strings might contain expensive edge cases ... 
  323. if ( 10000 < strlen( $piece ) ) { 
  324. // ... break it up 
  325. foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing paretheses 
  326. if ( 2101 < strlen( $chunk ) ) { 
  327. $r .= $chunk; // Too big, no whitespace: bail. 
  328. } else { 
  329. $r .= make_clickable( $chunk ); 
  330. } else { 
  331. $ret = " $piece "; // Pad with whitespace to simplify the regexes 
  332.  
  333. $url_clickable = '~ 
  334. ([\\s(<., ;:!?]) # 1: Leading whitespace, or punctuation 
  335. ( # 2: URL 
  336. [\\w]{1, 20}+:// # Scheme and hier-part prefix 
  337. (?=\S{1, 2000}\s) # Limit to URLs less than about 2000 characters long 
  338. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+ # Non-punctuation URL character 
  339. (?: # Unroll the Loop: Only allow puctuation URL character if followed by a non-punctuation URL character 
  340. [\'., ;:!?)] # Punctuation URL character 
  341. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character 
  342. )* 
  343. (\)?) # 3: Trailing closing parenthesis (for parethesis balancing post processing) 
  344. ~xS'; // The regex is a non-anchored pattern and does not have a single fixed starting character. 
  345. // Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times. 
  346.  
  347. $ret = preg_replace_callback( $url_clickable, '_make_url_clickable_cb', $ret ); 
  348.  
  349. $ret = preg_replace_callback( '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=, ?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $ret ); 
  350. $ret = preg_replace_callback( '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2, })#i', '_make_email_clickable_cb', $ret ); 
  351.  
  352. $ret = substr( $ret, 1, -1 ); // Remove our whitespace padding. 
  353. $r .= $ret; 
  354.  
  355. // Cleanup of accidental links within links 
  356. $r = preg_replace( '#(<a( [^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i', "$1$3</a>", $r ); 
  357. return $r; 
.