PluginProbe
bbPress / 2.6.17
bbPress v2.6.17
2.6.17 trunk 2.0 2.0-beta-1 2.0-beta-2b 2.0-beta-3 2.0-beta-3b 2.0-rc-2 2.0-rc-3 2.0-rc-4 2.0-rc-5 2.0.1 2.0.2 2.0.3 2.1 2.1-beta-1 2.1-rc1 2.1-rc2 2.1-rc3 2.1-rc4 2.1.1 2.1.2 2.1.3 2.2 2.2.1 All 72 releases
bbpress / includes / common / formatting.php

formatting.php in bbPress 2.6.17, at includes/common/formatting.php

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