/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. $textarr = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags 
  304. $nested_code_pre = 0; // Keep track of how many levels link is nested inside <pre> or <code> 
  305.  
  306. foreach ( $textarr as $piece ) { 
  307.  
  308. if ( preg_match( '|^<code[\s>]|i', $piece ) || preg_match( '|^<pre[\s>]|i', $piece ) || preg_match( '|^<script[\s>]|i', $piece ) || preg_match( '|^<style[\s>]|i', $piece ) ) { 
  309. $nested_code_pre++; 
  310. } elseif ( $nested_code_pre && ( '</code>' === strtolower( $piece ) || '</pre>' === strtolower( $piece ) || '</script>' === strtolower( $piece ) || '</style>' === strtolower( $piece ) ) ) { 
  311. $nested_code_pre--; 
  312.  
  313. if ( $nested_code_pre || empty( $piece ) || ( $piece[0] === '<' && ! preg_match( '|^<\s*[\w]{1, 20}+://|', $piece ) ) ) { 
  314. $r .= $piece; 
  315. continue; 
  316.  
  317. // Long strings might contain expensive edge cases ... 
  318. if ( 10000 < strlen( $piece ) ) { 
  319. // ... break it up 
  320. foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing paretheses 
  321. if ( 2101 < strlen( $chunk ) ) { 
  322. $r .= $chunk; // Too big, no whitespace: bail. 
  323. } else { 
  324. $r .= bbp_make_clickable( $chunk ); 
  325. } else { 
  326. $ret = " {$piece} "; // Pad with whitespace to simplify the regexes 
  327. $ret = apply_filters( 'bbp_make_clickable', $ret ); 
  328. $ret = substr( $ret, 1, -1 ); // Remove our whitespace padding. 
  329. $r .= $ret; 
  330.  
  331. // Cleanup of accidental links within links 
  332. return preg_replace( '#(<a([ \r\n\t]+[^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i', "$1$3</a>", $r ); 
  333.  
  334. /** 
  335. * Make URLs clickable in content areas 
  336. * 
  337. * @since 2.6.0 
  338. * 
  339. * @param string $text 
  340. * @return string 
  341. */ 
  342. function bbp_make_urls_clickable( $text = '' ) { 
  343. $url_clickable = '~ 
  344. ([\\s(<., ;:!?]) # 1: Leading whitespace, or punctuation 
  345. ( # 2: URL 
  346. [\\w]{1, 20}+:// # Scheme and hier-part prefix 
  347. (?=\S{1, 2000}\s) # Limit to URLs less than about 2000 characters long 
  348. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+ # Non-punctuation URL character 
  349. (?: # Unroll the Loop: Only allow puctuation URL character if followed by a non-punctuation URL character 
  350. [\'., ;:!?)] # Punctuation URL character 
  351. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character 
  352. )* 
  353. (\)?) # 3: Trailing closing parenthesis (for parethesis balancing post processing) 
  354. ~xS'; 
  355.  
  356. // The regex is a non-anchored pattern and does not have a single fixed starting character. 
  357. // Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times. 
  358. return preg_replace_callback( $url_clickable, '_make_url_clickable_cb', $text ); 
  359.  
  360. /** 
  361. * Make FTP clickable in content areas 
  362. * 
  363. * @since 2.6.0 
  364. * 
  365. * @see make_clickable() 
  366. * 
  367. * @param string $text 
  368. * @return string 
  369. */ 
  370. function bbp_make_ftps_clickable( $text = '' ) { 
  371. return preg_replace_callback( '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=, ?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $text ); 
  372.  
  373. /** 
  374. * Make emails clickable in content areas 
  375. * 
  376. * @since 2.6.0 
  377. * 
  378. * @see make_clickable() 
  379. * 
  380. * @param string $text 
  381. * @return string 
  382. */ 
  383. function bbp_make_emails_clickable( $text = '' ) { 
  384. return preg_replace_callback( '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2, })#i', '_make_email_clickable_cb', $text ); 
  385.  
  386. /** 
  387. * Make mentions clickable in content areas 
  388. * 
  389. * @since 2.6.0 
  390. * 
  391. * @see make_clickable() 
  392. * 
  393. * @param string $text 
  394. * @return string 
  395. */ 
  396. function bbp_make_mentions_clickable( $text = '' ) { 
  397. return preg_replace_callback( '#([\s>])@([0-9a-zA-Z-_]+)#i', 'bbp_make_mentions_clickable_callback', $text ); 
  398.  
  399. /** 
  400. * Callback to convert mention matchs to HTML A tag. 
  401. * 
  402. * @since 2.6.0 
  403. * 
  404. * @param array $matches Single Regex Match. 
  405. * 
  406. * @return string HTML A tag with link to user profile. 
  407. */ 
  408. function bbp_make_mentions_clickable_callback( $matches = array() ) { 
  409.  
  410. // Get user; bail if not found 
  411. $user = get_user_by( 'slug', $matches[2] ); 
  412. if ( empty( $user ) || bbp_is_user_inactive( $user->ID ) ) { 
  413. return $matches[0]; 
  414.  
  415. // Create the link to the user's profile 
  416. $url = bbp_get_user_profile_url( $user->ID ); 
  417. $anchor = '<a href="%1$s" rel="nofollow">@%2$s</a>'; 
  418. $link = sprintf( $anchor, esc_url( $url ), esc_html( $user->user_nicename ) ); 
  419.  
  420. return $matches[1] . $link; 
.