PluginProbe
bbPress / 2.6.19
bbPress v2.6.19
2.6.19 2.6.18 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 All 74 releases
bbpress / includes / users / functions.php

functions.php in bbPress 2.6.19, at includes/users/functions.php

1,345 lines 39.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 User Functions
5 *
6 * @package bbPress
7 * @subpackage Functions
8 */
9
10 // Exit if accessed directly
11 defined( 'ABSPATH' ) || exit;
12
13 /**
14 * Redirect back to $url when attempting to use the login page
15 *
16 * @since 2.0.0 bbPress (r2815)
17 *
18 * @param string $url The url
19 * @param string $raw_url Raw url
20 * @param object $user User object
21 */
22 function bbp_redirect_login( $url = '', $raw_url = '', $user = '' ) {
23
24 // Raw redirect_to was passed, so use it
25 if ( ! empty( $raw_url ) ) {
26 $url = $raw_url;
27
28 // $url was manually set in wp-login.php to redirect to admin
29 } elseif ( admin_url() === $url ) {
30 $url = home_url();
31
32 // $url is empty
33 } elseif ( empty( $url ) ) {
34 $url = home_url();
35 }
36
37 // Filter & return
38 return apply_filters( 'bbp_redirect_login', $url, $raw_url, $user );
39 }
40
41 /**
42 * Is an anonymous topic/reply being made?
43 *
44 * @since 2.0.0 bbPress (r2670)
45 *
46 * @return bool True if anonymous is allowed and user is not logged in, false if
47 * anonymous is not allowed or user is logged in
48 */
49 function bbp_is_anonymous() {
50 $is_anonymous = ( ! is_user_logged_in() && bbp_allow_anonymous() );
51
52 // Filter & return
53 return (bool) apply_filters( 'bbp_is_anonymous', $is_anonymous );
54 }
55
56 /**
57 * Echoes the values for current poster (uses WP comment cookies)
58 *
59 * @since 2.0.0 bbPress (r2734)
60 *
61 * @param string $key Which value to echo?
62 */
63 function bbp_current_anonymous_user_data( $key = '' ) {
64 echo esc_attr( bbp_get_current_anonymous_user_data( $key ) );
65 }
66
67 /**
68 * Get the cookies for current poster (uses WP comment cookies).
69 *
70 * @since 2.0.0 bbPress (r2734)
71 *
72 * @param string $key Optional. Which value to get? If not given, then
73 * an array is returned.
74 * @return string|array Cookie(s) for current poster
75 */
76 function bbp_get_current_anonymous_user_data( $key = '' ) {
77
78 // Array of allowed cookie names
79 $cookie_names = array(
80 'name' => 'comment_author',
81 'email' => 'comment_author_email',
82 'url' => 'comment_author_url',
83
84 // Here just for the sake of them, use the above ones
85 'comment_author' => 'comment_author',
86 'comment_author_email' => 'comment_author_email',
87 'comment_author_url' => 'comment_author_url',
88 );
89
90 // Get the current poster's info from the cookies
91 $bbp_current_poster = wp_get_current_commenter();
92
93 // Sanitize the cookie key being retrieved
94 $key = sanitize_key( $key );
95
96 // Maybe return a specific key
97 if ( ! empty( $key ) && in_array( $key, array_keys( $cookie_names ), true ) ) {
98 return $bbp_current_poster[ $cookie_names[ $key ] ];
99 }
100
101 // Return all keys
102 return $bbp_current_poster;
103 }
104
105 /**
106 * Set the cookies for current poster (uses WP comment cookies)
107 *
108 * @since 2.0.0 bbPress (r2734)
109 *
110 * @param array $anonymous_data Optional - if it's an anonymous post. Do not
111 * supply if supplying $author_id. Should be
112 * sanitized (see {@link bbp_filter_anonymous_post_data()}
113 */
114 function bbp_set_current_anonymous_user_data( $anonymous_data = array() ) {
115
116 // Bail if empty or not an array
117 if ( empty( $anonymous_data ) || ! is_array( $anonymous_data ) ) {
118 return;
119 }
120
121 // Setup cookie expiration
122 $lifetime = (int) apply_filters( 'comment_cookie_lifetime', 30000000 );
123 $expiry = time() + $lifetime;
124 $secure = ( 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ) );
125
126 // Set the cookies
127 setcookie( 'comment_author_' . COOKIEHASH, $anonymous_data['bbp_anonymous_name'], $expiry, COOKIEPATH, COOKIE_DOMAIN, $secure );
128 setcookie( 'comment_author_email_' . COOKIEHASH, $anonymous_data['bbp_anonymous_email'], $expiry, COOKIEPATH, COOKIE_DOMAIN, $secure );
129 setcookie( 'comment_author_url_' . COOKIEHASH, $anonymous_data['bbp_anonymous_website'], $expiry, COOKIEPATH, COOKIE_DOMAIN, $secure );
130 }
131
132 /**
133 * Get the poster IP address
134 *
135 * @since 2.0.0 bbPress (r3120)
136 * @since 2.6.0 bbPress (r5609) Added `empty()` check for unit tests
137 *
138 * @return string
139 */
140 function bbp_current_author_ip() {
141
142 // Check for remote address
143 $remote_address = ! empty( $_SERVER['REMOTE_ADDR'] )
144 ? wp_unslash( $_SERVER['REMOTE_ADDR'] )
145 : '127.0.0.1';
146
147 // Remove any unsavory bits
148 $retval = preg_replace( '/[^0-9a-fA-F:., ]/', '', $remote_address );
149
150 // Filter & return
151 return apply_filters( 'bbp_current_author_ip', $retval, $remote_address );
152 }
153
154 /**
155 * Get the poster user agent
156 *
157 * @since 2.0.0 bbPress (r3447)
158 *
159 * @return string
160 */
161 function bbp_current_author_ua() {
162 $retval = ! empty( $_SERVER['HTTP_USER_AGENT'] )
163 ? mb_substr( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ), 0, 254 )
164 : '';
165
166 // Filter & return
167 return apply_filters( 'bbp_current_author_ua', $retval );
168 }
169
170 /** Edit **********************************************************************/
171
172 /**
173 * Filter user profile data according to the current user's field permissions.
174 *
175 * @since 2.6.17 bbPress (r7514)
176 *
177 * @param array $data Submitted user profile data.
178 * @param int $user_id User being edited.
179 * @return array Filtered user profile data.
180 */
181 function bbp_filter_user_edit_post_data( $data = array(), $user_id = 0 ) {
182 $user_id = bbp_get_user_id( $user_id, false, false );
183 $user = get_userdata( $user_id );
184
185 // Remove general profile fields.
186 if ( ! bbp_current_user_can_edit_user_field( 'profile', $user_id ) ) {
187 unset(
188 $data['first_name'],
189 $data['last_name'],
190 $data['nickname'],
191 $data['display_name'],
192 $data['url'],
193 $data['description'],
194 $data['locale']
195 );
196
197 // Remove dynamic WordPress contact methods.
198 if ( ! empty( $user ) ) {
199 foreach ( array_keys( wp_get_user_contact_methods( $user ) ) as $contact_method ) {
200 unset( $data[ $contact_method ] );
201 }
202 }
203 }
204
205 // Preserve the existing email address.
206 if ( ! bbp_current_user_can_edit_user_field( 'email', $user_id ) ) {
207 $data['email'] = ! empty( $user->user_email )
208 ? $user->user_email
209 : '';
210 }
211
212 // Remove password fields.
213 if ( ! bbp_current_user_can_edit_user_field( 'password', $user_id ) ) {
214 unset( $data['pass1'], $data['pass2'] );
215 }
216
217 // Remove the WordPress site role.
218 if ( ! bbp_current_user_can_edit_user_field( 'site_role', $user_id ) ) {
219 unset( $data['role'] );
220 }
221
222 // Filter & return.
223 return (array) apply_filters( 'bbp_filter_user_edit_post_data', $data, $user_id, bbp_get_current_user_id() );
224 }
225
226 /**
227 * Return whether an email change requires confirmation from the edited user.
228 *
229 * Self-service changes require confirmation by default, while privileged edits
230 * to another user update the address directly.
231 *
232 * @since 2.6.17 bbPress (r7514)
233 *
234 * @param int $user_id User being edited.
235 * @return bool Whether confirmation is required.
236 */
237 function bbp_user_email_change_requires_confirmation( $user_id = 0 ) {
238 $user_id = bbp_get_user_id( $user_id, false, false );
239 $retval = ( bbp_get_current_user_id() === $user_id );
240
241 // Filter & return.
242 return (bool) apply_filters( 'bbp_user_email_change_requires_confirmation', $retval, $user_id, bbp_get_current_user_id() );
243 }
244
245 /**
246 * Update a user's network role when the profile form submitted that field.
247 *
248 * A missing checkbox is ambiguous unless the form also submits its presence
249 * marker. Continue accepting checked boxes from older custom templates.
250 *
251 * @since 2.6.19 bbPress (r7696)
252 *
253 * @param int $user_id User being edited.
254 * @param array $data Submitted profile fields.
255 */
256 function bbp_maybe_update_user_super_admin( $user_id = 0, $data = array() ) {
257 if ( ! is_multisite() || bbp_is_user_home_edit() || ! current_user_can( 'manage_network_options' ) || ! is_super_admin() ) {
258 return;
259 }
260
261 if ( ! isset( $data['bbp_super_admin_present'] ) && ! isset( $data['super_admin'] ) ) {
262 return;
263 }
264
265 if ( empty( $data['super_admin'] ) ) {
266 revoke_super_admin( $user_id );
267 } else {
268 grant_super_admin( $user_id );
269 }
270 }
271
272 /**
273 * Handles the front end user editing from POST requests
274 *
275 * @since 2.0.0 bbPress (r2688)
276 *
277 * @param string $action The requested action to compare this function to
278 */
279 function bbp_edit_user_handler( $action = '' ) {
280
281 // Bail if action is not `bbp-update-user`
282 if ( 'bbp-update-user' !== $action ) {
283 return;
284 }
285
286 // Bail if in wp-admin
287 if ( is_admin() ) {
288 return;
289 }
290
291 // Get the displayed user ID
292 $user_id = bbp_get_displayed_user_id();
293
294 // Request check
295 if ( ! bbp_is_user_profile_form_post_request( $user_id ) ) {
296 bbp_add_error( 'bbp_update_user_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
297 return;
298 }
299
300 // Cap check
301 if ( ! current_user_can( 'edit_user', $user_id ) ) {
302 bbp_add_error( 'bbp_update_user_capability', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
303 return;
304 }
305
306 // Enforce field-level profile permissions before validating and saving.
307 $_POST = bbp_filter_user_edit_post_data( $_POST, $user_id );
308
309 // Empty email check
310 if ( empty( $_POST['email'] ) ) {
311 bbp_add_error( 'bbp_user_email_empty', __( '<strong>Error</strong>: That is not a valid email address.', 'bbpress' ), array( 'form-field' => 'email' ) );
312 return;
313 }
314
315 // Get the users current email address to use for comparisons
316 $user_email = bbp_get_displayed_user_field( 'user_email', 'raw' );
317
318 // Bail if no email change
319 if ( $user_email !== $_POST['email'] ) {
320
321 // Check that new email address is valid
322 if ( ! is_email( $_POST['email'] ) ) {
323 bbp_add_error( 'bbp_user_email_invalid', __( '<strong>Error</strong>: That is not a valid email address.', 'bbpress' ), array( 'form-field' => 'email' ) );
324 return;
325 }
326
327 // Check if email address is already in use
328 if ( email_exists( $_POST['email'] ) ) {
329 bbp_add_error( 'bbp_user_email_taken', __( '<strong>Error</strong>: That email address is already in use.', 'bbpress' ), array( 'form-field' => 'email' ) );
330 return;
331 }
332
333 if ( bbp_user_email_change_requires_confirmation( $user_id ) ) {
334 // Update the option.
335 $option = array(
336 'hash' => md5( $_POST['email'] . time() . wp_rand() ),
337 'newemail' => $_POST['email'],
338 );
339 update_user_meta( $user_id, '_new_email', $option );
340
341 // Attempt to notify the user of email address change.
342 bbp_edit_user_email_send_notification( $user_id, $option );
343
344 // Set the POST email variable back to the user's email address
345 // so `edit_user()` does not attempt to update it. This is not ideal,
346 // but it's also what send_confirmation_on_profile_email() does.
347 $_POST['email'] = $user_email;
348 }
349 }
350
351 // Do action based on who's profile you're editing
352 $edit_action = bbp_is_user_home_edit()
353 ? 'personal_options_update'
354 : 'edit_user_profile_update';
355
356 do_action( $edit_action, $user_id );
357
358 // Prevent edit_user() from wiping out the user's Toolbar on front setting
359 if ( ! isset( $_POST['admin_bar_front'] ) && _get_admin_bar_pref( 'front', $user_id ) ) {
360 $_POST['admin_bar_front'] = 1;
361 }
362
363 // Bail if errors already exist
364 if ( bbp_has_errors() ) {
365 return;
366 }
367
368 // Handle user edit
369 $edit_user = edit_user( $user_id );
370
371 // Error(s) editng the user, so copy them into the global
372 if ( is_wp_error( $edit_user ) ) {
373 bbpress()->errors = $edit_user;
374
375 // Successful edit to redirect
376 } elseif ( is_integer( $edit_user ) ) {
377
378 // Maybe update super admin ability
379 bbp_maybe_update_user_super_admin( $edit_user, $_POST );
380
381 // Redirect
382 $args = array( 'updated' => 'true' );
383 $user_url = bbp_get_user_profile_edit_url( $edit_user );
384 $redirect = add_query_arg( $args, $user_url );
385
386 bbp_redirect( $redirect );
387 }
388 }
389
390 /**
391 * Handles user email address updating from GET requests
392 *
393 * @since 2.6.0 bbPress (r5663)
394 *
395 * @param string $action
396 */
397 function bbp_user_email_change_handler( $action = '' ) {
398
399 // Bail if action is not `bbp-update-user-email`
400 if ( 'bbp-update-user-email' !== $action ) {
401 return;
402 }
403
404 // Bail if not on users own profile
405 if ( ! bbp_is_user_home_edit() ) {
406 return;
407 }
408
409 // Bail if not attempting to modify user email address
410 if ( empty( $_GET['newuseremail'] ) && empty( $_GET['dismiss'] ) ) {
411 return;
412 }
413
414 // Get the displayed user ID & option key
415 $user_id = bbp_get_displayed_user_id();
416 $key = '_new_email';
417 $redirect_to = bbp_get_user_profile_edit_url( $user_id );
418
419 // Execute confirmed email change.
420 if ( ! empty( $_GET['newuseremail'] ) ) {
421
422 // Check for email address change option
423 $new_email = get_user_meta( $user_id, $key, true );
424
425 // Redirect if *no* email address change exists
426 if ( false === $new_email ) {
427 bbp_redirect( $redirect_to );
428 }
429
430 // Cleanup & redirect if *invalid* email address change exists
431 if ( empty( $new_email['hash'] ) || empty( $new_email['newemail'] ) ) {
432 delete_user_meta( $user_id, $key );
433
434 bbp_redirect( $redirect_to );
435 }
436
437 // Compare hashes, and update user if hashes match
438 if ( hash_equals( $new_email['hash'], $_GET['newuseremail'] ) ) {
439
440 // Does another user have this email address already?
441 if ( email_exists( $new_email['newemail'] ) ) {
442 delete_user_meta( $user_id, $key );
443
444 bbp_add_error( 'bbp_user_email_taken', __( '<strong>Error</strong>: That email address is already in use.', 'bbpress' ), array( 'form-field' => 'email' ) );
445
446 // Email address is good to change to
447 } else {
448
449 // Create a stdClass (for easy call to wp_update_user())
450 $user = new stdClass();
451 $user->ID = $user_id;
452 $user->user_email = esc_html( trim( $new_email['newemail'] ) );
453
454 // Attempt to update user email
455 $update_user = wp_update_user( $user );
456
457 // Error(s) editing the user, so copy them into the global
458 if ( is_wp_error( $update_user ) ) {
459 bbpress()->errors = $update_user;
460
461 // All done, so redirect and show the updated message
462 } else {
463
464 // Update signups table, if signups table & entry exists
465 // For Multisite & BuddyPress compatibility
466 $bbp_db = bbp_db();
467 if ( ! empty( $bbp_db->signups ) && $bbp_db->get_var( $bbp_db->prepare( "SELECT user_login FROM {$bbp_db->signups} WHERE user_login = %s", bbp_get_displayed_user_field( 'user_login', 'raw' ) ) ) ) {
468 $bbp_db->query( $bbp_db->prepare( "UPDATE {$bbp_db->signups} SET user_email = %s WHERE user_login = %s", $user->user_email, bbp_get_displayed_user_field( 'user_login', 'raw' ) ) );
469 }
470
471 delete_user_meta( $user_id, $key );
472
473 bbp_redirect( add_query_arg( array( 'updated' => 'true' ), $redirect_to ) );
474 }
475 }
476 }
477
478 // Delete new email address from user options
479 } elseif ( ! empty( $_GET['dismiss'] ) && ( "{$user_id}{$key}" === $_GET['dismiss'] ) ) {
480 if ( ! bbp_verify_nonce_request( "dismiss-{$user_id}{$key}" ) ) {
481 bbp_add_error( 'bbp_dismiss_new_email_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
482 return;
483 }
484
485 delete_user_meta( $user_id, $key );
486 bbp_redirect( $redirect_to );
487 }
488 }
489
490 /**
491 * Sends an email when an email address change occurs on POST requests
492 *
493 * @since 2.6.0 bbPress (r5663)
494 *
495 * @see send_confirmation_on_profile_email()
496 */
497 function bbp_edit_user_email_send_notification( $user_id = 0, $args = array() ) {
498
499 // Parse args
500 $r = bbp_parse_args(
501 $args,
502 array(
503 'hash' => '',
504 'newemail' => '',
505 )
506 );
507
508 // Bail if any relevant parameters are empty
509 if ( empty( $user_id ) || empty( $r['hash'] ) || empty( $r['newemail'] ) ) {
510 bbp_add_error( 'bbp_user_email_invalid_hash', __( '<strong>Error</strong>: An error occurred while updating your email address.', 'bbpress' ), array( 'form-field' => 'email' ) );
511 return;
512 }
513
514 // Build the nonced URL to dismiss the pending change
515 $user_login = bbp_get_displayed_user_field( 'user_login', 'raw' );
516 $user_url = bbp_get_user_profile_edit_url( $user_id );
517 $confirm_url = add_query_arg(
518 array(
519 'action' => 'bbp-update-user-email',
520 'newuseremail' => $r['hash']
521 ),
522 $user_url
523 );
524
525 /* translators: 1: Username, 2: Confirmation URL, 3: New email address, 4: Site name, 5: Site URL */
526 $email_text = __(
527 '%1$s
528
529 Someone requested a change to the email address on your account.
530
531 Please click the following link to confirm this change:
532 %2$s
533
534 If you did not request this, you can safely ignore and delete this notification.
535
536 This email was sent to: %3$s
537
538 Regards,
539 The %4$s Team
540 %5$s',
541 'bbpress'
542 );
543
544 /**
545 * Filter the email text sent when a user changes emails.
546 *
547 * The following strings have a special meaning and will get replaced dynamically:
548 *
549 * %1$s - The current user's username
550 * %2$s - The link to click on to confirm the email change
551 * %3$s - The new email
552 * %4$s - The name of the site
553 * %5$s - The URL to the site
554 *
555 * @param string $email_text Text in the email.
556 * @param string $r New user email that the current user has changed to.
557 */
558 $content = apply_filters( 'bbp_user_email_update_content', $email_text, $r );
559
560 // Build the email message
561 $message = sprintf( $content, $user_login, $confirm_url, $r['newemail'], get_site_option( 'site_name' ), network_home_url() );
562
563 // Build the email subject
564 /* translators: %s: Site name */
565 $subject = sprintf( __( '[%s] New Email Address', 'bbpress' ), wp_specialchars_decode( get_option( 'blogname' ) ) );
566
567 // Send the email
568 wp_mail( $r['newemail'], $subject, $message );
569 }
570
571 /**
572 * Conditionally hook the core WordPress output actions to the end of the
573 * default user's edit profile template
574 *
575 * This allows clever plugin authors to conditionally unhook the WordPress core
576 * output actions if they don't want any unexpected junk to appear there, and
577 * also avoids needing to pollute the templates with additional logic and actions.
578 *
579 * @since 2.2.0 bbPress (r4274)
580 */
581 function bbp_user_edit_after() {
582 $action = bbp_is_user_home_edit() ? 'show_user_profile' : 'edit_user_profile';
583
584 do_action( $action, get_userdata( bbp_get_displayed_user_id() ) );
585 }
586
587 /** User Queries **************************************************************/
588
589 /**
590 * Get the topics that a user created
591 *
592 * @since 2.0.0 bbPress (r2660)
593 * @since 2.6.0 bbPress (r6619) Signature changed to accept an array of arguments
594 *
595 * @param array $args Optional. Arguments to pass into bbp_has_topics()
596 *
597 * @return bool True if user has started topics, otherwise false
598 */
599 function bbp_get_user_topics_started( $args = array() ) {
600
601 // Backwards compat for pre-2.6.0
602 if ( is_numeric( $args ) ) {
603 $args = array(
604 'author' => bbp_get_user_id( $args, false, false )
605 );
606 }
607
608 // Default arguments
609 $defaults = array(
610 'author' => bbp_get_displayed_user_id()
611 );
612
613 // Parse arguments
614 $r = bbp_parse_args( $args, $defaults, 'get_user_topics_started' );
615
616 // Get the topics
617 $query = bbp_has_topics( $r );
618 $user_id = $r['author'];
619
620 // Filter & return
621 return apply_filters( 'bbp_get_user_topics_started', $query, $user_id, $r, $args );
622 }
623
624 /**
625 * Get the replies that a user created
626 *
627 * @since 2.2.0 bbPress (r4228)
628 * @since 2.6.0 bbPress (r6619) Signature changed to accept an array of arguments
629 *
630 * @param array $args Optional. Arguments to pass into bbp_has_replies()
631 *
632 * @return bool True if user has created replies, otherwise false
633 */
634 function bbp_get_user_replies_created( $args = array() ) {
635
636 // Backwards compat for pre-2.6.0
637 if ( is_numeric( $args ) ) {
638 $args = array(
639 'author' => bbp_get_user_id( $args, false, false ),
640 'post_type' => bbp_get_reply_post_type(),
641 'order' => 'DESC'
642 );
643 }
644
645 // Default arguments
646 $defaults = array(
647 'author' => bbp_get_displayed_user_id(),
648 'post_type' => bbp_get_reply_post_type(),
649 'order' => 'DESC'
650 );
651
652 // Parse arguments
653 $r = bbp_parse_args( $args, $defaults, 'get_user_replies_created' );
654 $r['_bbp_public_topic_replies'] = ! bbp_get_view_all( 'edit_others_replies' );
655 unset( $r['_bbp_search_private_topic_replies'] );
656
657 // Get the replies
658 $query = bbp_has_replies( $r );
659 $user_id = $r['author'];
660
661 // Filter & return
662 return apply_filters( 'bbp_get_user_replies_created', $query, $user_id, $r, $args );
663 }
664
665 /**
666 * Get user IDs from nicenames
667 *
668 * This function is primarily used when saving object moderators
669 *
670 * @since 2.6.0 bbPress (r6056)
671 *
672 * @param mixed $user_nicenames
673 * @return array
674 */
675 function bbp_get_user_ids_from_nicenames( $user_nicenames = array() ) {
676
677 // Default value
678 $retval = array();
679
680 // Only query if nicenames
681 if ( ! empty( $user_nicenames ) ) {
682
683 // Maybe explode by comma
684 $user_nicenames = ( is_string( $user_nicenames ) && strstr( $user_nicenames, ',' ) )
685 ? explode( ',', $user_nicenames )
686 : (array) $user_nicenames;
687
688 // Sanitize each nicename in the array
689 $user_nicenames = array_map( 'sanitize_title', $user_nicenames );
690
691 // Get users
692 $users = get_users(
693 array(
694 'nicename__in' => $user_nicenames
695 )
696 );
697
698 // Pluck or empty
699 if ( ! empty( $users ) ) {
700 $retval = wp_list_pluck( $users, 'ID' );
701 }
702 }
703
704 // Filter & return
705 return (array) apply_filters( 'bbp_get_user_ids_from_nicenames', $retval, $user_nicenames );
706 }
707
708 /**
709 * Get user nicenames from IDs
710 *
711 * This function is primarily used when saving object moderators
712 *
713 * @since 2.6.0 bbPress (r6056)
714 *
715 * @param mixed $user_ids
716 * @return array
717 */
718 function bbp_get_user_nicenames_from_ids( $user_ids = array() ) {
719
720 // Default value
721 $retval = array();
722
723 // Only query if nicenames
724 if ( ! empty( $user_ids ) ) {
725
726 // Get users
727 $users = get_users(
728 array(
729 'include' => $user_ids
730 )
731 );
732
733 // Pluck or empty
734 if ( ! empty( $users ) ) {
735 $retval = wp_list_pluck( $users, 'user_nicename' );
736 }
737 }
738
739 // Filter & return
740 return (array) apply_filters( 'bbp_get_user_nicenames_from_ids', $retval, $user_ids );
741 }
742
743 /** Post Counts ***************************************************************/
744
745 /**
746 * Return the raw database count of topics by a user
747 *
748 * @since 2.1.0 bbPress (r3633)
749 *
750 * @param int $user_id User ID to get count for
751 *
752 * @return int Raw DB count of topics
753 */
754 function bbp_get_user_topic_count_raw( $user_id = 0 ) {
755 $user_id = bbp_get_user_id( $user_id );
756 $bbp_db = bbp_db();
757 $statii = "'" . implode( "', '", bbp_get_public_topic_statuses() ) . "'";
758 $sql = "SELECT COUNT(*)
759 FROM {$bbp_db->posts}
760 WHERE post_type = %s
761 AND post_status IN ({$statii})
762 AND post_author = %d";
763
764 $query = $bbp_db->prepare( $sql, bbp_get_topic_post_type(), $user_id );
765 $count = (int) $bbp_db->get_var( $query );
766
767 // Filter & return
768 return (int) apply_filters( 'bbp_get_user_topic_count_raw', $count, $user_id );
769 }
770
771 /**
772 * Return the raw database count of replies by a user
773 *
774 * @since 2.1.0 bbPress (r3633)
775 *
776 * @param int $user_id User ID to get count for
777 *
778 * @return int Raw DB count of replies
779 */
780 function bbp_get_user_reply_count_raw( $user_id = 0 ) {
781 $user_id = bbp_get_user_id( $user_id );
782 $bbp_db = bbp_db();
783 $statii = "'" . implode( "', '", bbp_get_public_reply_statuses() ) . "'";
784 $sql = "SELECT COUNT(*)
785 FROM {$bbp_db->posts}
786 WHERE post_type = %s
787 AND post_status IN ({$statii})
788 AND post_author = %d";
789
790 $query = $bbp_db->prepare( $sql, bbp_get_reply_post_type(), $user_id );
791 $count = (int) $bbp_db->get_var( $query );
792
793 // Filter & return
794 return (int) apply_filters( 'bbp_get_user_reply_count_raw', $count, $user_id );
795 }
796
797 /**
798 * Bump the topic count for a user by a certain amount.
799 *
800 * @since 2.6.0 bbPress (r5309)
801 * @since 2.6.17 bbPress (r7451) Rebuild the count when the user option is missing.
802 *
803 * @param int $user_id
804 * @param int $difference
805 */
806 function bbp_bump_user_topic_count( $user_id = 0, $difference = 1 ) {
807
808 // Bail if no bump
809 if ( empty( $difference ) ) {
810 return false;
811 }
812
813 // Validate user ID
814 $user_id = bbp_get_user_id( $user_id );
815 if ( empty( $user_id ) ) {
816 return false;
817 }
818
819 // Get the current count, accounting for persisted changes if it is missing
820 $difference = (int) $difference;
821 $count = ( false === get_user_option( '_bbp_topic_count', $user_id ) )
822 ? bbp_get_user_topic_count_raw( $user_id ) - $difference
823 : bbp_get_user_topic_count( $user_id, true );
824
825 $user_topic_count = (int) bbp_number_not_negative( $count + $difference );
826
827 // Add them up and filter them
828 $new_count = (int) apply_filters( 'bbp_bump_user_topic_count', $user_topic_count, $user_id, $difference, $count );
829
830 // Preserve absolute count filters before using the atomic difference
831 $difference = ( $new_count === $user_topic_count )
832 ? $new_count - $count
833 : false;
834
835 return bbp_update_user_topic_count( $user_id, $new_count, $difference );
836 }
837
838 /**
839 * Bump the reply count for a user by a certain amount.
840 *
841 * @since 2.6.0 bbPress (r5309)
842 * @since 2.6.17 bbPress (r7451) Rebuild the count when the user option is missing.
843 *
844 * @param int $user_id
845 * @param int $difference
846 */
847 function bbp_bump_user_reply_count( $user_id = 0, $difference = 1 ) {
848
849 // Bail if no bump
850 if ( empty( $difference ) ) {
851 return false;
852 }
853
854 // Validate user ID
855 $user_id = bbp_get_user_id( $user_id );
856 if ( empty( $user_id ) ) {
857 return false;
858 }
859
860 // Get the current count, accounting for persisted changes if it is missing
861 $difference = (int) $difference;
862 $count = ( false === get_user_option( '_bbp_reply_count', $user_id ) )
863 ? bbp_get_user_reply_count_raw( $user_id ) - $difference
864 : bbp_get_user_reply_count( $user_id, true );
865
866 $user_reply_count = (int) bbp_number_not_negative( $count + $difference );
867
868 // Add them up and filter them
869 $new_count = (int) apply_filters( 'bbp_bump_user_reply_count', $user_reply_count, $user_id, $difference, $count );
870
871 // Preserve absolute count filters before using the atomic difference
872 $difference = ( $new_count === $user_reply_count )
873 ? $new_count - $count
874 : false;
875
876 return bbp_update_user_reply_count( $user_id, $new_count, $difference );
877 }
878
879 /**
880 * Update user counts when a topic or reply changes authors.
881 *
882 * @since 2.6.17 bbPress (r7468)
883 *
884 * @param int $post_id Post ID.
885 * @param WP_Post $post_after Post object following the update.
886 * @param WP_Post $post_before Post object before the update.
887 */
888 function bbp_update_counts_on_post_author_change( $post_id = 0, $post_after = false, $post_before = false ) {
889
890 // Bail if the author or post type did not change as expected
891 if ( ( $post_after->post_author === $post_before->post_author ) || ( $post_after->post_type !== $post_before->post_type ) ) {
892 return;
893 }
894
895 // Set topic public membership
896 if ( bbp_get_topic_post_type() === $post_after->post_type ) {
897 $public_statuses = bbp_get_public_topic_statuses();
898 $was_public = in_array( $post_before->post_status, $public_statuses, true );
899 $is_public = in_array( $post_after->post_status, $public_statuses, true );
900 $is_topic = true;
901
902 // Set reply public membership
903 } elseif ( bbp_get_reply_post_type() === $post_after->post_type ) {
904 $public_statuses = bbp_get_public_reply_statuses();
905 $was_public = in_array( $post_before->post_status, $public_statuses, true );
906 $is_public = in_array( $post_after->post_status, $public_statuses, true );
907 $is_topic = false;
908
909 // Bail if this is not a topic or reply
910 } else {
911 return;
912 }
913
914 // The transition callback already handles posts that were not public
915 if ( ! $was_public ) {
916 return;
917 }
918
919 // Transfer a public contribution between authors
920 if ( $is_public ) {
921 if ( $is_topic ) {
922 bbp_bump_user_topic_count( $post_before->post_author, -1 );
923 bbp_bump_user_topic_count( $post_after->post_author, 1 );
924 } else {
925 bbp_bump_user_reply_count( $post_before->post_author, -1 );
926 bbp_bump_user_reply_count( $post_after->post_author, 1 );
927 }
928
929 // Repair both authors after the transition callback targeted the new author
930 } else {
931 foreach ( bbp_get_unique_array_values( array( $post_before->post_author, $post_after->post_author ) ) as $user_id ) {
932 if ( $is_topic ) {
933 bbp_update_user_topic_count( $user_id, bbp_get_user_topic_count_raw( $user_id ) );
934 } else {
935 bbp_update_user_reply_count( $user_id, bbp_get_user_reply_count_raw( $user_id ) );
936 }
937 }
938 }
939 }
940
941 /**
942 * Update topic engagements when a topic or reply changes authors.
943 *
944 * @since 2.6.17 bbPress (r7468)
945 *
946 * @param int $post_id Post ID.
947 * @param WP_Post $post_after Post object following the update.
948 * @param WP_Post $post_before Post object before the update.
949 */
950 function bbp_recalculate_engagements_on_post_author_change( $post_id = 0, $post_after = false, $post_before = false ) {
951
952 // Bail if the author did not change
953 if ( $post_after->post_author === $post_before->post_author ) {
954 return;
955 }
956
957 // Get the topic ID from a topic or reply
958 if ( bbp_get_topic_post_type() === $post_after->post_type ) {
959 $topic_id = $post_id;
960 } elseif ( bbp_get_reply_post_type() === $post_after->post_type ) {
961 $topic_id = bbp_get_reply_topic_id( $post_id );
962 } else {
963 return;
964 }
965
966 // Recalculate engagements and their count
967 bbp_recalculate_topic_engagements( $topic_id );
968 bbp_update_topic_voice_count( $topic_id );
969 }
970
971 /**
972 * Update counts and engagements when a deleted user's posts are reassigned.
973 *
974 * WordPress reassigns post authors directly in the database, bypassing the
975 * normal post update actions. Record affected topics before that write, then
976 * repair the replacement user's counts and those topics after it completes.
977 *
978 * @since 2.6.17 bbPress (r7468)
979 *
980 * @param int $user_id ID of the user being deleted.
981 * @param int|null $reassign ID of the user receiving the posts.
982 */
983 function bbp_update_counts_on_user_reassignment( $user_id = 0, $reassign = null ) {
984 static $topic_ids = array();
985
986 $user_id = (int) $user_id;
987 $reassign = (int) $reassign;
988
989 // Bail if posts are not being reassigned to another user
990 if ( empty( $user_id ) || empty( $reassign ) || ( $user_id === $reassign ) ) {
991 return;
992 }
993
994 $key = get_current_blog_id() . ':' . $user_id . ':' . $reassign;
995
996 // Record affected topics before WordPress changes their authors directly
997 if ( 'delete_user' === current_filter() ) {
998 $bbp_db = bbp_db();
999 $topic_type = bbp_get_topic_post_type();
1000 $reply_type = bbp_get_reply_post_type();
1001 $query = $bbp_db->prepare(
1002 "SELECT DISTINCT CASE WHEN post_type = %s THEN ID ELSE post_parent END FROM {$bbp_db->posts} WHERE post_author = %d AND post_type IN ( %s, %s )",
1003 $topic_type,
1004 $user_id,
1005 $topic_type,
1006 $reply_type
1007 );
1008
1009 $topic_ids[ $key ] = wp_parse_id_list( array_filter( $bbp_db->get_col( $query ) ) );
1010 return;
1011 }
1012
1013 // Bail unless WordPress completed the reassignment recorded above
1014 if ( ( 'deleted_user' !== current_filter() ) || ! isset( $topic_ids[ $key ] ) ) {
1015 return;
1016 }
1017
1018 $affected_topic_ids = $topic_ids[ $key ];
1019 unset( $topic_ids[ $key ] );
1020
1021 // Bail if the deleted user did not author any topics or replies
1022 if ( empty( $affected_topic_ids ) ) {
1023 return;
1024 }
1025
1026 // Recount contributions for the replacement user
1027 bbp_update_user_topic_count( $reassign, bbp_get_user_topic_count_raw( $reassign ) );
1028 bbp_update_user_reply_count( $reassign, bbp_get_user_reply_count_raw( $reassign ) );
1029
1030 // Rebuild engagements and voices for each affected topic
1031 foreach ( $affected_topic_ids as $topic_id ) {
1032 bbp_recalculate_topic_engagements( $topic_id, true );
1033 bbp_update_topic_voice_count( $topic_id );
1034 }
1035 }
1036
1037 /**
1038 * Helper function used to increase (by one) the count of topics for a user when
1039 * a topic is published.
1040 *
1041 * @since 2.6.0 bbPress (r5309)
1042 *
1043 * @param int $topic_id Topic ID.
1044 */
1045 function bbp_increase_user_topic_count( $topic_id = 0 ) {
1046
1047 // Bail if topic is not public
1048 if ( ! bbp_is_topic_public( $topic_id ) ) {
1049 return false;
1050 }
1051
1052 $user_id = bbp_get_topic_author_id( $topic_id );
1053 return bbp_bump_user_topic_count( $user_id, 1 );
1054 }
1055
1056 /**
1057 * Helper function used to increase (by one) the count of replies for a user when
1058 * a reply is published.
1059 *
1060 * @since 2.6.0 bbPress (r5309)
1061 *
1062 * @param int $reply_id Reply ID.
1063 */
1064 function bbp_increase_user_reply_count( $reply_id = 0 ) {
1065
1066 // Bail if reply is not public
1067 if ( ! bbp_is_reply_public( $reply_id ) ) {
1068 return false;
1069 }
1070
1071 $user_id = bbp_get_reply_author_id( $reply_id );
1072 return bbp_bump_user_reply_count( $user_id, 1 );
1073 }
1074
1075 /**
1076 * Helper function used to decrease (by one) the count of topics for a user when
1077 * a topic is unpublished.
1078 *
1079 * @since 2.6.0 bbPress (r5309)
1080 *
1081 * @param int $topic_id Topic ID.
1082 */
1083 function bbp_decrease_user_topic_count( $topic_id = 0 ) {
1084
1085 // Bail if topic is not public
1086 if ( ! bbp_is_topic_public( $topic_id ) ) {
1087 return false;
1088 }
1089
1090 $user_id = bbp_get_topic_author_id( $topic_id );
1091 return bbp_bump_user_topic_count( $user_id, -1 );
1092 }
1093
1094 /**
1095 * Helper function used to decrease (by one) the count of replies for a user when
1096 * a reply is unpublished.
1097 *
1098 * @since 2.6.0 bbPress (r5309)
1099 *
1100 * @param int $reply_id Reply ID.
1101 */
1102 function bbp_decrease_user_reply_count( $reply_id = 0 ) {
1103
1104 // Bail if reply is not public
1105 if ( ! bbp_is_reply_public( $reply_id ) ) {
1106 return false;
1107 }
1108
1109 $user_id = bbp_get_reply_author_id( $reply_id );
1110 return bbp_bump_user_reply_count( $user_id, -1 );
1111 }
1112
1113 /** Permissions ***************************************************************/
1114
1115 /**
1116 * Redirect if unauthorized user is attempting to edit another user
1117 *
1118 * This is hooked to 'bbp_template_redirect' and controls the conditions under
1119 * which a user can edit another user (or themselves.) If these conditions are
1120 * met, we assume a user cannot perform this task, and look for ways they can
1121 * earn the ability to access this template.
1122 *
1123 * @since 2.1.0 bbPress (r3607)
1124 */
1125 function bbp_check_user_edit() {
1126
1127 // Bail if not editing a user
1128 if ( ! bbp_is_single_user_edit() ) {
1129 return;
1130 }
1131
1132 // Default to false
1133 $redirect = true;
1134 $user_id = bbp_get_displayed_user_id();
1135
1136 // Allow user to edit their own profile
1137 if ( bbp_is_user_home_edit() ) {
1138 $redirect = false;
1139
1140 // Allow if current user can edit the displayed user
1141 } elseif ( current_user_can( 'edit_user', $user_id ) ) {
1142 $redirect = false;
1143
1144 // Allow if user can manage network users, or edit-any is enabled
1145 } elseif ( current_user_can( 'manage_network_users' ) || apply_filters( 'enable_edit_any_user_configuration', false ) ) {
1146 $redirect = false;
1147 }
1148
1149 // Allow conclusion to be overridden
1150 $redirect = (bool) apply_filters( 'bbp_check_user_edit', $redirect, $user_id );
1151
1152 // Bail if not redirecting
1153 if ( false === $redirect ) {
1154 return;
1155 }
1156
1157 // Filter redirect URL
1158 $profile_url = bbp_get_user_profile_url( $user_id );
1159 $redirect_to = apply_filters( 'bbp_check_user_edit_redirect_to', $profile_url, $user_id );
1160
1161 // Redirect
1162 bbp_redirect( $redirect_to );
1163 }
1164
1165 /**
1166 * Check if a user is blocked, or cannot spectate the forums.
1167 *
1168 * @since 2.2.0 bbPress (r4316)
1169 */
1170 function bbp_forum_enforce_blocked() {
1171
1172 // Bail if not logged in or keymaster
1173 if ( ! is_user_logged_in() || bbp_is_user_keymaster() ) {
1174 return;
1175 }
1176
1177 // Set 404 if in bbPress and user cannot spectate
1178 if ( is_bbpress() && ! current_user_can( 'spectate' ) ) {
1179 bbp_set_404();
1180 }
1181 }
1182
1183 /** Sanitization **************************************************************/
1184
1185 /**
1186 * Sanitize displayed user data, when viewing and editing any user.
1187 *
1188 * This somewhat monolithic function handles the escaping and sanitization of
1189 * user data for a bbPress profile. There are two reasons this all happens here:
1190 *
1191 * 1. bbPress took a similar approach to WordPress, and funnels all user profile
1192 * data through a central helper. This eventually calls sanitize_user_field()
1193 * which applies a few context based filters, which some third party plugins
1194 * might be relying on bbPress to play nicely with.
1195 *
1196 * 2. Early versions of bbPress 2.x templates did not escape this data meaning
1197 * a backwards compatible approach like this one was necessary to protect
1198 * existing installations that may have custom template parts.
1199 *
1200 * @since 2.6.0 bbPress (r5369)
1201 *
1202 * @param string $value
1203 * @param string $field
1204 * @param string $context
1205 * @return string
1206 */
1207 function bbp_sanitize_displayed_user_field( $value = '', $field = '', $context = 'display' ) {
1208
1209 // Bail if not editing or displaying (maybe we'll do more here later)
1210 if ( ! in_array( $context, array( 'edit', 'display' ), true ) ) {
1211 return $value;
1212 }
1213
1214 // By default, no filter set (consider making this an array later)
1215 $filter = false;
1216
1217 // Big switch statement to decide which user field we're sanitizing and how
1218 switch ( $field ) {
1219
1220 // Description is a paragraph
1221 case 'description' :
1222 $filter = ( 'edit' === $context ) ? '' : 'wp_kses_data';
1223 break;
1224
1225 // Email addresses are sanitized with a specific function
1226 case 'user_email' :
1227 $filter = 'sanitize_email';
1228 break;
1229
1230 // Name & login fields
1231 case 'user_login' :
1232 case 'display_name' :
1233 case 'first_name' :
1234 case 'last_name' :
1235 case 'nickname' :
1236 $filter = ( 'edit' === $context ) ? 'esc_attr' : 'esc_html';
1237 break;
1238
1239 // wp-includes/default-filters.php escapes this for us via esc_url()
1240 case 'user_url' :
1241 break;
1242 }
1243
1244 // Run any applicable filters on the value
1245 if ( ! empty( $filter ) ) {
1246 $value = call_user_func( $filter, $value );
1247 }
1248
1249 return $value;
1250 }
1251
1252 /** Converter *****************************************************************/
1253
1254 /**
1255 * Convert passwords from previous platform encryption to WordPress encryption.
1256 *
1257 * @since 2.1.0 bbPress (r4072)
1258 * @since 2.6.10 bbPress (r7245) Switched from direct query to get_user_by()
1259 * @since 2.6.18 bbPress (r7546) Improved input handling and email login support.
1260 */
1261 function bbp_user_maybe_convert_pass() {
1262
1263 // Sanitize login
1264 $login = isset( $_POST['log'] ) && is_string( $_POST['log'] )
1265 ? sanitize_user( wp_unslash( $_POST['log'] ) )
1266 : '';
1267
1268 // Unslash password without changing its literal value
1269 $wp_pass = isset( $_POST['pwd'] ) && is_string( $_POST['pwd'] )
1270 ? $_POST['pwd']
1271 : '';
1272 $pass = wp_unslash( $wp_pass );
1273
1274 // Match WordPress's default password limit before invoking a legacy converter.
1275 if ( strlen( $wp_pass ) > 4096 ) {
1276 return;
1277 }
1278
1279 // Bail if no username or password
1280 if ( '' === $login || '' === $pass ) {
1281 return;
1282 }
1283
1284 // Get user by login...
1285 $user = get_user_by( 'login', $login );
1286
1287 // ...or get user by email
1288 if ( empty( $user ) && strpos( $login, '@' ) ) {
1289 $user = get_user_by( 'email', $login );
1290 }
1291
1292 // Bail if no user
1293 if ( empty( $user ) ) {
1294 return;
1295 }
1296
1297 // Get converter class from usermeta
1298 $class = get_user_meta( $user->ID, '_bbp_class', true );
1299 $has_password_meta = metadata_exists( 'user', $user->ID, '_bbp_password' );
1300 $user_pass_class = false;
1301
1302 // Older imports may have password metadata without a converter class. Use
1303 // the saved import platform as a candidate; its password callback still
1304 // needs to verify the stored legacy hash before changing the account.
1305 if ( empty( $class ) && $has_password_meta ) {
1306 $class = get_option( '_bbp_converter_platform' );
1307 }
1308
1309 // Completed Drupal 7 and PHPWind imports stored source data directly in
1310 // user_pass and removed temporary password metadata. Their callbacks must
1311 // verify the legacy value before changing the account.
1312 if ( ! $has_password_meta ) {
1313 if ( empty( $class ) && in_array( get_option( '_bbp_converter_platform' ), array( 'Drupal7', 'PHPWind' ), true ) ) {
1314 $class = get_option( '_bbp_converter_platform' );
1315 }
1316
1317 $user_pass_class = in_array( $class, array( 'Drupal7', 'PHPWind' ), true );
1318 }
1319
1320 // Bail if no converter class
1321 if ( empty( $class ) || ! is_string( $class ) ) {
1322 return;
1323 }
1324
1325 // Setup the converter
1326 bbp_setup_converter();
1327
1328 // Try to instantiate the converter class
1329 $converter = bbp_new_converter( $class );
1330
1331 // Bail if no converter
1332 if ( empty( $converter ) ) {
1333 return;
1334 }
1335
1336 // Try to upgrade a password stored directly in the users table
1337 if ( $user_pass_class && ( $converter instanceof BBP_Converter_Base ) && method_exists( $converter, 'callback_user_pass' ) ) {
1338 $converter->callback_user_pass( $user, $pass, $wp_pass );
1339
1340 // Try to call the password conversion callback method
1341 } elseif ( ( $converter instanceof BBP_Converter_Base ) && method_exists( $converter, 'callback_pass' ) ) {
1342 $converter->callback_pass( $user->user_login, $pass, $wp_pass );
1343 }
1344 }
1345