PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.4.4
Forumax – AI Powered Advanced Community Forum Plugin v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 trunk 1.0.8 1.1.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 2.0.0 2.1.0 All 29 releases
bbp-core / lib / bbpress / includes / common / formatting.php

formatting.php in Forumax – AI Powered Advanced Community Forum Plugin 2.4.4, at lib/bbpress/includes/common/formatting.php

820 lines 23.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * bbPress Formatting
5 *
6 * @package bbPress
7 * @subpackage Formatting
8 */
9
10 // Exit if accessed directly
11 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 2.3.0 bbPress (r4603)
21 *
22 * @return array Associative array of allowed tags and attributes
23 */
24 function bbp_kses_allowed_tags() {
25
26 // Filter & return
27 return (array) apply_filters( 'bbp_kses_allowed_tags', array(
28
29 // Links
30 'a' => array(
31 'href' => true,
32 'title' => true,
33 'rel' => true,
34 'target' => true
35 ),
36
37 // Quotes
38 'blockquote' => array(
39 'cite' => true
40 ),
41
42 // Code
43 'code' => array(),
44 'pre' => array(
45 'class' => true
46 ),
47
48 // Formatting
49 'em' => array(),
50 'strong' => array(),
51 'del' => array(
52 'datetime' => true,
53 'cite' => true
54 ),
55 'ins' => array(
56 'datetime' => true,
57 'cite' => true
58 ),
59
60 // Lists
61 'ul' => array(),
62 'ol' => array(
63 'start' => true,
64 ),
65 'li' => array(),
66
67 // Images
68 'img' => array(
69 'src' => true,
70 'border' => true,
71 'alt' => true,
72 'height' => true,
73 'width' => true,
74 )
75 ) );
76 }
77
78 /**
79 * Custom kses filter for forum topics and replies, for filtering incoming data
80 *
81 * @since 2.3.0 bbPress (r4603)
82 *
83 * @param string $data Content to filter, expected to be escaped with slashes
84 * @return string Filtered content
85 */
86 function bbp_filter_kses( $data = '' ) {
87 return wp_slash( wp_kses( wp_unslash( $data ), bbp_kses_allowed_tags() ) );
88 }
89
90 /**
91 * Custom kses filter for forum topics and replies, for raw data
92 *
93 * @since 2.3.0 bbPress (r4603)
94 *
95 * @param string $data Content to filter, expected to not be escaped
96 * @return string Filtered content
97 */
98 function bbp_kses_data( $data = '' ) {
99 return wp_kses( $data , bbp_kses_allowed_tags() );
100 }
101
102 /** Formatting ****************************************************************/
103
104 /**
105 * Filter the topic or reply content and output code and pre tags
106 *
107 * @since 2.3.0 bbPress (r4641)
108 *
109 * @param string $content Topic and reply content
110 * @return string Partially encoded content
111 */
112 function bbp_code_trick( $content = '' ) {
113 $content = str_replace( array( "\r\n", "\r" ), "\n", $content );
114 $content = preg_replace_callback( "|(`)(.*?)`|", 'bbp_encode_callback', $content );
115 $content = preg_replace_callback( "!(^|\n)`(.*?)`!s", 'bbp_encode_callback', $content );
116
117 return $content;
118 }
119
120 /**
121 * When editing a topic or reply, reverse the code trick so the textarea
122 * contains the correct editable content.
123 *
124 * @since 2.3.0 bbPress (r4641)
125 *
126 * @param string $content Topic and reply content
127 * @return string Partially encoded content
128 */
129 function bbp_code_trick_reverse( $content = '' ) {
130
131 // Setup variables
132 $openers = array( '<p>', '<br />' );
133 $content = preg_replace_callback( "!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", 'bbp_decode_callback', $content );
134
135 // Do the do
136 $content = str_replace( $openers, '', $content );
137 $content = str_replace( '</p>', "\n", $content );
138 $content = str_replace( '<coded_br />', '<br />', $content );
139 $content = str_replace( '<coded_p>', '<p>', $content );
140 $content = str_replace( '</coded_p>', '</p>', $content );
141
142 return $content;
143 }
144
145 /**
146 * Filter the content and encode any bad HTML tags
147 *
148 * @since 2.3.0 bbPress (r4641)
149 *
150 * @param string $content Topic and reply content
151 * @return string Partially encoded content
152 */
153 function bbp_encode_bad( $content = '' ) {
154
155 // Setup variables
156 $content = _wp_specialchars( $content, ENT_NOQUOTES );
157 $content = preg_split( '@(`[^`]*`)@m', $content, -1, PREG_SPLIT_NO_EMPTY + PREG_SPLIT_DELIM_CAPTURE );
158 $allowed = bbp_kses_allowed_tags();
159 $empty = array(
160 'br' => true,
161 'hr' => true,
162 'img' => true,
163 'input' => true,
164 'param' => true,
165 'area' => true,
166 'col' => true,
167 'embed' => true
168 );
169
170 // Loop through allowed tags and compare for empty and normal tags
171 foreach ( $allowed as $tag => $args ) {
172 $preg = $args ? "{$tag}(?:\s.*?)?" : $tag;
173
174 // Which walker to use based on the tag and arguments
175 if ( isset( $empty[ $tag ] ) ) {
176 array_walk( $content, 'bbp_encode_empty_callback', $preg );
177 } else {
178 array_walk( $content, 'bbp_encode_normal_callback', $preg );
179 }
180 }
181
182 // Return the joined content array
183 return implode( '', $content );
184 }
185
186 /** Code Callbacks ************************************************************/
187
188 /**
189 * Callback to encode the tags in topic or reply content
190 *
191 * @since 2.3.0 bbPress (r4641)
192 *
193 * @param array $matches
194 * @return string
195 */
196 function bbp_encode_callback( $matches = array() ) {
197
198 // Trim inline code, not pre blocks (to prevent removing indentation)
199 if ( "`" === $matches[1] ) {
200 $content = trim( $matches[2] );
201 } else {
202 $content = $matches[2];
203 }
204
205 // Do some replacing
206 $content = htmlspecialchars( $content, ENT_QUOTES );
207 $content = str_replace( array( "\r\n", "\r" ), "\n", $content );
208 $content = preg_replace( "|\n\n\n+|", "\n\n", $content );
209 $content = str_replace( '&amp;amp;', '&amp;', $content );
210 $content = str_replace( '&amp;lt;', '&lt;', $content );
211 $content = str_replace( '&amp;gt;', '&gt;', $content );
212
213 // Wrap in code tags
214 $content = '<code>' . $content . '</code>';
215
216 // Wrap blocks in pre tags
217 if ( "`" !== $matches[1] ) {
218 $content = "\n<pre>" . $content . "</pre>\n";
219 }
220
221 return $content;
222 }
223
224 /**
225 * Callback to decode the tags in topic or reply content
226 *
227 * @since 2.3.0 bbPress (r4641)
228 *
229 * @param array $matches
230 * @todo Experiment with _wp_specialchars()
231 * @return string
232 */
233 function bbp_decode_callback( $matches = array() ) {
234
235 // Setup variables
236 $trans_table = array_flip( get_html_translation_table( HTML_ENTITIES ) );
237 $amps = array( '&#38;','&#038;', '&amp;' );
238 $single = array( '&#39;','&#039;' );
239 $content = $matches[2];
240 $content = strtr( $content, $trans_table );
241
242 // Do the do
243 $content = str_replace( '<br />', '<coded_br />', $content );
244 $content = str_replace( '<p>', '<coded_p>', $content );
245 $content = str_replace( '</p>', '</coded_p>', $content );
246 $content = str_replace( $amps, '&', $content );
247 $content = str_replace( $single, "'", $content );
248
249 // Return content wrapped in code tags
250 return '`' . $content . '`';
251 }
252
253 /**
254 * Callback to replace empty HTML tags in a content string
255 *
256 * @since 2.3.0 bbPress (r4641)
257 *
258 * @internal Used by bbp_encode_bad()
259 * @param string $content
260 * @param string $key Not used
261 * @param string $preg
262 */
263 function bbp_encode_empty_callback( &$content = '', $key = '', $preg = '' ) {
264 if ( strpos( $content, '`' ) !== 0 ) {
265 $content = preg_replace( "|&lt;({$preg})\s*?/*?&gt;|i", '<$1 />', $content );
266 }
267 }
268
269 /**
270 * Callback to replace normal HTML tags in a content string
271 *
272 * @since 2.3.0 bbPress (r4641)
273 *
274 * @internal Used by bbp_encode_bad()
275 *
276 * @param string $content
277 * @param string $key
278 * @param string $preg
279 */
280 function bbp_encode_normal_callback( &$content = '', $key = '', $preg = '') {
281 if ( strpos( $content, '`' ) !== 0 ) {
282 $content = preg_replace( "|&lt;(/?{$preg})&gt;|i", '<$1>', $content );
283 }
284 }
285
286 /** No Follow *****************************************************************/
287
288 /**
289 * Catches links so rel=nofollow can be added (on output, not save)
290 *
291 * @since 2.3.0 bbPress (r4865)
292 *
293 * @param string $text Post text
294 * @return string $text Text with rel=nofollow added to any links
295 */
296 function bbp_rel_nofollow( $text = '' ) {
297 return preg_replace_callback( '|<a (.+?)>|i', 'bbp_rel_nofollow_callback', $text );
298 }
299
300 /**
301 * Adds rel=nofollow to a link
302 *
303 * @since 2.3.0 bbPress (r4865)
304 *
305 * @param array $matches
306 * @return string $text Link with rel=nofollow added
307 */
308 function bbp_rel_nofollow_callback( $matches = array() ) {
309 $text = $matches[1];
310 $atts = shortcode_parse_atts( $matches[1] );
311 $rel = 'nofollow';
312 $home_url = home_url();
313
314 // Bail on links that match the current domain
315 if ( preg_match( '%href=["\'](' . preg_quote( set_url_scheme( $home_url, 'http' ) ) . ')%i', $text ) ||
316 preg_match( '%href=["\'](' . preg_quote( set_url_scheme( $home_url, 'https' ) ) . ')%i', $text )
317 ) {
318 return "<a {$text}>";
319 }
320
321 // Avoid collisions with existing "rel" attribute
322 if ( ! empty( $atts['rel'] ) ) {
323 $parts = array_map( 'trim', explode( ' ', $atts['rel'] ) );
324 if ( false === array_search( 'nofollow', $parts ) ) {
325 $parts[] = 'nofollow';
326 }
327
328 $rel = implode( ' ', $parts );
329 unset( $atts['rel'] );
330
331 $html = '';
332 foreach ( $atts as $name => $value ) {
333 $html .= "{$name}=\"{$value}\" ";
334 }
335
336 $text = trim( $html );
337 }
338
339 return "<a {$text} rel=\"{$rel}\">";
340 }
341
342 /** Make Clickable ************************************************************/
343
344 /**
345 * Convert plaintext URI to HTML links.
346 *
347 * Converts URI, www and ftp, and email addresses. Finishes by fixing links
348 * within links.
349 *
350 * This custom version of WordPress's make_clickable() skips links inside of
351 * pre and code tags.
352 *
353 * @since 2.4.0 bbPress (r4941)
354 *
355 * @param string $text Content to convert URIs.
356 * @return string Content with converted URIs.
357 */
358 function bbp_make_clickable( $text = '' ) {
359 $r = '';
360 $textarr = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags
361 $nested_code_pre = 0; // Keep track of how many levels link is nested inside <pre> or <code>
362
363 foreach ( $textarr as $piece ) {
364
365 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 ) ) {
366 $nested_code_pre++;
367 } elseif ( $nested_code_pre && ( '</code>' === strtolower( $piece ) || '</pre>' === strtolower( $piece ) || '</script>' === strtolower( $piece ) || '</style>' === strtolower( $piece ) ) ) {
368 $nested_code_pre--;
369 }
370
371 if ( $nested_code_pre || empty( $piece ) || ( $piece[0] === '<' && ! preg_match( '|^<\s*[\w]{1,20}+://|', $piece ) ) ) {
372 $r .= $piece;
373 continue;
374 }
375
376 // Long strings might contain expensive edge cases ...
377 if ( 10000 < strlen( $piece ) ) {
378 // ... break it up
379 foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing paretheses
380 if ( 2101 < strlen( $chunk ) ) {
381 $r .= $chunk; // Too big, no whitespace: bail.
382 } else {
383 $r .= bbp_make_clickable( $chunk );
384 }
385 }
386 } else {
387 $ret = " {$piece} "; // Pad with whitespace to simplify the regexes
388 $ret = apply_filters( 'bbp_make_clickable', $ret, $text );
389 $ret = substr( $ret, 1, -1 ); // Remove our whitespace padding.
390 $r .= $ret;
391 }
392 }
393
394 // Cleanup of accidental links within links
395 return preg_replace( '#(<a([ \r\n\t]+[^>]+?>|>))<a [^>]+?>([^>]+?)</a>([^<]*)</a>#i', "$1$3$4</a>", $r );
396 }
397
398 /**
399 * Make URLs clickable in content areas
400 *
401 * @since 2.6.0 bbPress (r6014)
402 *
403 * @param string $text
404 * @return string
405 */
406 function bbp_make_urls_clickable( $text = '' ) {
407 $url_clickable = '~
408 ([\\s(<.,;:!?]) # 1: Leading whitespace, or punctuation
409 ( # 2: URL
410 [\\w]{1,20}+:// # Scheme and hier-part prefix
411 (?=\S{1,2000}\s) # Limit to URLs less than about 2000 characters long
412 [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+ # Non-punctuation URL character
413 (?: # Unroll the Loop: Only allow puctuation URL character if followed by a non-punctuation URL character
414 [\'.,;:!?)] # Punctuation URL character
415 [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character
416 )*
417 )
418 (\)?) # 3: Trailing closing parenthesis (for parethesis balancing post processing)
419 ~xS';
420
421 // The regex is a non-anchored pattern and does not have a single fixed starting character.
422 // Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times.
423 return preg_replace_callback( $url_clickable, '_make_url_clickable_cb', $text );
424 }
425
426 /**
427 * Make FTP clickable in content areas
428 *
429 * @since 2.6.0 bbPress (r6014)
430 *
431 * @see make_clickable()
432 *
433 * @param string $text
434 * @return string
435 */
436 function bbp_make_ftps_clickable( $text = '' ) {
437 return preg_replace_callback( '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $text );
438 }
439
440 /**
441 * Make emails clickable in content areas
442 *
443 * @since 2.6.0 bbPress (r6014)
444 *
445 * @see make_clickable()
446 *
447 * @param string $text
448 * @return string
449 */
450 function bbp_make_emails_clickable( $text = '' ) {
451 return preg_replace_callback( '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', '_make_email_clickable_cb', $text );
452 }
453
454 /**
455 * Make mentions clickable in content areas
456 *
457 * @since 2.6.0 bbPress (r6014)
458 *
459 * @see make_clickable()
460 *
461 * @param string $text
462 * @return string
463 */
464 function bbp_make_mentions_clickable( $text = '' ) {
465 return preg_replace_callback( '#([\s>])@([0-9a-zA-Z-_]+)#i', 'bbp_make_mentions_clickable_callback', $text );
466 }
467
468 /**
469 * Callback to convert mention matches to HTML A tag.
470 *
471 * @since 2.6.0 bbPress (r6014)
472 *
473 * @param array $matches Regular expression matches in the current text blob.
474 *
475 * @return string Original text if no user exists, or link to user profile.
476 */
477 function bbp_make_mentions_clickable_callback( $matches = array() ) {
478
479 // Bail if the match is empty malformed
480 if ( empty( $matches[2] ) || ! is_string( $matches[2] ) ) {
481 return $matches[0];
482 }
483
484 // Get user; bail if not found
485 $user = get_user_by( 'slug', $matches[2] );
486 if ( empty( $user ) || bbp_is_user_inactive( $user->ID ) ) {
487 return $matches[0];
488 }
489
490 // Default anchor classes
491 $classes = array(
492 'bbp-user-mention',
493 'bbp-user-id-' . absint( $user->ID )
494 );
495
496 // Filter classes
497 $classes = (array) apply_filters( 'bbp_make_mentions_clickable_classes', $classes, $user );
498
499 // Escape & implode if not empty, otherwise an empty string
500 $class_str = ! empty( $classes )
501 ? implode( ' ', array_map( 'sanitize_html_class', $classes ) )
502 : '';
503
504 // Setup as a variable to avoid a potentially empty class attribute
505 $class = ! empty( $class_str )
506 ? ' class="' . esc_attr( $class_str ) . '"'
507 : '';
508
509 // Create the link to the user's profile
510 $html = '<a href="%1$s"' . $class . '>%2$s</a>';
511 $url = bbp_get_user_profile_url( $user->ID );
512 $anchor = sprintf( $html, esc_url( $url ), esc_html( $matches[0] ) );
513
514 // Prevent this link from being followed by bots
515 $link = bbp_rel_nofollow( $anchor );
516
517 // Concatenate the matches into the return value
518 $retval = $matches[1] . $link;
519
520 // Return the link
521 return $retval;
522 }
523
524 /** Numbers *******************************************************************/
525
526 /**
527 * Never let a numeric value be less than zero.
528 *
529 * @since 2.6.0 bbPress (r6300)
530 *
531 * @param int $number
532 */
533 function bbp_number_not_negative( $number = 0 ) {
534
535 // Protect against formatted strings
536 if ( is_string( $number ) ) {
537 $number = strip_tags( $number ); // No HTML
538 $number = preg_replace( '/[^0-9-]/', '', $number ); // No number-format
539
540 // Protect against objects, arrays, scalars, etc...
541 } elseif ( ! is_numeric( $number ) ) {
542 $number = 0;
543 }
544
545 // Make the number an integer
546 $int = intval( $number );
547
548 // Pick the maximum value, never less than zero
549 $not_less_than_zero = max( 0, $int );
550
551 // Filter & return
552 return (int) apply_filters( 'bbp_number_not_negative', $not_less_than_zero, $int, $number );
553 }
554
555 /**
556 * A bbPress specific method of formatting numeric values
557 *
558 * @since 2.0.0 bbPress (r2486)
559 *
560 * @param string $number Number to format
561 * @param string $decimals Optional. Display decimals
562 *
563 * @return string Formatted string
564 */
565 function bbp_number_format( $number = 0, $decimals = false, $dec_point = '.', $thousands_sep = ',' ) {
566
567 // If empty, set $number to (int) 0
568 if ( ! is_numeric( $number ) ) {
569 $number = 0;
570 }
571
572 // Filter & return
573 return apply_filters( 'bbp_number_format', number_format( $number, $decimals, $dec_point, $thousands_sep ), $number, $decimals, $dec_point, $thousands_sep );
574 }
575
576 /**
577 * A bbPress specific method of formatting numeric values
578 *
579 * @since 2.1.0 bbPress (r3857)
580 *
581 * @param string $number Number to format
582 * @param string $decimals Optional. Display decimals
583 *
584 * @return string Formatted string
585 */
586 function bbp_number_format_i18n( $number = 0, $decimals = false ) {
587
588 // If empty, set $number to (int) 0
589 if ( ! is_numeric( $number ) ) {
590 $number = 0;
591 }
592
593 // Filter & return
594 return apply_filters( 'bbp_number_format_i18n', number_format_i18n( $number, $decimals ), $number, $decimals );
595 }
596
597 /** Dates *********************************************************************/
598
599 /**
600 * Convert time supplied from database query into specified date format.
601 *
602 * @since 2.0.0 bbPress (r2544)
603 *
604 * @param string $time Time to convert
605 * @param string $d Optional. Default is 'U'. Either 'G', 'U', or php date
606 * format
607 * @param bool $translate Optional. Default is false. Whether to translate the
608 *
609 * @return string Returns timestamp
610 */
611 function bbp_convert_date( $time, $d = 'U', $translate = false ) {
612 $new_time = mysql2date( $d, $time, $translate );
613
614 // Filter & return
615 return apply_filters( 'bbp_convert_date', $new_time, $d, $translate, $time );
616 }
617
618 /**
619 * Output formatted time to display human readable time difference.
620 *
621 * @since 2.0.0 bbPress (r2544)
622 *
623 * @param string $older_date Unix timestamp from which the difference begins.
624 * @param string $newer_date Optional. Unix timestamp from which the
625 * difference ends. False for current time.
626 * @param int $gmt Optional. Whether to use GMT timezone. Default is false.
627 */
628 function bbp_time_since( $older_date, $newer_date = false, $gmt = false ) {
629 echo bbp_get_time_since( $older_date, $newer_date, $gmt );
630 }
631 /**
632 * Return formatted time to display human readable time difference.
633 *
634 * @since 2.0.0 bbPress (r2544)
635 *
636 * @param string $older_date Unix timestamp from which the difference begins.
637 * @param string $newer_date Optional. Unix timestamp from which the
638 * difference ends. False for current time.
639 * @param int $gmt Optional. Whether to use GMT timezone. Default is false.
640 *
641 * @return string Formatted time
642 */
643 function bbp_get_time_since( $older_date, $newer_date = false, $gmt = false ) {
644
645 // Setup the strings
646 $unknown_text = apply_filters( 'bbp_core_time_since_unknown_text', esc_html__( 'sometime', 'bbpress' ) );
647 $right_now_text = apply_filters( 'bbp_core_time_since_right_now_text', esc_html__( 'right now', 'bbpress' ) );
648 $ago_text = apply_filters( 'bbp_core_time_since_ago_text', esc_html__( '%s ago', 'bbpress' ) );
649
650 // array of time period chunks
651 $chunks = array(
652 array( YEAR_IN_SECONDS, _n_noop( '%s year', '%s years', 'bbpress' ) ),
653 array( MONTH_IN_SECONDS, _n_noop( '%s month', '%s months', 'bbpress' ) ),
654 array( WEEK_IN_SECONDS, _n_noop( '%s week', '%s weeks', 'bbpress' ) ),
655 array( DAY_IN_SECONDS, _n_noop( '%s day', '%s days', 'bbpress' ) ),
656 array( HOUR_IN_SECONDS, _n_noop( '%s hour', '%s hours', 'bbpress' ) ),
657 array( MINUTE_IN_SECONDS, _n_noop( '%s minute', '%s minutes', 'bbpress' ) ),
658 array( 1, _n_noop( '%s second', '%s seconds', 'bbpress' ) ),
659 );
660
661 // Attempt to parse non-numeric older date
662 if ( ! empty( $older_date ) && ! is_numeric( $older_date ) ) {
663 $time_chunks = explode( ':', str_replace( ' ', ':', $older_date ) );
664 $date_chunks = explode( '-', str_replace( ' ', '-', $older_date ) );
665 $older_date = gmmktime( (int) $time_chunks[1], (int) $time_chunks[2], (int) $time_chunks[3], (int) $date_chunks[1], (int) $date_chunks[2], (int) $date_chunks[0] );
666 }
667
668 // Attempt to parse non-numeric newer date
669 if ( ! empty( $newer_date ) && ! is_numeric( $newer_date ) ) {
670 $time_chunks = explode( ':', str_replace( ' ', ':', $newer_date ) );
671 $date_chunks = explode( '-', str_replace( ' ', '-', $newer_date ) );
672 $newer_date = gmmktime( (int) $time_chunks[1], (int) $time_chunks[2], (int) $time_chunks[3], (int) $date_chunks[1], (int) $date_chunks[2], (int) $date_chunks[0] );
673 }
674
675 // Set newer date to current time
676 if ( empty( $newer_date ) ) {
677 $newer_date = strtotime( current_time( 'mysql', $gmt ) );
678 }
679
680 // Cast both dates to ints to avoid notices & errors with invalid values
681 $newer_date = intval( $newer_date );
682 $older_date = intval( $older_date );
683
684 // Difference in seconds
685 $since = intval( $newer_date - $older_date );
686
687 // Something went wrong with date calculation and we ended up with a negative date.
688 if ( 0 > $since ) {
689 $output = $unknown_text;
690
691 // We only want to output two chunks of time here, eg:
692 // x years, xx months
693 // x days, xx hours
694 // so there's only two bits of calculation below:
695 } else {
696
697 // Default count values
698 $count = 0;
699 $count2 = 0;
700
701 // Step one: the first chunk
702 for ( $i = 0, $j = count( $chunks ); $i < $j; ++$i ) {
703 $seconds = $chunks[ $i ][0];
704
705 // Finding the biggest chunk (if the chunk fits, break)
706 $count = floor( $since / $seconds );
707 if ( 0 != $count ) {
708 break;
709 }
710 }
711
712 // If $i iterates all the way to $j, then the event happened 0 seconds ago
713 if ( ! isset( $chunks[ $i ] ) ) {
714 $output = $right_now_text;
715
716 } else {
717
718 // Set output var
719 $output = sprintf( translate_nooped_plural( $chunks[ $i ][1], $count, 'bbpress' ), bbp_number_format_i18n( $count ) );
720
721 // Step two: the second chunk
722 if ( $i + 2 < $j ) {
723 $seconds2 = $chunks[ $i + 1 ][0];
724 $count2 = floor( ( $since - ( $seconds * $count ) ) / $seconds2 );
725
726 // Add to output var
727 if ( 0 != $count2 ) {
728 $output .= _x( ',', 'Separator in time since', 'bbpress' ) . ' ';
729 $output .= sprintf( translate_nooped_plural( $chunks[ $i + 1 ][1], $count2, 'bbpress' ), bbp_number_format_i18n( $count2 ) );
730 }
731 }
732
733 // Empty counts, so fallback to right now
734 if ( empty( $count ) && empty( $count2 ) ) {
735 $output = $right_now_text;
736 }
737 }
738 }
739
740 // Append 'ago' to the end of time-since if not 'right now'
741 if ( $output != $right_now_text ) {
742 $output = sprintf( $ago_text, $output );
743 }
744
745 // Filter & return
746 return apply_filters( 'bbp_get_time_since', $output, $older_date, $newer_date );
747 }
748
749 /** Revisions *****************************************************************/
750
751 /**
752 * Formats the reason for editing the topic/reply.
753 *
754 * Does these things:
755 * - Trimming
756 * - Removing periods from the end of the string
757 * - Trimming again
758 *
759 * @since 2.0.0 bbPress (r2782)
760 *
761 * @param string $reason Optional. User submitted reason for editing.
762 * @return string Status of topic
763 */
764 function bbp_format_revision_reason( $reason = '' ) {
765 $reason = (string) $reason;
766
767 // Bail if reason is empty
768 if ( empty( $reason ) ) {
769 return $reason;
770 }
771
772 // Trimming
773 $reason = trim( $reason );
774
775 // We add our own full stop.
776 while ( substr( $reason, -1 ) === '.' ) {
777 $reason = substr( $reason, 0, -1 );
778 }
779
780 // Trim again
781 $reason = trim( $reason );
782
783 return $reason;
784 }
785
786 /** Users *********************************************************************/
787
788 /**
789 * Format the display name of a user.
790 *
791 * Abstracts mbstring library check and fallback to utf8_encode().
792 *
793 * @link https://bbpress.trac.wordpress.org/ticket/2141
794 *
795 * @since 2.6.14
796 *
797 * @param string $display_name The author display
798 * @return string
799 */
800 function bbp_format_user_display_name( $display_name = '' ) {
801
802 // Default return value
803 $retval = $display_name;
804
805 // Use the mbstring library if possible
806 if ( function_exists( 'mb_check_encoding' ) && ! mb_check_encoding( $display_name, 'UTF-8' ) ) {
807
808 $retval = mb_convert_encoding( $display_name, 'UTF-8', 'ISO-8859-1' );
809
810 // Updated check: use wp_is_valid_utf8() instead of deprecated seems_utf8()
811 } elseif ( function_exists( 'wp_is_valid_utf8' ) && ! wp_is_valid_utf8( $display_name ) ) {
812
813 $retval = utf8_encode( $display_name );
814
815 }
816
817 // Return
818 return $retval;
819 }
820