PluginProbe
bbPress / 2.6.6
bbPress v2.6.6
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 2.2.2 All 71 releases
bbpress / includes / common / formatting.php

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

785 lines 23.0 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