[ Index ] |
PHP Cross Reference of BuddyPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * BuddyPress Common Functions. 4 * 5 * @package BuddyPress 6 * @subpackage Functions 7 * @since 1.5.0 8 */ 9 10 // Exit if accessed directly. 11 defined( 'ABSPATH' ) || exit; 12 13 /** Versions ******************************************************************/ 14 15 /** 16 * Output the BuddyPress version. 17 * 18 * @since 1.6.0 19 */ 20 function bp_version() { 21 echo bp_get_version(); 22 } 23 /** 24 * Return the BuddyPress version. 25 * 26 * @since 1.6.0 27 * 28 * @return string The BuddyPress version. 29 */ 30 function bp_get_version() { 31 return buddypress()->version; 32 } 33 34 /** 35 * Output the BuddyPress database version. 36 * 37 * @since 1.6.0 38 */ 39 function bp_db_version() { 40 echo bp_get_db_version(); 41 } 42 /** 43 * Return the BuddyPress database version. 44 * 45 * @since 1.6.0 46 * 47 * @return string The BuddyPress database version. 48 */ 49 function bp_get_db_version() { 50 return buddypress()->db_version; 51 } 52 53 /** 54 * Output the BuddyPress database version. 55 * 56 * @since 1.6.0 57 */ 58 function bp_db_version_raw() { 59 echo bp_get_db_version_raw(); 60 } 61 /** 62 * Return the BuddyPress database version. 63 * 64 * @since 1.6.0 65 * 66 * @return string The BuddyPress version direct from the database. 67 */ 68 function bp_get_db_version_raw() { 69 $bp = buddypress(); 70 return !empty( $bp->db_version_raw ) ? $bp->db_version_raw : 0; 71 } 72 73 /** 74 * Check whether the current version of WP exceeds a given version. 75 * 76 * @since 7.0.0 77 * 78 * @param string $version WP version, in "PHP-standardized" format. 79 * @param string $compare Optional. Comparison operator. Default '>='. 80 * @return bool 81 */ 82 function bp_is_running_wp( $version, $compare = '>=' ) { 83 return version_compare( $GLOBALS['wp_version'], $version, $compare ); 84 } 85 86 /** Functions *****************************************************************/ 87 88 /** 89 * Get the $wpdb base prefix, run through the 'bp_core_get_table_prefix' filter. 90 * 91 * The filter is intended primarily for use in multinetwork installations. 92 * 93 * @since 1.2.6 94 * 95 * @global object $wpdb WordPress database object. 96 * 97 * @return string Filtered database prefix. 98 */ 99 function bp_core_get_table_prefix() { 100 global $wpdb; 101 102 /** 103 * Filters the $wpdb base prefix. 104 * 105 * Intended primarily for use in multinetwork installations. 106 * 107 * @since 1.2.6 108 * 109 * @param string $base_prefix Base prefix to use. 110 */ 111 return apply_filters( 'bp_core_get_table_prefix', $wpdb->base_prefix ); 112 } 113 114 /** 115 * Sort an array of objects or arrays by a specific key/property. 116 * 117 * The main purpose for this function is so that you can avoid having to create 118 * your own awkward callback function for usort(). 119 * 120 * @since 2.2.0 121 * @since 2.7.0 Added $preserve_keys parameter. 122 * 123 * @param array $items The items to be sorted. Its constituent items 124 * can be either associative arrays or objects. 125 * @param string|int $key The array index or property name to sort by. 126 * @param string $type Sort type. 'alpha' for alphabetical, 'num' 127 * for numeric. Default: 'alpha'. 128 * @param bool $preserve_keys Whether to keep the keys or not. 129 * 130 * @return array $items The sorted array. 131 */ 132 function bp_sort_by_key( $items, $key, $type = 'alpha', $preserve_keys = false ) { 133 $callback = function( $a, $b ) use ( $key, $type ) { 134 $values = array( 0 => false, 1 => false ); 135 foreach ( func_get_args() as $indexi => $index ) { 136 if ( isset( $index->{$key} ) ) { 137 $values[ $indexi ] = $index->{$key}; 138 } elseif ( isset( $index[ $key ] ) ) { 139 $values[ $indexi ] = $index[ $key ]; 140 } 141 } 142 143 if ( isset( $values[0], $values[1] ) ) { 144 if ( 'num' === $type ) { 145 $cmp = $values[0] - $values[1]; 146 } else { 147 $cmp = strcmp( $values[0], $values[1] ); 148 } 149 150 if ( 0 > $cmp ) { 151 $retval = -1; 152 } elseif ( 0 < $cmp ) { 153 $retval = 1; 154 } else { 155 $retval = 0; 156 } 157 return $retval; 158 } else { 159 return 0; 160 } 161 }; 162 163 if ( true === $preserve_keys ) { 164 uasort( $items, $callback ); 165 } else { 166 usort( $items, $callback ); 167 } 168 169 return $items; 170 } 171 172 /** 173 * Sort an array of objects or arrays by alphabetically sorting by a specific key/property. 174 * 175 * For instance, if you have an array of WordPress post objects, you can sort 176 * them by post_name as follows: 177 * $sorted_posts = bp_alpha_sort_by_key( $posts, 'post_name' ); 178 * 179 * @since 1.9.0 180 * 181 * @param array $items The items to be sorted. Its constituent items can be either associative arrays or objects. 182 * @param string|int $key The array index or property name to sort by. 183 * @return array $items The sorted array. 184 */ 185 function bp_alpha_sort_by_key( $items, $key ) { 186 return bp_sort_by_key( $items, $key, 'alpha' ); 187 } 188 189 /** 190 * Format numbers the BuddyPress way. 191 * 192 * @since 1.2.0 193 * 194 * @param int $number The number to be formatted. 195 * @param bool $decimals Whether to use decimals. See {@link number_format_i18n()}. 196 * @return string The formatted number. 197 */ 198 function bp_core_number_format( $number = 0, $decimals = false ) { 199 200 // Force number to 0 if needed. 201 if ( ! is_numeric( $number ) ) { 202 $number = 0; 203 } 204 205 /** 206 * Filters the BuddyPress formatted number. 207 * 208 * @since 1.2.4 209 * 210 * @param string $value BuddyPress formatted value. 211 * @param int $number The number to be formatted. 212 * @param bool $decimals Whether or not to use decimals. 213 */ 214 return apply_filters( 'bp_core_number_format', number_format_i18n( $number, $decimals ), $number, $decimals ); 215 } 216 217 /** 218 * A utility for parsing individual function arguments into an array. 219 * 220 * The purpose of this function is to help with backward compatibility in cases where 221 * 222 * function foo( $bar = 1, $baz = false, $barry = array(), $blip = false ) { // ... 223 * 224 * is deprecated in favor of 225 * 226 * function foo( $args = array() ) { 227 * $defaults = array( 228 * 'bar' => 1, 229 * 'arg2' => false, 230 * 'arg3' => array(), 231 * 'arg4' => false, 232 * ); 233 * $r = wp_parse_args( $args, $defaults ); // ... 234 * 235 * The first argument, $old_args_keys, is an array that matches the parameter positions (keys) to 236 * the new $args keys (values): 237 * 238 * $old_args_keys = array( 239 * 0 => 'bar', // because $bar was the 0th parameter for foo() 240 * 1 => 'baz', // because $baz was the 1st parameter for foo() 241 * 2 => 'barry', // etc 242 * 3 => 'blip' 243 * ); 244 * 245 * For the second argument, $func_args, you should just pass the value of func_get_args(). 246 * 247 * @since 1.6.0 248 * 249 * @param array $old_args_keys Old argument indexes, keyed to their positions. 250 * @param array $func_args The parameters passed to the originating function. 251 * @return array $new_args The parsed arguments. 252 */ 253 function bp_core_parse_args_array( $old_args_keys, $func_args ) { 254 $new_args = array(); 255 256 foreach( $old_args_keys as $arg_num => $arg_key ) { 257 if ( isset( $func_args[$arg_num] ) ) { 258 $new_args[$arg_key] = $func_args[$arg_num]; 259 } 260 } 261 262 return $new_args; 263 } 264 265 /** 266 * Merge user defined arguments into defaults array. 267 * 268 * This function is used throughout BuddyPress to allow for either a string or 269 * array to be merged into another array. It is identical to wp_parse_args() 270 * except it allows for arguments to be passively or aggressively filtered using 271 * the optional $filter_key parameter. If no $filter_key is passed, no filters 272 * are applied. 273 * 274 * @since 2.0.0 275 * 276 * @param string|array $args Value to merge with $defaults. 277 * @param array $defaults Array that serves as the defaults. 278 * @param string $filter_key String to key the filters from. 279 * @return array Merged user defined values with defaults. 280 */ 281 function bp_parse_args( $args, $defaults = array(), $filter_key = '' ) { 282 283 // Setup a temporary array from $args. 284 if ( is_object( $args ) ) { 285 $r = get_object_vars( $args ); 286 } elseif ( is_array( $args ) ) { 287 $r =& $args; 288 } else { 289 wp_parse_str( $args, $r ); 290 } 291 292 // Passively filter the args before the parse. 293 if ( !empty( $filter_key ) ) { 294 295 /** 296 * Filters the arguments key before parsing if filter key provided. 297 * 298 * This is a dynamic filter dependent on the specified key. 299 * 300 * @since 2.0.0 301 * 302 * @param array $r Array of arguments to use. 303 */ 304 $r = apply_filters( 'bp_before_' . $filter_key . '_parse_args', $r ); 305 } 306 307 // Parse. 308 if ( is_array( $defaults ) && !empty( $defaults ) ) { 309 $r = array_merge( $defaults, $r ); 310 } 311 312 // Aggressively filter the args after the parse. 313 if ( !empty( $filter_key ) ) { 314 315 /** 316 * Filters the arguments key after parsing if filter key provided. 317 * 318 * This is a dynamic filter dependent on the specified key. 319 * 320 * @since 2.0.0 321 * 322 * @param array $r Array of parsed arguments. 323 */ 324 $r = apply_filters( 'bp_after_' . $filter_key . '_parse_args', $r ); 325 } 326 327 // Return the parsed results. 328 return $r; 329 } 330 331 /** 332 * Sanitizes a pagination argument based on both the request override and the 333 * original value submitted via a query argument, likely to a template class 334 * responsible for limiting the result set of a template loop. 335 * 336 * @since 2.2.0 337 * 338 * @param string $page_arg The $_REQUEST argument to look for. 339 * @param int $page The original page value to fall back to. 340 * @return int A sanitized integer value, good for pagination. 341 */ 342 function bp_sanitize_pagination_arg( $page_arg = '', $page = 1 ) { 343 344 // Check if request overrides exist. 345 if ( isset( $_REQUEST[ $page_arg ] ) ) { 346 347 // Get the absolute integer value of the override. 348 $int = absint( $_REQUEST[ $page_arg ] ); 349 350 // If override is 0, do not use it. This prevents unlimited result sets. 351 // @see https://buddypress.trac.wordpress.org/ticket/5796. 352 if ( $int ) { 353 $page = $int; 354 } 355 } 356 357 return intval( $page ); 358 } 359 360 /** 361 * Sanitize an 'order' parameter for use in building SQL queries. 362 * 363 * Strings like 'DESC', 'desc', ' desc' will be interpreted into 'DESC'. 364 * Everything else becomes 'ASC'. 365 * 366 * @since 1.8.0 367 * 368 * @param string $order The 'order' string, as passed to the SQL constructor. 369 * @return string The sanitized value 'DESC' or 'ASC'. 370 */ 371 function bp_esc_sql_order( $order = '' ) { 372 $order = strtoupper( trim( $order ) ); 373 return 'DESC' === $order ? 'DESC' : 'ASC'; 374 } 375 376 /** 377 * Escape special characters in a SQL LIKE clause. 378 * 379 * In WordPress 4.0, like_escape() was deprecated, due to incorrect 380 * documentation and improper sanitization leading to a history of misuse. To 381 * maintain compatibility with versions of WP before 4.0, we duplicate the 382 * logic of the replacement, wpdb::esc_like(). 383 * 384 * @since 2.1.0 385 * 386 * @see wpdb::esc_like() for more details on proper use. 387 * 388 * @param string $text The raw text to be escaped. 389 * @return string Text in the form of a LIKE phrase. Not SQL safe. Run through 390 * wpdb::prepare() before use. 391 */ 392 function bp_esc_like( $text ) { 393 global $wpdb; 394 395 if ( method_exists( $wpdb, 'esc_like' ) ) { 396 return $wpdb->esc_like( $text ); 397 } else { 398 return addcslashes( $text, '_%\\' ); 399 } 400 } 401 402 /** 403 * Are we running username compatibility mode? 404 * 405 * @since 1.5.0 406 * 407 * @todo Move to members component? 408 * 409 * @return bool False when compatibility mode is disabled, true when enabled. 410 * Default: false. 411 */ 412 function bp_is_username_compatibility_mode() { 413 414 /** 415 * Filters whether or not to use username compatibility mode. 416 * 417 * @since 1.5.0 418 * 419 * @param bool $value Whether or not username compatibility mode should be used. 420 */ 421 return apply_filters( 'bp_is_username_compatibility_mode', defined( 'BP_ENABLE_USERNAME_COMPATIBILITY_MODE' ) && BP_ENABLE_USERNAME_COMPATIBILITY_MODE ); 422 } 423 424 /** 425 * Should we use the WP Toolbar? 426 * 427 * The WP Toolbar, introduced in WP 3.1, is fully supported in BuddyPress as 428 * of BP 1.5. For BP 1.6, the WP Toolbar is the default. 429 * 430 * @since 1.5.0 431 * 432 * @return bool Default: true. False when WP Toolbar support is disabled. 433 */ 434 function bp_use_wp_admin_bar() { 435 436 // Default to true (to avoid loading deprecated BuddyBar code). 437 $use_admin_bar = true; 438 439 // Has the WP Toolbar constant been explicitly opted into? 440 if ( defined( 'BP_USE_WP_ADMIN_BAR' ) ) { 441 $use_admin_bar = (bool) BP_USE_WP_ADMIN_BAR; 442 443 // ...or is the old BuddyBar being forced back into use? 444 } elseif ( bp_force_buddybar( false ) ) { 445 $use_admin_bar = false; 446 } 447 448 /** 449 * Filters whether or not to use the admin bar. 450 * 451 * @since 1.5.0 452 * 453 * @param bool $use_admin_bar Whether or not to use the admin bar. 454 */ 455 return (bool) apply_filters( 'bp_use_wp_admin_bar', $use_admin_bar ); 456 } 457 458 459 /** 460 * Return the parent forum ID for the Legacy Forums abstraction layer. 461 * 462 * @since 1.5.0 463 * @since 3.0.0 Supported for compatibility with bbPress 2. 464 * 465 * @return int Forum ID. 466 */ 467 function bp_forums_parent_forum_id() { 468 469 /** 470 * Filters the parent forum ID for the bbPress abstraction layer. 471 * 472 * @since 1.5.0 473 * 474 * @param int BP_FORUMS_PARENT_FORUM_ID The Parent forum ID constant. 475 */ 476 return apply_filters( 'bp_forums_parent_forum_id', BP_FORUMS_PARENT_FORUM_ID ); 477 } 478 479 /** Directory *****************************************************************/ 480 481 /** 482 * Returns an array of core component IDs. 483 * 484 * @since 2.1.0 485 * 486 * @return array 487 */ 488 function bp_core_get_packaged_component_ids() { 489 $components = array( 490 'activity', 491 'members', 492 'groups', 493 'blogs', 494 'xprofile', 495 'friends', 496 'messages', 497 'settings', 498 'notifications', 499 ); 500 501 return $components; 502 } 503 504 /** 505 * Fetch a list of BP directory pages from the appropriate meta table. 506 * 507 * @since 1.5.0 508 * 509 * @param string $status 'active' to return only pages associated with active components, 'all' to return all saved 510 * pages. When running save routines, use 'all' to avoid removing data related to inactive 511 * components. Default: 'active'. 512 * @return array|string An array of page IDs, keyed by component names, or an 513 * empty string if the list is not found. 514 */ 515 function bp_core_get_directory_page_ids( $status = 'active' ) { 516 $page_ids = bp_get_option( 'bp-pages', array() ); 517 518 // Loop through pages. 519 foreach ( $page_ids as $component_name => $page_id ) { 520 521 // Ensure that empty indexes are unset. Should only matter in edge cases. 522 if ( empty( $component_name ) || empty( $page_id ) ) { 523 unset( $page_ids[ $component_name ] ); 524 } 525 526 // Trashed pages should never appear in results. 527 if ( 'trash' == get_post_status( $page_id ) ) { 528 unset( $page_ids[ $component_name ] ); 529 } 530 531 // 'register' and 'activate' do not have components, but are allowed as special cases. 532 if ( in_array( $component_name, array( 'register', 'activate' ), true ) ) { 533 continue; 534 } 535 536 // Remove inactive component pages. 537 if ( ( 'active' === $status ) && ! bp_is_active( $component_name ) ) { 538 unset( $page_ids[ $component_name ] ); 539 } 540 } 541 542 /** 543 * Filters the list of BP directory pages from the appropriate meta table. 544 * 545 * @since 1.5.0 546 * @since 2.9.0 Add $status parameter 547 * 548 * @param array $page_ids Array of directory pages. 549 * @param string $status Page status to limit results to 550 */ 551 return (array) apply_filters( 'bp_core_get_directory_page_ids', $page_ids, $status ); 552 } 553 554 /** 555 * Get the page ID corresponding to a component directory. 556 * 557 * @since 2.6.0 558 * 559 * @param string|null $component The slug representing the component. Defaults to the current component. 560 * @return int|false The ID of the directory page associated with the component. False if none is found. 561 */ 562 function bp_core_get_directory_page_id( $component = null ) { 563 if ( ! $component ) { 564 $component = bp_current_component(); 565 } 566 567 $bp_pages = bp_core_get_directory_page_ids( 'all' ); 568 569 $page_id = false; 570 if ( $component && isset( $bp_pages[ $component ] ) ) { 571 $page_id = (int) $bp_pages[ $component ]; 572 } 573 574 return $page_id; 575 } 576 577 /** 578 * Store the list of BP directory pages in the appropriate meta table. 579 * 580 * The bp-pages data is stored in site_options (falls back to options on non-MS), 581 * in an array keyed by blog_id. This allows you to change your 582 * bp_get_root_blog_id() and go through the setup process again. 583 * 584 * @since 1.5.0 585 * 586 * @param array $blog_page_ids The IDs of the WP pages corresponding to BP 587 * component directories. 588 */ 589 function bp_core_update_directory_page_ids( $blog_page_ids ) { 590 bp_update_option( 'bp-pages', $blog_page_ids ); 591 } 592 593 /** 594 * Get names and slugs for BuddyPress component directory pages. 595 * 596 * @since 1.5.0 597 * 598 * @return object Page names, IDs, and slugs. 599 */ 600 function bp_core_get_directory_pages() { 601 global $wpdb; 602 603 // Look in cache first. 604 $pages = wp_cache_get( 'directory_pages', 'bp_pages' ); 605 606 if ( false === $pages ) { 607 608 // Set pages as standard class. 609 $pages = new stdClass; 610 611 // Get pages and IDs. 612 $page_ids = bp_core_get_directory_page_ids(); 613 if ( !empty( $page_ids ) ) { 614 615 // Always get page data from the root blog, except on multiblog mode, when it comes 616 // from the current blog. 617 $posts_table_name = bp_is_multiblog_mode() ? $wpdb->posts : $wpdb->get_blog_prefix( bp_get_root_blog_id() ) . 'posts'; 618 $page_ids_sql = implode( ',', wp_parse_id_list( $page_ids ) ); 619 $page_names = $wpdb->get_results( "SELECT ID, post_name, post_parent, post_title FROM {$posts_table_name} WHERE ID IN ({$page_ids_sql}) AND post_status = 'publish' " ); 620 621 foreach ( (array) $page_ids as $component_id => $page_id ) { 622 foreach ( (array) $page_names as $page_name ) { 623 if ( $page_name->ID == $page_id ) { 624 if ( !isset( $pages->{$component_id} ) || !is_object( $pages->{$component_id} ) ) { 625 $pages->{$component_id} = new stdClass; 626 } 627 628 $pages->{$component_id}->name = $page_name->post_name; 629 $pages->{$component_id}->id = $page_name->ID; 630 $pages->{$component_id}->title = $page_name->post_title; 631 $slug[] = $page_name->post_name; 632 633 // Get the slug. 634 while ( $page_name->post_parent != 0 ) { 635 $parent = $wpdb->get_results( $wpdb->prepare( "SELECT post_name, post_parent FROM {$posts_table_name} WHERE ID = %d", $page_name->post_parent ) ); 636 $slug[] = $parent[0]->post_name; 637 $page_name->post_parent = $parent[0]->post_parent; 638 } 639 640 $pages->{$component_id}->slug = implode( '/', array_reverse( (array) $slug ) ); 641 } 642 643 unset( $slug ); 644 } 645 } 646 } 647 648 wp_cache_set( 'directory_pages', $pages, 'bp_pages' ); 649 } 650 651 /** 652 * Filters the names and slugs for BuddyPress component directory pages. 653 * 654 * @since 1.5.0 655 * 656 * @param object $pages Object holding page names and slugs. 657 */ 658 return apply_filters( 'bp_core_get_directory_pages', $pages ); 659 } 660 661 /** 662 * Creates necessary directory pages. 663 * 664 * Directory pages are those WordPress pages used by BP components to display 665 * content (eg, the 'groups' page created by BP). 666 * 667 * @since 1.7.0 668 * 669 * @param array $components Components to create pages for. 670 * @param string $existing 'delete' if you want to delete existing page mappings 671 * and replace with new ones. Otherwise existing page mappings 672 * are kept, and the gaps filled in with new pages. Default: 'keep'. 673 */ 674 function bp_core_add_page_mappings( $components, $existing = 'keep' ) { 675 676 // If no value is passed, there's nothing to do. 677 if ( empty( $components ) ) { 678 return; 679 } 680 681 // Make sure that the pages are created on the root blog no matter which 682 // dashboard the setup is being run on. 683 if ( ! bp_is_root_blog() ) { 684 switch_to_blog( bp_get_root_blog_id() ); 685 } 686 687 $pages = bp_core_get_directory_page_ids( 'all' ); 688 689 // Delete any existing pages. 690 if ( 'delete' === $existing ) { 691 foreach ( $pages as $page_id ) { 692 wp_delete_post( $page_id, true ); 693 } 694 695 $pages = array(); 696 } 697 698 $page_titles = bp_core_get_directory_page_default_titles(); 699 700 $pages_to_create = array(); 701 foreach ( array_keys( $components ) as $component_name ) { 702 if ( ! isset( $pages[ $component_name ] ) && isset( $page_titles[ $component_name ] ) ) { 703 $pages_to_create[ $component_name ] = $page_titles[ $component_name ]; 704 } 705 } 706 707 // Register and Activate are not components, but need pages when 708 // registration is enabled. 709 if ( bp_get_signup_allowed() ) { 710 foreach ( array( 'register', 'activate' ) as $slug ) { 711 if ( ! isset( $pages[ $slug ] ) ) { 712 $pages_to_create[ $slug ] = $page_titles[ $slug ]; 713 } 714 } 715 } 716 717 // No need for a Sites directory unless we're on multisite. 718 if ( ! is_multisite() && isset( $pages_to_create['blogs'] ) ) { 719 unset( $pages_to_create['blogs'] ); 720 } 721 722 // Members must always have a page, no matter what. 723 if ( ! isset( $pages['members'] ) && ! isset( $pages_to_create['members'] ) ) { 724 $pages_to_create['members'] = $page_titles['members']; 725 } 726 727 // Create the pages. 728 foreach ( $pages_to_create as $component_name => $page_name ) { 729 $exists = get_page_by_path( $component_name ); 730 731 // If page already exists, use it. 732 if ( ! empty( $exists ) ) { 733 $pages[ $component_name ] = $exists->ID; 734 } else { 735 $pages[ $component_name ] = wp_insert_post( array( 736 'comment_status' => 'closed', 737 'ping_status' => 'closed', 738 'post_status' => 'publish', 739 'post_title' => $page_name, 740 'post_type' => 'page', 741 ) ); 742 } 743 } 744 745 // Save the page mapping. 746 bp_update_option( 'bp-pages', $pages ); 747 748 // If we had to switch_to_blog, go back to the original site. 749 if ( ! bp_is_root_blog() ) { 750 restore_current_blog(); 751 } 752 } 753 754 /** 755 * Get the default page titles for BP directory pages. 756 * 757 * @since 2.7.0 758 * 759 * @return array 760 */ 761 function bp_core_get_directory_page_default_titles() { 762 $page_default_titles = array( 763 'activity' => _x( 'Activity', 'Page title for the Activity directory.', 'buddypress' ), 764 'groups' => _x( 'Groups', 'Page title for the Groups directory.', 'buddypress' ), 765 'blogs' => _x( 'Sites', 'Page title for the Sites directory.', 'buddypress' ), 766 'members' => _x( 'Members', 'Page title for the Members directory.', 'buddypress' ), 767 'activate' => _x( 'Activate', 'Page title for the user activation screen.', 'buddypress' ), 768 'register' => _x( 'Register', 'Page title for the user registration screen.', 'buddypress' ), 769 ); 770 771 /** 772 * Filters the default page titles array 773 * 774 * @since 2.7.0 775 * 776 * @param array $page_default_titles the array of default WP (post_title) titles. 777 */ 778 return apply_filters( 'bp_core_get_directory_page_default_titles', $page_default_titles ); 779 } 780 781 /** 782 * Remove the entry from bp_pages when the corresponding WP page is deleted. 783 * 784 * Bails early on multisite installations when not viewing the root site. 785 * 786 * @link https://buddypress.trac.wordpress.org/ticket/6226 787 * 788 * @since 2.2.0 789 * 790 * @param int $post_id Post ID. 791 */ 792 function bp_core_on_directory_page_delete( $post_id ) { 793 794 // Stop if we are not on the main BP root blog. 795 if ( ! bp_is_root_blog() ) { 796 return; 797 } 798 799 $page_ids = bp_core_get_directory_page_ids( 'all' ); 800 $component_name = array_search( $post_id, $page_ids ); 801 802 if ( ! empty( $component_name ) ) { 803 unset( $page_ids[ $component_name ] ); 804 } 805 806 bp_core_update_directory_page_ids( $page_ids ); 807 } 808 add_action( 'delete_post', 'bp_core_on_directory_page_delete' ); 809 810 /** 811 * Create a default component slug from a WP page root_slug. 812 * 813 * Since 1.5, BP components get their root_slug (the slug used immediately 814 * following the root domain) from the slug of a corresponding WP page. 815 * 816 * E.g. if your BP installation at example.com has its members page at 817 * example.com/community/people, $bp->members->root_slug will be 818 * 'community/people'. 819 * 820 * By default, this function creates a shorter version of the root_slug for 821 * use elsewhere in the URL, by returning the content after the final '/' 822 * in the root_slug ('people' in the example above). 823 * 824 * Filter on 'bp_core_component_slug_from_root_slug' to override this method 825 * in general, or define a specific component slug constant (e.g. 826 * BP_MEMBERS_SLUG) to override specific component slugs. 827 * 828 * @since 1.5.0 829 * 830 * @param string $root_slug The root slug, which comes from $bp->pages->[component]->slug. 831 * @return string The short slug for use in the middle of URLs. 832 */ 833 function bp_core_component_slug_from_root_slug( $root_slug ) { 834 $slug_chunks = explode( '/', $root_slug ); 835 $slug = array_pop( $slug_chunks ); 836 837 /** 838 * Filters the default component slug from a WP page root_slug. 839 * 840 * @since 1.5.0 841 * 842 * @param string $slug Short slug for use in the middle of URLs. 843 * @param string $root_slug The root slug which comes from $bp->pages-[component]->slug. 844 */ 845 return apply_filters( 'bp_core_component_slug_from_root_slug', $slug, $root_slug ); 846 } 847 848 /** 849 * Add support for a top-level ("root") component. 850 * 851 * This function originally (pre-1.5) let plugins add support for pages in the 852 * root of the install. These root level pages are now handled by actual 853 * WordPress pages and this function is now a convenience for compatibility 854 * with the new method. 855 * 856 * @since 1.0.0 857 * 858 * @param string $slug The slug of the component being added to the root list. 859 */ 860 function bp_core_add_root_component( $slug ) { 861 $bp = buddypress(); 862 863 if ( empty( $bp->pages ) ) { 864 $bp->pages = bp_core_get_directory_pages(); 865 } 866 867 $match = false; 868 869 // Check if the slug is registered in the $bp->pages global. 870 foreach ( (array) $bp->pages as $key => $page ) { 871 if ( $key == $slug || $page->slug == $slug ) { 872 $match = true; 873 } 874 } 875 876 // Maybe create the add_root array. 877 if ( empty( $bp->add_root ) ) { 878 $bp->add_root = array(); 879 } 880 881 // If there was no match, add a page for this root component. 882 if ( empty( $match ) ) { 883 $add_root_items = $bp->add_root; 884 $add_root_items[] = $slug; 885 $bp->add_root = $add_root_items; 886 } 887 888 // Make sure that this component is registered as requiring a top-level directory. 889 if ( isset( $bp->{$slug} ) ) { 890 $bp->loaded_components[$bp->{$slug}->slug] = $bp->{$slug}->id; 891 $bp->{$slug}->has_directory = true; 892 } 893 } 894 895 /** 896 * Create WordPress pages to be used as BP component directories. 897 * 898 * @since 1.5.0 899 */ 900 function bp_core_create_root_component_page() { 901 902 // Get BuddyPress. 903 $bp = buddypress(); 904 905 $new_page_ids = array(); 906 907 foreach ( (array) $bp->add_root as $slug ) { 908 $new_page_ids[ $slug ] = wp_insert_post( array( 909 'comment_status' => 'closed', 910 'ping_status' => 'closed', 911 'post_title' => ucwords( $slug ), 912 'post_status' => 'publish', 913 'post_type' => 'page' 914 ) ); 915 } 916 917 $page_ids = array_merge( $new_page_ids, bp_core_get_directory_page_ids( 'all' ) ); 918 bp_core_update_directory_page_ids( $page_ids ); 919 } 920 921 /** 922 * Get the 'search' query argument for a given component. 923 * 924 * @since 2.4.0 925 * @since 2.7.0 The `$component` parameter was made optional, with the current component 926 * as the fallback value. 927 * 928 * @param string|null $component Optional. Component name. Defaults to current component. 929 * @return string|bool Query argument on success. False on failure. 930 */ 931 function bp_core_get_component_search_query_arg( $component = null ) { 932 if ( ! $component ) { 933 $component = bp_current_component(); 934 } 935 936 $query_arg = false; 937 if ( isset( buddypress()->{$component}->search_query_arg ) ) { 938 $query_arg = sanitize_title( buddypress()->{$component}->search_query_arg ); 939 } 940 941 /** 942 * Filters the query arg for a component search string. 943 * 944 * @since 2.4.0 945 * 946 * @param string $query_arg Query argument. 947 * @param string $component Component name. 948 */ 949 return apply_filters( 'bp_core_get_component_search_query_arg', $query_arg, $component ); 950 } 951 952 /** 953 * Determine whether BuddyPress should register the bp-themes directory. 954 * 955 * @since 1.9.0 956 * 957 * @return bool True if bp-themes should be registered, false otherwise. 958 */ 959 function bp_do_register_theme_directory() { 960 // If bp-default exists in another theme directory, bail. 961 // This ensures that the version of bp-default in the regular themes 962 // directory will always take precedence, as part of a migration away 963 // from the version packaged with BuddyPress. 964 foreach ( array_values( (array) $GLOBALS['wp_theme_directories'] ) as $directory ) { 965 if ( is_dir( $directory . '/bp-default' ) ) { 966 return false; 967 } 968 } 969 970 // If the current theme is bp-default (or a bp-default child), BP 971 // should register its directory. 972 $register = 'bp-default' === get_stylesheet() || 'bp-default' === get_template(); 973 974 // Legacy sites continue to have the theme registered. 975 if ( empty( $register ) && ( 1 == get_site_option( '_bp_retain_bp_default' ) ) ) { 976 $register = true; 977 } 978 979 /** 980 * Filters whether BuddyPress should register the bp-themes directory. 981 * 982 * @since 1.9.0 983 * 984 * @param bool $register If bp-themes should be registered. 985 */ 986 return apply_filters( 'bp_do_register_theme_directory', $register ); 987 } 988 989 /** URI ***********************************************************************/ 990 991 /** 992 * Return the domain for the root blog. 993 * 994 * Eg: http://example.com OR https://example.com 995 * 996 * @since 1.0.0 997 * 998 * @return string The domain URL for the blog. 999 */ 1000 function bp_core_get_root_domain() { 1001 1002 $domain = get_home_url( bp_get_root_blog_id() ); 1003 1004 /** 1005 * Filters the domain for the root blog. 1006 * 1007 * @since 1.0.1 1008 * 1009 * @param string $domain The domain URL for the blog. 1010 */ 1011 return apply_filters( 'bp_core_get_root_domain', $domain ); 1012 } 1013 1014 /** 1015 * Perform a status-safe wp_redirect() that is compatible with BP's URI parser. 1016 * 1017 * @since 1.0.0 1018 * 1019 * @param string $location The redirect URL. 1020 * @param int $status Optional. The numeric code to give in the redirect 1021 * headers. Default: 302. 1022 */ 1023 function bp_core_redirect( $location = '', $status = 302 ) { 1024 1025 // On some setups, passing the value of wp_get_referer() may result in an 1026 // empty value for $location, which results in an error. Ensure that we 1027 // have a valid URL. 1028 if ( empty( $location ) ) { 1029 $location = bp_get_root_domain(); 1030 } 1031 1032 // Make sure we don't call status_header() in bp_core_do_catch_uri() as this 1033 // conflicts with wp_redirect() and wp_safe_redirect(). 1034 buddypress()->no_status_set = true; 1035 1036 wp_safe_redirect( $location, $status ); 1037 1038 // If PHPUnit is running, do not kill execution. 1039 if ( ! defined( 'BP_TESTS_DIR' ) ) { 1040 die; 1041 } 1042 } 1043 1044 /** 1045 * Return the URL path of the referring page. 1046 * 1047 * This is a wrapper for `wp_get_referer()` that sanitizes the referer URL to 1048 * a webroot-relative path. For example, 'http://example.com/foo/' will be 1049 * reduced to '/foo/'. 1050 * 1051 * @since 2.3.0 1052 * 1053 * @return bool|string Returns false on error, a URL path on success. 1054 */ 1055 function bp_get_referer_path() { 1056 $referer = wp_get_referer(); 1057 1058 if ( false === $referer ) { 1059 return false; 1060 } 1061 1062 // Turn into an absolute path. 1063 $referer = preg_replace( '|https?\://[^/]+/|', '/', $referer ); 1064 1065 return $referer; 1066 } 1067 1068 /** 1069 * Get the path of the current site. 1070 * 1071 * @since 1.0.0 1072 * 1073 * @global object $current_site 1074 * 1075 * @return string URL to the current site. 1076 */ 1077 function bp_core_get_site_path() { 1078 global $current_site; 1079 1080 if ( is_multisite() ) { 1081 $site_path = $current_site->path; 1082 } else { 1083 $site_path = (array) explode( '/', home_url() ); 1084 1085 if ( count( $site_path ) < 2 ) { 1086 $site_path = '/'; 1087 } else { 1088 // Unset the first three segments (http(s)://example.com part). 1089 unset( $site_path[0] ); 1090 unset( $site_path[1] ); 1091 unset( $site_path[2] ); 1092 1093 if ( !count( $site_path ) ) { 1094 $site_path = '/'; 1095 } else { 1096 $site_path = '/' . implode( '/', $site_path ) . '/'; 1097 } 1098 } 1099 } 1100 1101 /** 1102 * Filters the path of the current site. 1103 * 1104 * @since 1.2.0 1105 * 1106 * @param string $site_path URL to the current site. 1107 */ 1108 return apply_filters( 'bp_core_get_site_path', $site_path ); 1109 } 1110 1111 /** Time **********************************************************************/ 1112 1113 /** 1114 * Get the current GMT time to save into the DB. 1115 * 1116 * @since 1.2.6 1117 * 1118 * @param bool $gmt True to use GMT (rather than local) time. Default: true. 1119 * @param string $type See the 'type' parameter in {@link current_time()}. 1120 * Default: 'mysql'. 1121 * @return string Current time in 'Y-m-d h:i:s' format. 1122 */ 1123 function bp_core_current_time( $gmt = true, $type = 'mysql' ) { 1124 1125 /** 1126 * Filters the current GMT time to save into the DB. 1127 * 1128 * @since 1.2.6 1129 * 1130 * @param string $value Current GMT time. 1131 */ 1132 return apply_filters( 'bp_core_current_time', current_time( $type, $gmt ) ); 1133 } 1134 1135 /** 1136 * Get an English-language representation of the time elapsed since a given date. 1137 * 1138 * Based on function created by Dunstan Orchard - http://1976design.com 1139 * 1140 * This function will return an English representation of the time elapsed 1141 * since a given date. 1142 * eg: 2 hours and 50 minutes 1143 * eg: 4 days 1144 * eg: 4 weeks and 6 days 1145 * 1146 * Note that fractions of minutes are not represented in the return string. So 1147 * an interval of 3 minutes will be represented by "3 minutes ago", as will an 1148 * interval of 3 minutes 59 seconds. 1149 * 1150 * @since 1.0.0 1151 * 1152 * @param int|string $older_date The earlier time from which you're calculating 1153 * the time elapsed. Enter either as an integer Unix timestamp, 1154 * or as a date string of the format 'Y-m-d h:i:s'. 1155 * @param int|bool $newer_date Optional. Unix timestamp of date to compare older 1156 * date to. Default: false (current time). 1157 * @return string String representing the time since the older date, eg 1158 * "2 hours and 50 minutes". 1159 */ 1160 function bp_core_time_since( $older_date, $newer_date = false ) { 1161 1162 /** 1163 * Filters whether or not to bypass BuddyPress' time_since calculations. 1164 * 1165 * @since 1.7.0 1166 * 1167 * @param bool $value Whether or not to bypass. 1168 * @param string $older_date Earlier time from which we're calculating time elapsed. 1169 * @param string $newer_date Unix timestamp of date to compare older time to. 1170 */ 1171 $pre_value = apply_filters( 'bp_core_time_since_pre', false, $older_date, $newer_date ); 1172 if ( false !== $pre_value ) { 1173 return $pre_value; 1174 } 1175 1176 /** 1177 * Filters the value to use if the time since is unknown. 1178 * 1179 * @since 1.5.0 1180 * 1181 * @param string $value String representing the time since the older date. 1182 */ 1183 $unknown_text = apply_filters( 'bp_core_time_since_unknown_text', __( 'sometime', 'buddypress' ) ); 1184 1185 /** 1186 * Filters the value to use if the time since is right now. 1187 * 1188 * @since 1.5.0 1189 * 1190 * @param string $value String representing the time since the older date. 1191 */ 1192 $right_now_text = apply_filters( 'bp_core_time_since_right_now_text', __( 'right now', 'buddypress' ) ); 1193 1194 /** 1195 * Filters the value to use if the time since is some time ago. 1196 * 1197 * @since 1.5.0 1198 * 1199 * @param string $value String representing the time since the older date. 1200 */ 1201 $ago_text = apply_filters( 1202 'bp_core_time_since_ago_text', 1203 /* translators: %s: the human time diff. */ 1204 __( '%s ago', 'buddypress' ) 1205 ); 1206 1207 // Array of time period chunks. 1208 $chunks = array( 1209 YEAR_IN_SECONDS, 1210 30 * DAY_IN_SECONDS, 1211 WEEK_IN_SECONDS, 1212 DAY_IN_SECONDS, 1213 HOUR_IN_SECONDS, 1214 MINUTE_IN_SECONDS, 1215 1 1216 ); 1217 1218 if ( !empty( $older_date ) && !is_numeric( $older_date ) ) { 1219 $time_chunks = explode( ':', str_replace( ' ', ':', $older_date ) ); 1220 $date_chunks = explode( '-', str_replace( ' ', '-', $older_date ) ); 1221 $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] ); 1222 } 1223 1224 /** 1225 * $newer_date will equal false if we want to know the time elapsed between 1226 * a date and the current time. $newer_date will have a value if we want to 1227 * work out time elapsed between two known dates. 1228 */ 1229 $newer_date = ( !$newer_date ) ? bp_core_current_time( true, 'timestamp' ) : $newer_date; 1230 1231 // Difference in seconds. 1232 $since = $newer_date - $older_date; 1233 1234 // Something went wrong with date calculation and we ended up with a negative date. 1235 if ( 0 > $since ) { 1236 $output = $unknown_text; 1237 1238 /** 1239 * We only want to output two chunks of time here, eg: 1240 * x years, xx months 1241 * x days, xx hours 1242 * so there's only two bits of calculation below: 1243 */ 1244 } else { 1245 1246 // Step one: the first chunk. 1247 for ( $i = 0, $j = count( $chunks ); $i < $j; ++$i ) { 1248 $seconds = $chunks[$i]; 1249 1250 // Finding the biggest chunk (if the chunk fits, break). 1251 $count = floor( $since / $seconds ); 1252 if ( 0 != $count ) { 1253 break; 1254 } 1255 } 1256 1257 // If $i iterates all the way to $j, then the event happened 0 seconds ago. 1258 if ( !isset( $chunks[$i] ) ) { 1259 $output = $right_now_text; 1260 1261 } else { 1262 1263 // Set output var. 1264 switch ( $seconds ) { 1265 case YEAR_IN_SECONDS : 1266 /* translators: %s: the number of years. */ 1267 $output = sprintf( _n( '%s year', '%s years', $count, 'buddypress' ), $count ); 1268 break; 1269 case 30 * DAY_IN_SECONDS : 1270 /* translators: %s: the number of months. */ 1271 $output = sprintf( _n( '%s month', '%s months', $count, 'buddypress' ), $count ); 1272 break; 1273 case WEEK_IN_SECONDS : 1274 /* translators: %s: the number of weeks. */ 1275 $output = sprintf( _n( '%s week', '%s weeks', $count, 'buddypress' ), $count ); 1276 break; 1277 case DAY_IN_SECONDS : 1278 /* translators: %s: the number of days. */ 1279 $output = sprintf( _n( '%s day', '%s days', $count, 'buddypress' ), $count ); 1280 break; 1281 case HOUR_IN_SECONDS : 1282 /* translators: %s: the number of hours. */ 1283 $output = sprintf( _n( '%s hour', '%s hours', $count, 'buddypress' ), $count ); 1284 break; 1285 case MINUTE_IN_SECONDS : 1286 /* translators: %s: the number of minutes. */ 1287 $output = sprintf( _n( '%s minute', '%s minutes', $count, 'buddypress' ), $count ); 1288 break; 1289 default: 1290 /* translators: %s: the number of seconds. */ 1291 $output = sprintf( _n( '%s second', '%s seconds', $count, 'buddypress' ), $count ); 1292 } 1293 1294 // Step two: the second chunk 1295 // A quirk in the implementation means that this 1296 // condition fails in the case of minutes and seconds. 1297 // We've left the quirk in place, since fractions of a 1298 // minute are not a useful piece of information for our 1299 // purposes. 1300 if ( $i + 2 < $j ) { 1301 $seconds2 = $chunks[$i + 1]; 1302 $count2 = floor( ( $since - ( $seconds * $count ) ) / $seconds2 ); 1303 1304 // Add to output var. 1305 if ( 0 != $count2 ) { 1306 $output .= _x( ',', 'Separator in time since', 'buddypress' ) . ' '; 1307 1308 switch ( $seconds2 ) { 1309 case 30 * DAY_IN_SECONDS : 1310 /* translators: %s: the number of months. */ 1311 $output .= sprintf( _n( '%s month', '%s months', $count2, 'buddypress' ), $count2 ); 1312 break; 1313 case WEEK_IN_SECONDS : 1314 /* translators: %s: the number of weeks. */ 1315 $output .= sprintf( _n( '%s week', '%s weeks', $count2, 'buddypress' ), $count2 ); 1316 break; 1317 case DAY_IN_SECONDS : 1318 /* translators: %s: the number of days. */ 1319 $output .= sprintf( _n( '%s day', '%s days', $count2, 'buddypress' ), $count2 ); 1320 break; 1321 case HOUR_IN_SECONDS : 1322 /* translators: %s: the number of hours. */ 1323 $output .= sprintf( _n( '%s hour', '%s hours', $count2, 'buddypress' ), $count2 ); 1324 break; 1325 case MINUTE_IN_SECONDS : 1326 /* translators: %s: the number of minutes. */ 1327 $output .= sprintf( _n( '%s minute', '%s minutes', $count2, 'buddypress' ), $count2 ); 1328 break; 1329 default: 1330 /* translators: %s: the number of seconds. */ 1331 $output .= sprintf( _n( '%s second', '%s seconds', $count2, 'buddypress' ), $count2 ); 1332 } 1333 } 1334 } 1335 1336 // No output, so happened right now. 1337 if ( ! (int) trim( $output ) ) { 1338 $output = $right_now_text; 1339 } 1340 } 1341 } 1342 1343 // Append 'ago' to the end of time-since if not 'right now'. 1344 if ( $output != $right_now_text ) { 1345 $output = sprintf( $ago_text, $output ); 1346 } 1347 1348 /** 1349 * Filters the English-language representation of the time elapsed since a given date. 1350 * 1351 * @since 1.7.0 1352 * 1353 * @param string $output Final 'time since' string. 1354 * @param string $older_date Earlier time from which we're calculating time elapsed. 1355 * @param string $newer_date Unix timestamp of date to compare older time to. 1356 */ 1357 return apply_filters( 'bp_core_time_since', $output, $older_date, $newer_date ); 1358 } 1359 1360 /** 1361 * Output an ISO-8601 date from a date string. 1362 * 1363 * @since 2.7.0 1364 * 1365 * @param string String of date to convert. Timezone should be UTC before using this. 1366 * @return string|null 1367 */ 1368 function bp_core_iso8601_date( $timestamp = '' ) { 1369 echo bp_core_get_iso8601_date( $timestamp ); 1370 } 1371 /** 1372 * Return an ISO-8601 date from a date string. 1373 * 1374 * @since 2.7.0 1375 * 1376 * @param string String of date to convert. Timezone should be UTC before using this. 1377 * @return string 1378 */ 1379 function bp_core_get_iso8601_date( $timestamp = '' ) { 1380 if ( ! $timestamp ) { 1381 return ''; 1382 } 1383 1384 try { 1385 $date = new DateTime( $timestamp, new DateTimeZone( 'UTC' ) ); 1386 1387 // Not a valid date, so return blank string. 1388 } catch( Exception $e ) { 1389 return ''; 1390 } 1391 1392 return $date->format( DateTime::ISO8601 ); 1393 } 1394 1395 /** Messages ******************************************************************/ 1396 1397 /** 1398 * Add a feedback (error/success) message to the WP cookie so it can be displayed after the page reloads. 1399 * 1400 * @since 1.0.0 1401 * 1402 * @param string $message Feedback message to be displayed. 1403 * @param string $type Message type. 'updated', 'success', 'error', 'warning'. 1404 * Default: 'success'. 1405 */ 1406 function bp_core_add_message( $message, $type = '' ) { 1407 1408 // Success is the default. 1409 if ( empty( $type ) ) { 1410 $type = 'success'; 1411 } 1412 1413 // Send the values to the cookie for page reload display. 1414 @setcookie( 'bp-message', $message, time() + 60 * 60 * 24, COOKIEPATH, COOKIE_DOMAIN, is_ssl() ); 1415 @setcookie( 'bp-message-type', $type, time() + 60 * 60 * 24, COOKIEPATH, COOKIE_DOMAIN, is_ssl() ); 1416 1417 // Get BuddyPress. 1418 $bp = buddypress(); 1419 1420 /** 1421 * Send the values to the $bp global so we can still output messages 1422 * without a page reload 1423 */ 1424 $bp->template_message = $message; 1425 $bp->template_message_type = $type; 1426 } 1427 1428 /** 1429 * Set up the display of the 'template_notices' feedback message. 1430 * 1431 * Checks whether there is a feedback message in the WP cookie and, if so, adds 1432 * a "template_notices" action so that the message can be parsed into the 1433 * template and displayed to the user. 1434 * 1435 * After the message is displayed, it removes the message vars from the cookie 1436 * so that the message is not shown to the user multiple times. 1437 * 1438 * @since 1.1.0 1439 */ 1440 function bp_core_setup_message() { 1441 1442 // Get BuddyPress. 1443 $bp = buddypress(); 1444 1445 if ( empty( $bp->template_message ) && isset( $_COOKIE['bp-message'] ) ) { 1446 $bp->template_message = stripslashes( $_COOKIE['bp-message'] ); 1447 } 1448 1449 if ( empty( $bp->template_message_type ) && isset( $_COOKIE['bp-message-type'] ) ) { 1450 $bp->template_message_type = stripslashes( $_COOKIE['bp-message-type'] ); 1451 } 1452 1453 add_action( 'template_notices', 'bp_core_render_message' ); 1454 1455 if ( isset( $_COOKIE['bp-message'] ) ) { 1456 @setcookie( 'bp-message', false, time() - 1000, COOKIEPATH, COOKIE_DOMAIN, is_ssl() ); 1457 } 1458 1459 if ( isset( $_COOKIE['bp-message-type'] ) ) { 1460 @setcookie( 'bp-message-type', false, time() - 1000, COOKIEPATH, COOKIE_DOMAIN, is_ssl() ); 1461 } 1462 } 1463 add_action( 'bp_actions', 'bp_core_setup_message', 5 ); 1464 1465 /** 1466 * Render the 'template_notices' feedback message. 1467 * 1468 * The hook action 'template_notices' is used to call this function, it is not 1469 * called directly. 1470 * 1471 * @since 1.1.0 1472 */ 1473 function bp_core_render_message() { 1474 1475 // Get BuddyPress. 1476 $bp = buddypress(); 1477 1478 if ( !empty( $bp->template_message ) ) : 1479 $type = ( 'success' === $bp->template_message_type ) ? 'updated' : 'error'; 1480 1481 /** 1482 * Filters the 'template_notices' feedback message content. 1483 * 1484 * @since 1.5.5 1485 * 1486 * @param string $template_message Feedback message content. 1487 * @param string $type The type of message being displayed. 1488 * Either 'updated' or 'error'. 1489 */ 1490 $content = apply_filters( 'bp_core_render_message_content', $bp->template_message, $type ); ?> 1491 1492 <div id="message" class="bp-template-notice <?php echo esc_attr( $type ); ?>"> 1493 1494 <?php echo $content; ?> 1495 1496 </div> 1497 1498 <?php 1499 1500 /** 1501 * Fires after the display of any template_notices feedback messages. 1502 * 1503 * @since 1.1.0 1504 */ 1505 do_action( 'bp_core_render_message' ); 1506 1507 endif; 1508 } 1509 1510 /** Last active ***************************************************************/ 1511 1512 /** 1513 * Listener function for the logged-in user's 'last_activity' metadata. 1514 * 1515 * Many functions use a "last active" feature to show the length of time since 1516 * the user was last active. This function will update that time as a usermeta 1517 * setting for the user every 5 minutes while the user is actively browsing the 1518 * site. 1519 * 1520 * @since 1.0.0 1521 * 1522 * @return false|null Returns false if there is nothing to do. 1523 */ 1524 function bp_core_record_activity() { 1525 1526 // Bail if user is not logged in. 1527 if ( ! is_user_logged_in() ) { 1528 return false; 1529 } 1530 1531 // Get the user ID. 1532 $user_id = bp_loggedin_user_id(); 1533 1534 // Bail if user is not active. 1535 if ( bp_is_user_inactive( $user_id ) ) { 1536 return false; 1537 } 1538 1539 // Get the user's last activity. 1540 $activity = bp_get_user_last_activity( $user_id ); 1541 1542 // Make sure it's numeric. 1543 if ( ! is_numeric( $activity ) ) { 1544 $activity = strtotime( $activity ); 1545 } 1546 1547 // Get current time. 1548 $current_time = bp_core_current_time( true, 'timestamp' ); 1549 1550 // Use this action to detect the very first activity for a given member. 1551 if ( empty( $activity ) ) { 1552 1553 /** 1554 * Fires inside the recording of an activity item. 1555 * 1556 * Use this action to detect the very first activity for a given member. 1557 * 1558 * @since 1.6.0 1559 * 1560 * @param int $user_id ID of the user whose activity is recorded. 1561 */ 1562 do_action( 'bp_first_activity_for_member', $user_id ); 1563 } 1564 1565 // If it's been more than 5 minutes, record a newer last-activity time. 1566 if ( empty( $activity ) || ( $current_time >= strtotime( '+5 minutes', $activity ) ) ) { 1567 bp_update_user_last_activity( $user_id, date( 'Y-m-d H:i:s', $current_time ) ); 1568 } 1569 } 1570 add_action( 'wp_head', 'bp_core_record_activity' ); 1571 1572 /** 1573 * Format last activity string based on time since date given. 1574 * 1575 * @since 1.0.0 1576 * 1577 * @param int|string $last_activity_date The date of last activity. 1578 * @param string $string A sprintf()-able statement of the form 'Active %s'. 1579 * @return string $last_active A string of the form '3 years ago'. 1580 */ 1581 function bp_core_get_last_activity( $last_activity_date = '', $string = '' ) { 1582 1583 // Setup a default string if none was passed. 1584 $string = empty( $string ) 1585 ? '%s' // Gettext library's placeholder. 1586 : $string; 1587 1588 // Use the string if a last activity date was passed. 1589 $last_active = empty( $last_activity_date ) 1590 ? __( 'Not recently active', 'buddypress' ) 1591 : sprintf( $string, bp_core_time_since( $last_activity_date ) ); 1592 1593 /** 1594 * Filters last activity string based on time since date given. 1595 * 1596 * @since 1.2.0 1597 * 1598 * @param string $last_active Last activity string based on time since date given. 1599 * @param string $last_activity_date The date of last activity. 1600 * @param string $string A sprintf()-able statement of the form 'Active %s'. 1601 */ 1602 return apply_filters( 'bp_core_get_last_activity', $last_active, $last_activity_date, $string ); 1603 } 1604 1605 /** Meta **********************************************************************/ 1606 1607 /** 1608 * Get the meta_key for a given piece of user metadata 1609 * 1610 * BuddyPress stores a number of pieces of user data in the WordPress central 1611 * usermeta table. In order to allow plugins to enable multiple instances of 1612 * BuddyPress on a single WP installation, BP's usermeta keys are filtered 1613 * through this function, so that they can be altered on the fly. 1614 * 1615 * Plugin authors should use BP's _user_meta() functions, which bakes in 1616 * bp_get_user_meta_key(): 1617 * $friend_count = bp_get_user_meta( $user_id, 'total_friend_count', true ); 1618 * If you must use WP's _user_meta() functions directly for some reason, you 1619 * should use this function to determine the $key parameter, eg 1620 * $friend_count = get_user_meta( $user_id, bp_get_user_meta_key( 'total_friend_count' ), true ); 1621 * If using the WP functions, do not not hardcode your meta keys. 1622 * 1623 * @since 1.5.0 1624 * 1625 * @param string|bool $key The usermeta meta_key. 1626 * @return string $key The usermeta meta_key. 1627 */ 1628 function bp_get_user_meta_key( $key = false ) { 1629 1630 /** 1631 * Filters the meta_key for a given piece of user metadata. 1632 * 1633 * @since 1.5.0 1634 * 1635 * @param string $key The usermeta meta key. 1636 */ 1637 return apply_filters( 'bp_get_user_meta_key', $key ); 1638 } 1639 1640 /** 1641 * Get a piece of usermeta. 1642 * 1643 * This is a wrapper for get_user_meta() that allows for easy use of 1644 * bp_get_user_meta_key(), thereby increasing compatibility with non-standard 1645 * BP setups. 1646 * 1647 * @since 1.5.0 1648 * 1649 * @see get_user_meta() For complete details about parameters and return values. 1650 * 1651 * @param int $user_id The ID of the user whose meta you're fetching. 1652 * @param string $key The meta key to retrieve. 1653 * @param bool $single Whether to return a single value. 1654 * @return mixed Will be an array if $single is false. Will be value of meta data field if $single 1655 * is true. 1656 */ 1657 function bp_get_user_meta( $user_id, $key, $single = false ) { 1658 return get_user_meta( $user_id, bp_get_user_meta_key( $key ), $single ); 1659 } 1660 1661 /** 1662 * Update a piece of usermeta. 1663 * 1664 * This is a wrapper for update_user_meta() that allows for easy use of 1665 * bp_get_user_meta_key(), thereby increasing compatibility with non-standard 1666 * BP setups. 1667 * 1668 * @since 1.5.0 1669 * 1670 * @see update_user_meta() For complete details about parameters and return values. 1671 * 1672 * @param int $user_id The ID of the user whose meta you're setting. 1673 * @param string $key The meta key to set. 1674 * @param mixed $value Metadata value. 1675 * @param mixed $prev_value Optional. Previous value to check before removing. 1676 * @return bool False on failure, true on success. 1677 */ 1678 function bp_update_user_meta( $user_id, $key, $value, $prev_value = '' ) { 1679 return update_user_meta( $user_id, bp_get_user_meta_key( $key ), $value, $prev_value ); 1680 } 1681 1682 /** 1683 * Delete a piece of usermeta. 1684 * 1685 * This is a wrapper for delete_user_meta() that allows for easy use of 1686 * bp_get_user_meta_key(), thereby increasing compatibility with non-standard 1687 * BP setups. 1688 * 1689 * @since 1.5.0 1690 * 1691 * @see delete_user_meta() For complete details about parameters and return values. 1692 * 1693 * @param int $user_id The ID of the user whose meta you're deleting. 1694 * @param string $key The meta key to delete. 1695 * @param mixed $value Optional. Metadata value. 1696 * @return bool False for failure. True for success. 1697 */ 1698 function bp_delete_user_meta( $user_id, $key, $value = '' ) { 1699 return delete_user_meta( $user_id, bp_get_user_meta_key( $key ), $value ); 1700 } 1701 1702 /** Embeds ********************************************************************/ 1703 1704 /** 1705 * Initializes {@link BP_Embed} after everything is loaded. 1706 * 1707 * @since 1.5.0 1708 */ 1709 function bp_embed_init() { 1710 1711 // Get BuddyPress. 1712 $bp = buddypress(); 1713 1714 if ( empty( $bp->embed ) ) { 1715 $bp->embed = new BP_Embed(); 1716 } 1717 } 1718 add_action( 'bp_init', 'bp_embed_init', 9 ); 1719 1720 /** 1721 * Are oembeds allowed in activity items? 1722 * 1723 * @since 1.5.0 1724 * 1725 * @return bool False when activity embed support is disabled; true when 1726 * enabled. Default: true. 1727 */ 1728 function bp_use_embed_in_activity() { 1729 1730 /** 1731 * Filters whether or not oEmbeds are allowed in activity items. 1732 * 1733 * @since 1.5.0 1734 * 1735 * @param bool $value Whether or not oEmbeds are allowed. 1736 */ 1737 return apply_filters( 'bp_use_oembed_in_activity', !defined( 'BP_EMBED_DISABLE_ACTIVITY' ) || !BP_EMBED_DISABLE_ACTIVITY ); 1738 } 1739 1740 /** 1741 * Are oembeds allowed in activity replies? 1742 * 1743 * @since 1.5.0 1744 * 1745 * @return bool False when activity replies embed support is disabled; true 1746 * when enabled. Default: true. 1747 */ 1748 function bp_use_embed_in_activity_replies() { 1749 1750 /** 1751 * Filters whether or not oEmbeds are allowed in activity replies. 1752 * 1753 * @since 1.5.0 1754 * 1755 * @param bool $value Whether or not oEmbeds are allowed. 1756 */ 1757 return apply_filters( 'bp_use_embed_in_activity_replies', !defined( 'BP_EMBED_DISABLE_ACTIVITY_REPLIES' ) || !BP_EMBED_DISABLE_ACTIVITY_REPLIES ); 1758 } 1759 1760 /** 1761 * Are oembeds allowed in private messages? 1762 * 1763 * @since 1.5.0 1764 * 1765 * @return bool False when private message embed support is disabled; true when 1766 * enabled. Default: true. 1767 */ 1768 function bp_use_embed_in_private_messages() { 1769 1770 /** 1771 * Filters whether or not oEmbeds are allowed in private messages. 1772 * 1773 * @since 1.5.0 1774 * 1775 * @param bool $value Whether or not oEmbeds are allowed. 1776 */ 1777 return apply_filters( 'bp_use_embed_in_private_messages', !defined( 'BP_EMBED_DISABLE_PRIVATE_MESSAGES' ) || !BP_EMBED_DISABLE_PRIVATE_MESSAGES ); 1778 } 1779 1780 /** 1781 * Extracts media metadata from a given content. 1782 * 1783 * @since 2.6.0 1784 * 1785 * @param string $content The content to check. 1786 * @param string|int $type The type to check. Can also use a bitmask. See the class constants in the 1787 * BP_Media_Extractor class for more info. 1788 * @return false|array If media exists, will return array of media metadata. Else, boolean false. 1789 */ 1790 function bp_core_extract_media_from_content( $content = '', $type = 'all' ) { 1791 if ( is_string( $type ) ) { 1792 $class = new ReflectionClass( 'BP_Media_Extractor' ); 1793 $bitmask = $class->getConstant( strtoupper( $type ) ); 1794 } else { 1795 $bitmask = (int) $type; 1796 } 1797 1798 // Type isn't valid, so bail. 1799 if ( empty( $bitmask ) ) { 1800 return false; 1801 } 1802 1803 $x = new BP_Media_Extractor; 1804 $media = $x->extract( $content, $bitmask ); 1805 1806 unset( $media['has'] ); 1807 $retval = array_filter( $media ); 1808 1809 return ! empty( $retval ) ? $retval : false; 1810 } 1811 1812 /** Admin *********************************************************************/ 1813 1814 /** 1815 * Output the correct admin URL based on BuddyPress and WordPress configuration. 1816 * 1817 * @since 1.5.0 1818 * 1819 * @see bp_get_admin_url() For description of parameters. 1820 * 1821 * @param string $path See {@link bp_get_admin_url()}. 1822 * @param string $scheme See {@link bp_get_admin_url()}. 1823 */ 1824 function bp_admin_url( $path = '', $scheme = 'admin' ) { 1825 echo esc_url( bp_get_admin_url( $path, $scheme ) ); 1826 } 1827 /** 1828 * Return the correct admin URL based on BuddyPress and WordPress configuration. 1829 * 1830 * @since 1.5.0 1831 * 1832 * 1833 * @param string $path Optional. The sub-path under /wp-admin to be 1834 * appended to the admin URL. 1835 * @param string $scheme The scheme to use. Default is 'admin', which 1836 * obeys {@link force_ssl_admin()} and {@link is_ssl()}. 'http' 1837 * or 'https' can be passed to force those schemes. 1838 * @return string Admin url link with optional path appended. 1839 */ 1840 function bp_get_admin_url( $path = '', $scheme = 'admin' ) { 1841 1842 // Links belong in network admin. 1843 if ( bp_core_do_network_admin() ) { 1844 $url = network_admin_url( $path, $scheme ); 1845 1846 // Links belong in site admin. 1847 } else { 1848 $url = admin_url( $path, $scheme ); 1849 } 1850 1851 return $url; 1852 } 1853 1854 /** 1855 * Should BuddyPress appear in network admin (vs a single site Dashboard)? 1856 * 1857 * Because BuddyPress can be installed in multiple ways and with multiple 1858 * configurations, we need to check a few things to be confident about where 1859 * to hook into certain areas of WordPress's admin. 1860 * 1861 * @since 1.5.0 1862 * 1863 * @return bool True if the BP admin screen should appear in the Network Admin, 1864 * otherwise false. 1865 */ 1866 function bp_core_do_network_admin() { 1867 1868 // Default. 1869 $retval = bp_is_network_activated(); 1870 1871 if ( bp_is_multiblog_mode() ) { 1872 $retval = false; 1873 } 1874 1875 /** 1876 * Filters whether or not BuddyPress should appear in network admin. 1877 * 1878 * @since 1.5.0 1879 * 1880 * @param bool $retval Whether or not BuddyPress should be in the network admin. 1881 */ 1882 return (bool) apply_filters( 'bp_core_do_network_admin', $retval ); 1883 } 1884 1885 /** 1886 * Return the action name that BuddyPress nav setup callbacks should be hooked to. 1887 * 1888 * Functions used to set up BP Dashboard pages (wrapping such admin-panel 1889 * functions as add_submenu_page()) should use bp_core_admin_hook() for the 1890 * first parameter in add_action(). BuddyPress will then determine 1891 * automatically whether to load the panels in the Network Admin. Ie: 1892 * 1893 * add_action( bp_core_admin_hook(), 'myplugin_dashboard_panel_setup' ); 1894 * 1895 * @since 1.5.0 1896 * 1897 * @return string $hook The proper hook ('network_admin_menu' or 'admin_menu'). 1898 */ 1899 function bp_core_admin_hook() { 1900 $hook = bp_core_do_network_admin() ? 'network_admin_menu' : 'admin_menu'; 1901 1902 /** 1903 * Filters the action name that BuddyPress nav setup callbacks should be hooked to. 1904 * 1905 * @since 1.5.0 1906 * 1907 * @param string $hook Action name to be attached to. 1908 */ 1909 return apply_filters( 'bp_core_admin_hook', $hook ); 1910 } 1911 1912 /** Multisite *****************************************************************/ 1913 1914 /** 1915 * Is this the root blog? 1916 * 1917 * @since 1.5.0 1918 * 1919 * @param int $blog_id Optional. Default: the ID of the current blog. 1920 * @return bool $is_root_blog Returns true if this is bp_get_root_blog_id(). 1921 */ 1922 function bp_is_root_blog( $blog_id = 0 ) { 1923 1924 // Assume false. 1925 $is_root_blog = false; 1926 1927 // Use current blog if no ID is passed. 1928 if ( empty( $blog_id ) || ! is_int( $blog_id ) ) { 1929 $blog_id = get_current_blog_id(); 1930 } 1931 1932 // Compare to root blog ID. 1933 if ( bp_get_root_blog_id() === $blog_id ) { 1934 $is_root_blog = true; 1935 } 1936 1937 /** 1938 * Filters whether or not we're on the root blog. 1939 * 1940 * @since 1.5.0 1941 * 1942 * @param bool $is_root_blog Whether or not we're on the root blog. 1943 */ 1944 return (bool) apply_filters( 'bp_is_root_blog', (bool) $is_root_blog ); 1945 } 1946 1947 /** 1948 * Get the ID of the root blog. 1949 * 1950 * The "root blog" is the blog on a WordPress network where BuddyPress content 1951 * appears (where member profile URLs resolve, where a given theme is loaded, 1952 * etc.). 1953 * 1954 * @since 1.5.0 1955 * 1956 * @return int The root site ID. 1957 */ 1958 function bp_get_root_blog_id() { 1959 1960 /** 1961 * Filters the ID for the root blog. 1962 * 1963 * @since 1.5.0 1964 * 1965 * @param int $root_blog_id ID for the root blog. 1966 */ 1967 return (int) apply_filters( 'bp_get_root_blog_id', (int) buddypress()->root_blog_id ); 1968 } 1969 1970 /** 1971 * Are we running multiblog mode? 1972 * 1973 * Note that BP_ENABLE_MULTIBLOG is different from (but dependent on) WordPress 1974 * Multisite. "Multiblog" is BuddyPress setup that allows BuddyPress components 1975 * to be viewed on every blog on the network, each with their own settings. 1976 * 1977 * Thus, instead of having all 'boonebgorges' links go to 1978 * http://example.com/members/boonebgorges 1979 * on the root blog, each blog will have its own version of the same content, eg 1980 * http://site2.example.com/members/boonebgorges (for subdomains) 1981 * http://example.com/site2/members/boonebgorges (for subdirectories) 1982 * 1983 * Multiblog mode is disabled by default, meaning that all BuddyPress content 1984 * must be viewed on the root blog. It's also recommended not to use the 1985 * BP_ENABLE_MULTIBLOG constant beyond 1.7, as BuddyPress can now be activated 1986 * on individual sites. 1987 * 1988 * Why would you want to use this? Originally it was intended to allow 1989 * BuddyPress to live in mu-plugins and be visible on mapped domains. This is 1990 * a very small use-case with large architectural shortcomings, so do not go 1991 * down this road unless you specifically need to. 1992 * 1993 * @since 1.5.0 1994 * 1995 * @return bool False when multiblog mode is disabled; true when enabled. 1996 * Default: false. 1997 */ 1998 function bp_is_multiblog_mode() { 1999 2000 // Setup some default values. 2001 $retval = false; 2002 $is_multisite = is_multisite(); 2003 $network_active = bp_is_network_activated(); 2004 $is_multiblog = defined( 'BP_ENABLE_MULTIBLOG' ) && BP_ENABLE_MULTIBLOG; 2005 2006 // Multisite, Network Activated, and Specifically Multiblog. 2007 if ( $is_multisite && $network_active && $is_multiblog ) { 2008 $retval = true; 2009 2010 // Multisite, but not network activated. 2011 } elseif ( $is_multisite && ! $network_active ) { 2012 $retval = true; 2013 } 2014 2015 /** 2016 * Filters whether or not we're running in multiblog mode. 2017 * 2018 * @since 1.5.0 2019 * 2020 * @param bool $retval Whether or not we're running multiblog mode. 2021 */ 2022 return apply_filters( 'bp_is_multiblog_mode', $retval ); 2023 } 2024 2025 /** 2026 * Is BuddyPress active at the network level for this network? 2027 * 2028 * Used to determine admin menu placement, and where settings and options are 2029 * stored. If you're being *really* clever and manually pulling BuddyPress in 2030 * with an mu-plugin or some other method, you'll want to filter 2031 * 'bp_is_network_activated' and override the auto-determined value. 2032 * 2033 * @since 1.7.0 2034 * 2035 * @return bool True if BuddyPress is network activated. 2036 */ 2037 function bp_is_network_activated() { 2038 2039 // Default to is_multisite(). 2040 $retval = is_multisite(); 2041 2042 // Check the sitewide plugins array. 2043 $base = buddypress()->basename; 2044 $plugins = get_site_option( 'active_sitewide_plugins' ); 2045 2046 // Override is_multisite() if not network activated. 2047 if ( ! is_array( $plugins ) || ! isset( $plugins[ $base ] ) ) { 2048 $retval = false; 2049 } 2050 2051 /** 2052 * Filters whether or not we're active at the network level. 2053 * 2054 * @since 1.7.0 2055 * 2056 * @param bool $retval Whether or not we're network activated. 2057 */ 2058 return (bool) apply_filters( 'bp_is_network_activated', $retval ); 2059 } 2060 2061 /** Global Manipulators *******************************************************/ 2062 2063 /** 2064 * Set the "is_directory" global. 2065 * 2066 * @since 1.5.0 2067 * 2068 * @param bool $is_directory Optional. Default: false. 2069 * @param string $component Optional. Component name. Default: the current 2070 * component. 2071 */ 2072 function bp_update_is_directory( $is_directory = false, $component = '' ) { 2073 2074 if ( empty( $component ) ) { 2075 $component = bp_current_component(); 2076 } 2077 2078 /** 2079 * Filters the "is_directory" global value. 2080 * 2081 * @since 1.5.0 2082 * 2083 * @param bool $is_directory Whether or not we're "is_directory". 2084 * @param string $component Component name. Default: the current component. 2085 */ 2086 buddypress()->is_directory = apply_filters( 'bp_update_is_directory', $is_directory, $component ); 2087 } 2088 2089 /** 2090 * Set the "is_item_admin" global. 2091 * 2092 * @since 1.5.0 2093 * 2094 * @param bool $is_item_admin Optional. Default: false. 2095 * @param string $component Optional. Component name. Default: the current 2096 * component. 2097 */ 2098 function bp_update_is_item_admin( $is_item_admin = false, $component = '' ) { 2099 2100 if ( empty( $component ) ) { 2101 $component = bp_current_component(); 2102 } 2103 2104 /** 2105 * Filters the "is_item_admin" global value. 2106 * 2107 * @since 1.5.0 2108 * 2109 * @param bool $is_item_admin Whether or not we're "is_item_admin". 2110 * @param string $component Component name. Default: the current component. 2111 */ 2112 buddypress()->is_item_admin = apply_filters( 'bp_update_is_item_admin', $is_item_admin, $component ); 2113 } 2114 2115 /** 2116 * Set the "is_item_mod" global. 2117 * 2118 * @since 1.5.0 2119 * 2120 * @param bool $is_item_mod Optional. Default: false. 2121 * @param string $component Optional. Component name. Default: the current 2122 * component. 2123 */ 2124 function bp_update_is_item_mod( $is_item_mod = false, $component = '' ) { 2125 2126 if ( empty( $component ) ) { 2127 $component = bp_current_component(); 2128 } 2129 2130 /** 2131 * Filters the "is_item_mod" global value. 2132 * 2133 * @since 1.5.0 2134 * 2135 * @param bool $is_item_mod Whether or not we're "is_item_mod". 2136 * @param string $component Component name. Default: the current component. 2137 */ 2138 buddypress()->is_item_mod = apply_filters( 'bp_update_is_item_mod', $is_item_mod, $component ); 2139 } 2140 2141 /** 2142 * Trigger a 404. 2143 * 2144 * @since 1.5.0 2145 * 2146 * @global WP_Query $wp_query WordPress query object. 2147 * 2148 * @param string $redirect If 'remove_canonical_direct', remove WordPress' "helpful" 2149 * redirect_canonical action. Default: 'remove_canonical_redirect'. 2150 */ 2151 function bp_do_404( $redirect = 'remove_canonical_direct' ) { 2152 global $wp_query; 2153 2154 /** 2155 * Fires inside the triggering of a 404. 2156 * 2157 * @since 1.5.0 2158 * 2159 * @param string $redirect Redirect type used to determine if redirect_canonical 2160 * function should be be removed. 2161 */ 2162 do_action( 'bp_do_404', $redirect ); 2163 2164 $wp_query->set_404(); 2165 status_header( 404 ); 2166 nocache_headers(); 2167 2168 if ( 'remove_canonical_direct' === $redirect ) { 2169 remove_action( 'template_redirect', 'redirect_canonical' ); 2170 } 2171 } 2172 2173 /** Nonces ********************************************************************/ 2174 2175 /** 2176 * Makes sure the user requested an action from another page on this site. 2177 * 2178 * To avoid security exploits within the theme. 2179 * 2180 * @since 1.6.0 2181 * 2182 * @param string $action Action nonce. 2183 * @param string $query_arg Where to look for nonce in $_REQUEST. 2184 * @return bool True if the nonce is verified, otherwise false. 2185 */ 2186 function bp_verify_nonce_request( $action = '', $query_arg = '_wpnonce' ) { 2187 2188 /* Home URL **************************************************************/ 2189 2190 // Parse home_url() into pieces to remove query-strings, strange characters, 2191 // and other funny things that plugins might to do to it. 2192 $parsed_home = parse_url( home_url( '/', ( is_ssl() ? 'https' : 'http' ) ) ); 2193 2194 // Maybe include the port, if it's included in home_url(). 2195 if ( isset( $parsed_home['port'] ) ) { 2196 $parsed_host = $parsed_home['host'] . ':' . $parsed_home['port']; 2197 } else { 2198 $parsed_host = $parsed_home['host']; 2199 } 2200 2201 // Set the home URL for use in comparisons. 2202 $home_url = trim( strtolower( $parsed_home['scheme'] . '://' . $parsed_host . $parsed_home['path'] ), '/' ); 2203 2204 /* Requested URL *********************************************************/ 2205 2206 // Maybe include the port, if it's included in home_url(). 2207 if ( isset( $parsed_home['port'] ) && false === strpos( $_SERVER['HTTP_HOST'], ':' ) ) { 2208 $request_host = $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT']; 2209 } else { 2210 $request_host = $_SERVER['HTTP_HOST']; 2211 } 2212 2213 // Build the currently requested URL. 2214 $scheme = is_ssl() ? 'https://' : 'http://'; 2215 $requested_url = strtolower( $scheme . $request_host . $_SERVER['REQUEST_URI'] ); 2216 2217 /* Look for match ********************************************************/ 2218 2219 /** 2220 * Filters the requested URL being nonce-verified. 2221 * 2222 * Useful for configurations like reverse proxying. 2223 * 2224 * @since 1.9.0 2225 * 2226 * @param string $requested_url The requested URL. 2227 */ 2228 $matched_url = apply_filters( 'bp_verify_nonce_request_url', $requested_url ); 2229 2230 // Check the nonce. 2231 $result = isset( $_REQUEST[$query_arg] ) ? wp_verify_nonce( $_REQUEST[$query_arg], $action ) : false; 2232 2233 // Nonce check failed. 2234 if ( empty( $result ) || empty( $action ) || ( strpos( $matched_url, $home_url ) !== 0 ) ) { 2235 $result = false; 2236 } 2237 2238 /** 2239 * Fires at the end of the nonce verification check. 2240 * 2241 * @since 1.6.0 2242 * 2243 * @param string $action Action nonce. 2244 * @param bool $result Boolean result of nonce verification. 2245 */ 2246 do_action( 'bp_verify_nonce_request', $action, $result ); 2247 2248 return $result; 2249 } 2250 2251 /** Requests ******************************************************************/ 2252 2253 /** 2254 * Return true|false if this is a POST request. 2255 * 2256 * @since 1.9.0 2257 * 2258 * @return bool 2259 */ 2260 function bp_is_post_request() { 2261 return (bool) ( 'POST' === strtoupper( $_SERVER['REQUEST_METHOD'] ) ); 2262 } 2263 2264 /** 2265 * Return true|false if this is a GET request. 2266 * 2267 * @since 1.9.0 2268 * 2269 * @return bool 2270 */ 2271 function bp_is_get_request() { 2272 return (bool) ( 'GET' === strtoupper( $_SERVER['REQUEST_METHOD'] ) ); 2273 } 2274 2275 2276 /** Miscellaneous hooks *******************************************************/ 2277 2278 /** 2279 * Load the buddypress translation file for current language. 2280 * 2281 * @since 1.0.2 2282 * 2283 * @see load_textdomain() for a description of return values. 2284 * 2285 * @return bool True on success, false on failure. 2286 */ 2287 function bp_core_load_buddypress_textdomain() { 2288 $domain = 'buddypress'; 2289 2290 /** 2291 * Filters the locale to be loaded for the language files. 2292 * 2293 * @since 1.0.2 2294 * 2295 * @param string $value Current locale for the install. 2296 */ 2297 $mofile_custom = sprintf( '%s-%s.mo', $domain, apply_filters( 'buddypress_locale', get_locale() ) ); 2298 2299 /** 2300 * Filters the locations to load language files from. 2301 * 2302 * @since 2.2.0 2303 * 2304 * @param array $value Array of directories to check for language files in. 2305 */ 2306 $locations = apply_filters( 'buddypress_locale_locations', array( 2307 trailingslashit( WP_LANG_DIR . '/' . $domain ), 2308 trailingslashit( WP_LANG_DIR ), 2309 ) ); 2310 2311 // Try custom locations in WP_LANG_DIR. 2312 foreach ( $locations as $location ) { 2313 if ( load_textdomain( 'buddypress', $location . $mofile_custom ) ) { 2314 return true; 2315 } 2316 } 2317 2318 // Default to WP and glotpress. 2319 return load_plugin_textdomain( $domain ); 2320 } 2321 add_action( 'bp_core_loaded', 'bp_core_load_buddypress_textdomain' ); 2322 2323 /** 2324 * A JavaScript-free implementation of the search functions in BuddyPress. 2325 * 2326 * @since 1.0.1 2327 * 2328 * @param string $slug The slug to redirect to for searching. 2329 */ 2330 function bp_core_action_search_site( $slug = '' ) { 2331 2332 if ( ! bp_is_current_component( bp_get_search_slug() ) ) { 2333 return; 2334 } 2335 2336 if ( empty( $_POST['search-terms'] ) ) { 2337 bp_core_redirect( bp_get_root_domain() ); 2338 return; 2339 } 2340 2341 $search_terms = stripslashes( $_POST['search-terms'] ); 2342 $search_which = !empty( $_POST['search-which'] ) ? $_POST['search-which'] : ''; 2343 $query_string = '/?s='; 2344 2345 if ( empty( $slug ) ) { 2346 switch ( $search_which ) { 2347 case 'posts': 2348 $slug = ''; 2349 $var = '/?s='; 2350 2351 // If posts aren't displayed on the front page, find the post page's slug. 2352 if ( 'page' == get_option( 'show_on_front' ) ) { 2353 $page = get_post( get_option( 'page_for_posts' ) ); 2354 2355 if ( !is_wp_error( $page ) && !empty( $page->post_name ) ) { 2356 $slug = $page->post_name; 2357 $var = '?s='; 2358 } 2359 } 2360 break; 2361 2362 case 'blogs': 2363 $slug = bp_is_active( 'blogs' ) ? bp_get_blogs_root_slug() : ''; 2364 break; 2365 2366 case 'groups': 2367 $slug = bp_is_active( 'groups' ) ? bp_get_groups_root_slug() : ''; 2368 break; 2369 2370 case 'members': 2371 default: 2372 $slug = bp_get_members_root_slug(); 2373 break; 2374 } 2375 2376 if ( empty( $slug ) && 'posts' != $search_which ) { 2377 bp_core_redirect( bp_get_root_domain() ); 2378 return; 2379 } 2380 } 2381 2382 /** 2383 * Filters the constructed url for use with site searching. 2384 * 2385 * @since 1.0.0 2386 * 2387 * @param string $value URL for use with site searching. 2388 * @param array $search_terms Array of search terms. 2389 */ 2390 bp_core_redirect( apply_filters( 'bp_core_search_site', home_url( $slug . $query_string . urlencode( $search_terms ) ), $search_terms ) ); 2391 } 2392 add_action( 'bp_init', 'bp_core_action_search_site', 7 ); 2393 2394 /** 2395 * Remove "prev" and "next" relational links from <head> on BuddyPress pages. 2396 * 2397 * WordPress automatically generates these relational links to the current 2398 * page. However, BuddyPress doesn't adhere to these links. In this 2399 * function, we remove these links when on a BuddyPress page. This also 2400 * prevents additional, unnecessary queries from running. 2401 * 2402 * @since 2.1.0 2403 */ 2404 function bp_remove_adjacent_posts_rel_link() { 2405 if ( ! is_buddypress() ) { 2406 return; 2407 } 2408 2409 remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head', 10 ); 2410 } 2411 add_action( 'bp_init', 'bp_remove_adjacent_posts_rel_link' ); 2412 2413 /** 2414 * Strip the span count of a menu item or of a title part. 2415 * 2416 * @since 2.2.2 2417 * 2418 * @param string $title_part Title part to clean up. 2419 * @return string 2420 */ 2421 function _bp_strip_spans_from_title( $title_part = '' ) { 2422 $title = $title_part; 2423 $span = strpos( $title, '<span' ); 2424 if ( false !== $span ) { 2425 $title = substr( $title, 0, $span - 1 ); 2426 } 2427 return trim( $title ); 2428 } 2429 2430 /** 2431 * Get the correct filename suffix for minified assets. 2432 * 2433 * @since 2.5.0 2434 * 2435 * @return string 2436 */ 2437 function bp_core_get_minified_asset_suffix() { 2438 $ext = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; 2439 2440 // Ensure the assets can be located when running from /src/. 2441 if ( defined( 'BP_SOURCE_SUBDIRECTORY' ) && BP_SOURCE_SUBDIRECTORY === 'src' ) { 2442 $ext = str_replace( '.min', '', $ext ); 2443 } 2444 2445 return $ext; 2446 } 2447 2448 /** 2449 * Return a list of component information. 2450 * 2451 * @since 2.6.0 2452 * 2453 * @param string $type Optional; component type to fetch. Default value is 'all', or 'optional', 'retired', 'required'. 2454 * @return array Requested components' data. 2455 */ 2456 function bp_core_get_components( $type = 'all' ) { 2457 $required_components = array( 2458 'core' => array( 2459 'title' => __( 'BuddyPress Core', 'buddypress' ), 2460 'description' => __( 'It‘s what makes <del>time travel</del> BuddyPress possible!', 'buddypress' ) 2461 ), 2462 'members' => array( 2463 'title' => __( 'Community Members', 'buddypress' ), 2464 'description' => __( 'Everything in a BuddyPress community revolves around its members.', 'buddypress' ) 2465 ), 2466 ); 2467 2468 $retired_components = array( 2469 ); 2470 2471 $optional_components = array( 2472 'xprofile' => array( 2473 'title' => __( 'Extended Profiles', 'buddypress' ), 2474 'description' => __( 'Customize your community with fully editable profile fields that allow your users to describe themselves.', 'buddypress' ) 2475 ), 2476 'settings' => array( 2477 'title' => __( 'Account Settings', 'buddypress' ), 2478 'description' => __( 'Allow your users to modify their account and notification settings directly from within their profiles.', 'buddypress' ) 2479 ), 2480 'friends' => array( 2481 'title' => __( 'Friend Connections', 'buddypress' ), 2482 'description' => __( 'Let your users make connections so they can track the activity of others and focus on the people they care about the most.', 'buddypress' ) 2483 ), 2484 'messages' => array( 2485 'title' => __( 'Private Messaging', 'buddypress' ), 2486 'description' => __( 'Allow your users to talk to each other directly and in private. Not just limited to one-on-one discussions, messages can be sent between any number of members.', 'buddypress' ) 2487 ), 2488 'activity' => array( 2489 'title' => __( 'Activity Streams', 'buddypress' ), 2490 'description' => __( 'Global, personal, and group activity streams with threaded commenting, direct posting, favoriting, and @mentions, all with full RSS feed and email notification support.', 'buddypress' ) 2491 ), 2492 'notifications' => array( 2493 'title' => __( 'Notifications', 'buddypress' ), 2494 'description' => __( 'Notify members of relevant activity with a toolbar bubble and/or via email, and allow them to customize their notification settings.', 'buddypress' ) 2495 ), 2496 'groups' => array( 2497 'title' => __( 'User Groups', 'buddypress' ), 2498 'description' => __( 'Groups allow your users to organize themselves into specific public, private or hidden sections with separate activity streams and member listings.', 'buddypress' ) 2499 ), 2500 'blogs' => array( 2501 'title' => __( 'Site Tracking', 'buddypress' ), 2502 'description' => __( 'Record activity for new posts and comments from your site.', 'buddypress' ) 2503 ) 2504 ); 2505 2506 // Add blogs tracking if multisite. 2507 if ( is_multisite() ) { 2508 $optional_components['blogs']['description'] = __( 'Record activity for new sites, posts, and comments across your network.', 'buddypress' ); 2509 } 2510 2511 switch ( $type ) { 2512 case 'required' : 2513 $components = $required_components; 2514 break; 2515 case 'optional' : 2516 $components = $optional_components; 2517 break; 2518 case 'retired' : 2519 $components = $retired_components; 2520 break; 2521 case 'all' : 2522 default : 2523 $components = array_merge( $required_components, $optional_components, $retired_components ); 2524 break; 2525 } 2526 2527 /** 2528 * Filters the list of component information. 2529 * 2530 * @since 2.6.0 2531 * 2532 * @param array $components Array of component information. 2533 * @param string $type Type of component list requested. 2534 * Possible values are 'all', 'optional', 'retired', 'required'. 2535 */ 2536 return apply_filters( 'bp_core_get_components', $components, $type ); 2537 } 2538 2539 /** Nav Menu ******************************************************************/ 2540 2541 /** 2542 * Create fake "post" objects for BP's logged-in nav menu for use in the WordPress "Menus" settings page. 2543 * 2544 * WordPress nav menus work by representing post or tax term data as a custom 2545 * post type, which is then used to populate the checkboxes that appear on 2546 * Dashboard > Appearance > Menu as well as the menu as rendered on the front 2547 * end. Most of the items in the BuddyPress set of nav items are neither posts 2548 * nor tax terms, so we fake a post-like object so as to be compatible with the 2549 * menu. 2550 * 2551 * This technique also allows us to generate links dynamically, so that, for 2552 * example, "My Profile" will always point to the URL of the profile of the 2553 * logged-in user. 2554 * 2555 * @since 1.9.0 2556 * 2557 * @return mixed A URL or an array of dummy pages. 2558 */ 2559 function bp_nav_menu_get_loggedin_pages() { 2560 $bp = buddypress(); 2561 2562 // Try to catch the cached version first. 2563 if ( ! empty( $bp->wp_nav_menu_items->loggedin ) ) { 2564 return $bp->wp_nav_menu_items->loggedin; 2565 } 2566 2567 // Pull up a list of items registered in BP's primary nav for the member. 2568 $bp_menu_items = $bp->members->nav->get_primary(); 2569 2570 // Some BP nav menu items will not be represented in bp_nav, because 2571 // they are not real BP components. We add them manually here. 2572 $bp_menu_items[] = array( 2573 'name' => __( 'Log Out', 'buddypress' ), 2574 'slug' => 'logout', 2575 'link' => wp_logout_url(), 2576 ); 2577 2578 // If there's nothing to show, we're done. 2579 if ( count( $bp_menu_items ) < 1 ) { 2580 return false; 2581 } 2582 2583 $page_args = array(); 2584 2585 foreach ( $bp_menu_items as $bp_item ) { 2586 2587 // Remove <span>number</span>. 2588 $item_name = _bp_strip_spans_from_title( $bp_item['name'] ); 2589 2590 $page_args[ $bp_item['slug'] ] = (object) array( 2591 'ID' => -1, 2592 'post_title' => $item_name, 2593 'post_author' => 0, 2594 'post_date' => 0, 2595 'post_excerpt' => $bp_item['slug'], 2596 'post_type' => 'bp_nav_menu_item', 2597 'post_status' => 'publish', 2598 'comment_status' => 'closed', 2599 'guid' => $bp_item['link'] 2600 ); 2601 } 2602 2603 if ( empty( $bp->wp_nav_menu_items ) ) { 2604 buddypress()->wp_nav_menu_items = new stdClass; 2605 } 2606 2607 $bp->wp_nav_menu_items->loggedin = $page_args; 2608 2609 return $page_args; 2610 } 2611 2612 /** 2613 * Create fake "post" objects for BP's logged-out nav menu for use in the WordPress "Menus" settings page. 2614 * 2615 * WordPress nav menus work by representing post or tax term data as a custom 2616 * post type, which is then used to populate the checkboxes that appear on 2617 * Dashboard > Appearance > Menu as well as the menu as rendered on the front 2618 * end. Most of the items in the BuddyPress set of nav items are neither posts 2619 * nor tax terms, so we fake a post-like object so as to be compatible with the 2620 * menu. 2621 * 2622 * @since 1.9.0 2623 * 2624 * @return mixed A URL or an array of dummy pages. 2625 */ 2626 function bp_nav_menu_get_loggedout_pages() { 2627 $bp = buddypress(); 2628 2629 // Try to catch the cached version first. 2630 if ( ! empty( $bp->wp_nav_menu_items->loggedout ) ) { 2631 return $bp->wp_nav_menu_items->loggedout; 2632 } 2633 2634 $bp_menu_items = array(); 2635 2636 // Some BP nav menu items will not be represented in bp_nav, because 2637 // they are not real BP components. We add them manually here. 2638 $bp_menu_items[] = array( 2639 'name' => __( 'Log In', 'buddypress' ), 2640 'slug' => 'login', 2641 'link' => wp_login_url(), 2642 ); 2643 2644 // The Register page will not always be available (ie, when 2645 // registration is disabled). 2646 $bp_directory_page_ids = bp_core_get_directory_page_ids(); 2647 2648 if( ! empty( $bp_directory_page_ids['register'] ) ) { 2649 $register_page = get_post( $bp_directory_page_ids['register'] ); 2650 $bp_menu_items[] = array( 2651 'name' => $register_page->post_title, 2652 'slug' => 'register', 2653 'link' => get_permalink( $register_page->ID ), 2654 ); 2655 } 2656 2657 // If there's nothing to show, we're done. 2658 if ( count( $bp_menu_items ) < 1 ) { 2659 return false; 2660 } 2661 2662 $page_args = array(); 2663 2664 foreach ( $bp_menu_items as $bp_item ) { 2665 $page_args[ $bp_item['slug'] ] = (object) array( 2666 'ID' => -1, 2667 'post_title' => $bp_item['name'], 2668 'post_author' => 0, 2669 'post_date' => 0, 2670 'post_excerpt' => $bp_item['slug'], 2671 'post_type' => 'bp_nav_menu_item', 2672 'post_status' => 'publish', 2673 'comment_status' => 'closed', 2674 'guid' => $bp_item['link'] 2675 ); 2676 } 2677 2678 if ( empty( $bp->wp_nav_menu_items ) ) { 2679 $bp->wp_nav_menu_items = new stdClass; 2680 } 2681 2682 $bp->wp_nav_menu_items->loggedout = $page_args; 2683 2684 return $page_args; 2685 } 2686 2687 /** 2688 * Get the URL for a BuddyPress WP nav menu item, based on slug. 2689 * 2690 * BuddyPress-specific WP nav menu items have dynamically generated URLs, 2691 * based on the identity of the current user. This function lets you fetch the 2692 * proper URL for a given nav item slug (such as 'login' or 'messages'). 2693 * 2694 * @since 1.9.0 2695 * 2696 * @param string $slug The slug of the nav item: login, register, or one of the 2697 * slugs from the members navigation. 2698 * @return string $nav_item_url The URL generated for the current user. 2699 */ 2700 function bp_nav_menu_get_item_url( $slug ) { 2701 $nav_item_url = ''; 2702 $nav_menu_items = bp_nav_menu_get_loggedin_pages(); 2703 2704 if ( isset( $nav_menu_items[ $slug ] ) ) { 2705 $nav_item_url = $nav_menu_items[ $slug ]->guid; 2706 } 2707 2708 return $nav_item_url; 2709 } 2710 2711 /** Suggestions***************************************************************/ 2712 2713 /** 2714 * BuddyPress Suggestions API for types of at-mentions. 2715 * 2716 * This is used to power BuddyPress' at-mentions suggestions, but it is flexible enough to be used 2717 * for similar kinds of future requirements, or those implemented by third-party developers. 2718 * 2719 * @since 2.1.0 2720 * 2721 * @param array $args Array of args for the suggestions. 2722 * @return array|WP_Error Array of results. If there were any problems, returns a WP_Error object. 2723 */ 2724 function bp_core_get_suggestions( $args ) { 2725 $args = bp_parse_args( $args, array(), 'get_suggestions' ); 2726 2727 if ( ! $args['type'] ) { 2728 return new WP_Error( 'missing_parameter' ); 2729 } 2730 2731 // Members @name suggestions. 2732 if ( $args['type'] === 'members' ) { 2733 $class = 'BP_Members_Suggestions'; 2734 2735 // Members @name suggestions for users in a specific Group. 2736 if ( isset( $args['group_id'] ) ) { 2737 $class = 'BP_Groups_Member_Suggestions'; 2738 } 2739 2740 } else { 2741 2742 /** 2743 * Filters the default suggestions service to use. 2744 * 2745 * Use this hook to tell BP the name of your class 2746 * if you've built a custom suggestions service. 2747 * 2748 * @since 2.1.0 2749 * 2750 * @param string $value Custom class to use. Default: none. 2751 * @param array $args Array of arguments for suggestions. 2752 */ 2753 $class = apply_filters( 'bp_suggestions_services', '', $args ); 2754 } 2755 2756 if ( ! $class || ! class_exists( $class ) ) { 2757 return new WP_Error( 'missing_parameter' ); 2758 } 2759 2760 2761 $suggestions = new $class( $args ); 2762 $validation = $suggestions->validate(); 2763 2764 if ( is_wp_error( $validation ) ) { 2765 $retval = $validation; 2766 } else { 2767 $retval = $suggestions->get_suggestions(); 2768 } 2769 2770 /** 2771 * Filters the available type of at-mentions. 2772 * 2773 * @since 2.1.0 2774 * 2775 * @param array|WP_Error $retval Array of results or WP_Error object. 2776 * @param array $args Array of arguments for suggestions. 2777 */ 2778 return apply_filters( 'bp_core_get_suggestions', $retval, $args ); 2779 } 2780 2781 /** 2782 * AJAX endpoint for Suggestions API lookups. 2783 * 2784 * @since 2.1.0 2785 * @since 4.0.0 Moved here to make sure this function is available 2786 * even if the Activity component is not active. 2787 */ 2788 function bp_ajax_get_suggestions() { 2789 if ( ! bp_is_user_active() || empty( $_GET['term'] ) || empty( $_GET['type'] ) ) { 2790 wp_send_json_error( 'missing_parameter' ); 2791 exit; 2792 } 2793 2794 $args = array( 2795 'term' => sanitize_text_field( $_GET['term'] ), 2796 'type' => sanitize_text_field( $_GET['type'] ), 2797 ); 2798 2799 // Support per-Group suggestions. 2800 if ( ! empty( $_GET['group-id'] ) ) { 2801 $args['group_id'] = absint( $_GET['group-id'] ); 2802 } 2803 2804 $results = bp_core_get_suggestions( $args ); 2805 2806 if ( is_wp_error( $results ) ) { 2807 wp_send_json_error( $results->get_error_message() ); 2808 exit; 2809 } 2810 2811 wp_send_json_success( $results ); 2812 } 2813 add_action( 'wp_ajax_bp_get_suggestions', 'bp_ajax_get_suggestions' ); 2814 2815 /** 2816 * Set data from the BP root blog's upload directory. 2817 * 2818 * Handy for multisite instances because all uploads are made on the BP root 2819 * blog and we need to query the BP root blog for the upload directory data. 2820 * 2821 * This function ensures that we only need to use {@link switch_to_blog()} 2822 * once to get what we need. 2823 * 2824 * @since 2.3.0 2825 * 2826 * @return bool|array 2827 */ 2828 function bp_upload_dir() { 2829 $bp = buddypress(); 2830 2831 if ( empty( $bp->upload_dir ) ) { 2832 $need_switch = (bool) ( is_multisite() && ! bp_is_root_blog() ); 2833 2834 // Maybe juggle to root blog. 2835 if ( true === $need_switch ) { 2836 switch_to_blog( bp_get_root_blog_id() ); 2837 } 2838 2839 // Get the upload directory (maybe for root blog). 2840 $wp_upload_dir = wp_upload_dir(); 2841 2842 // Maybe juggle back to current blog. 2843 if ( true === $need_switch ) { 2844 restore_current_blog(); 2845 } 2846 2847 // Bail if an error occurred. 2848 if ( ! empty( $wp_upload_dir['error'] ) ) { 2849 return false; 2850 } 2851 2852 $bp->upload_dir = $wp_upload_dir; 2853 } 2854 2855 return $bp->upload_dir; 2856 } 2857 2858 2859 /** Post Types *****************************************************************/ 2860 2861 /** 2862 * Output the name of the email post type. 2863 * 2864 * @since 2.5.0 2865 */ 2866 function bp_email_post_type() { 2867 echo bp_get_email_post_type(); 2868 } 2869 /** 2870 * Returns the name of the email post type. 2871 * 2872 * @since 2.5.0 2873 * 2874 * @return string The name of the email post type. 2875 */ 2876 function bp_get_email_post_type() { 2877 2878 /** 2879 * Filters the name of the email post type. 2880 * 2881 * @since 2.5.0 2882 * 2883 * @param string $value Email post type name. 2884 */ 2885 return apply_filters( 'bp_get_email_post_type', buddypress()->email_post_type ); 2886 } 2887 2888 /** 2889 * Return labels used by the email post type. 2890 * 2891 * @since 2.5.0 2892 * 2893 * @return array 2894 */ 2895 function bp_get_email_post_type_labels() { 2896 2897 /** 2898 * Filters email post type labels. 2899 * 2900 * @since 2.5.0 2901 * 2902 * @param array $value Associative array (name => label). 2903 */ 2904 return apply_filters( 'bp_get_email_post_type_labels', array( 2905 'add_new' => _x( 'Add New', 'email post type label', 'buddypress' ), 2906 'add_new_item' => _x( 'Add a New Email', 'email post type label', 'buddypress' ), 2907 'all_items' => _x( 'All Emails', 'email post type label', 'buddypress' ), 2908 'edit_item' => _x( 'Edit Email', 'email post type label', 'buddypress' ), 2909 'filter_items_list' => _x( 'Filter email list', 'email post type label', 'buddypress' ), 2910 'items_list' => _x( 'Email list', 'email post type label', 'buddypress' ), 2911 'items_list_navigation' => _x( 'Email list navigation', 'email post type label', 'buddypress' ), 2912 'menu_name' => _x( 'Emails', 'email post type name', 'buddypress' ), 2913 'name' => _x( 'BuddyPress Emails', 'email post type label', 'buddypress' ), 2914 'new_item' => _x( 'New Email', 'email post type label', 'buddypress' ), 2915 'not_found' => _x( 'No emails found', 'email post type label', 'buddypress' ), 2916 'not_found_in_trash' => _x( 'No emails found in Trash', 'email post type label', 'buddypress' ), 2917 'search_items' => _x( 'Search Emails', 'email post type label', 'buddypress' ), 2918 'singular_name' => _x( 'Email', 'email post type singular name', 'buddypress' ), 2919 'uploaded_to_this_item' => _x( 'Uploaded to this email', 'email post type label', 'buddypress' ), 2920 'view_item' => _x( 'View Email', 'email post type label', 'buddypress' ), 2921 ) ); 2922 } 2923 2924 /** 2925 * Return array of features that the email post type supports. 2926 * 2927 * @since 2.5.0 2928 * 2929 * @return array 2930 */ 2931 function bp_get_email_post_type_supports() { 2932 2933 /** 2934 * Filters the features that the email post type supports. 2935 * 2936 * @since 2.5.0 2937 * 2938 * @param array $value Supported features. 2939 */ 2940 return apply_filters( 'bp_get_email_post_type_supports', array( 2941 'custom-fields', 2942 'editor', 2943 'excerpt', 2944 'revisions', 2945 'title', 2946 ) ); 2947 } 2948 2949 2950 /** Taxonomies *****************************************************************/ 2951 2952 /** 2953 * Returns the BP Taxonomy common arguments. 2954 * 2955 * @since 7.0.0 2956 * 2957 * @return array The BP Taxonomy common arguments. 2958 */ 2959 function bp_get_taxonomy_common_args() { 2960 return array( 2961 'public' => false, 2962 'show_in_rest' => false, 2963 'query_var' => false, 2964 'rewrite' => false, 2965 'show_in_menu' => false, 2966 'show_tagcloud' => false, 2967 'show_ui' => bp_is_root_blog() && bp_current_user_can( 'bp_moderate' ), 2968 ); 2969 } 2970 2971 /** 2972 * Returns the BP Taxonomy common labels. 2973 * 2974 * @since 7.0.0 2975 * 2976 * @return array The BP Taxonomy common labels. 2977 */ 2978 function bp_get_taxonomy_common_labels() { 2979 return array( 2980 'bp_type_name' => _x( 'Plural Name', 'BP Type name label', 'buddypress' ), 2981 'bp_type_singular_name' => _x( 'Singular name', 'BP Type singular name label', 'buddypress' ), 2982 'bp_type_has_directory' => _x( 'Has Directory View', 'BP Type has directory checkbox label', 'buddypress' ), 2983 'bp_type_directory_slug' => _x( 'Custom type directory slug', 'BP Type slug label', 'buddypress' ), 2984 ); 2985 } 2986 2987 /** 2988 * Output the name of the email type taxonomy. 2989 * 2990 * @since 2.5.0 2991 */ 2992 function bp_email_tax_type() { 2993 echo bp_get_email_tax_type(); 2994 } 2995 /** 2996 * Return the name of the email type taxonomy. 2997 * 2998 * @since 2.5.0 2999 * 3000 * @return string The unique email taxonomy type ID. 3001 */ 3002 function bp_get_email_tax_type() { 3003 3004 /** 3005 * Filters the name of the email type taxonomy. 3006 * 3007 * @since 2.5.0 3008 * 3009 * @param string $value Email type taxonomy name. 3010 */ 3011 return apply_filters( 'bp_get_email_tax_type', buddypress()->email_taxonomy_type ); 3012 } 3013 3014 /** 3015 * Return labels used by the email type taxonomy. 3016 * 3017 * @since 2.5.0 3018 * 3019 * @return array 3020 */ 3021 function bp_get_email_tax_type_labels() { 3022 3023 /** 3024 * Filters email type taxonomy labels. 3025 * 3026 * @since 2.5.0 3027 * 3028 * @param array $value Associative array (name => label). 3029 */ 3030 return apply_filters( 'bp_get_email_tax_type_labels', array( 3031 'add_new_item' => _x( 'New Email Situation', 'email type taxonomy label', 'buddypress' ), 3032 'all_items' => _x( 'All Email Situations', 'email type taxonomy label', 'buddypress' ), 3033 'edit_item' => _x( 'Edit Email Situations', 'email type taxonomy label', 'buddypress' ), 3034 'items_list' => _x( 'Email list', 'email type taxonomy label', 'buddypress' ), 3035 'items_list_navigation' => _x( 'Email list navigation', 'email type taxonomy label', 'buddypress' ), 3036 'menu_name' => _x( 'Situations', 'email type taxonomy label', 'buddypress' ), 3037 'name' => _x( 'Situation', 'email type taxonomy name', 'buddypress' ), 3038 'new_item_name' => _x( 'New email situation name', 'email type taxonomy label', 'buddypress' ), 3039 'not_found' => _x( 'No email situations found.', 'email type taxonomy label', 'buddypress' ), 3040 'no_terms' => _x( 'No email situations', 'email type taxonomy label', 'buddypress' ), 3041 'popular_items' => _x( 'Popular Email Situation', 'email type taxonomy label', 'buddypress' ), 3042 'search_items' => _x( 'Search Emails', 'email type taxonomy label', 'buddypress' ), 3043 'singular_name' => _x( 'Email', 'email type taxonomy singular name', 'buddypress' ), 3044 'update_item' => _x( 'Update Email Situation', 'email type taxonomy label', 'buddypress' ), 3045 'view_item' => _x( 'View Email Situation', 'email type taxonomy label', 'buddypress' ), 3046 ) ); 3047 } 3048 3049 /** 3050 * Return arguments used by the email type taxonomy. 3051 * 3052 * @since 7.0.0 3053 * 3054 * @return array 3055 */ 3056 function bp_get_email_tax_type_args() { 3057 3058 /** 3059 * Filters emails type taxonomy args. 3060 * 3061 * @since 7.0.0 3062 * 3063 * @param array $value Associative array (key => arg). 3064 */ 3065 return apply_filters( 3066 'bp_register_email_tax_type', 3067 array_merge( 3068 array( 3069 'description' => _x( 'BuddyPress email types', 'email type taxonomy description', 'buddypress' ), 3070 'labels' => bp_get_email_tax_type_labels(), 3071 'meta_box_cb' => 'bp_email_tax_type_metabox', 3072 ), 3073 bp_get_taxonomy_common_args() 3074 ) 3075 ); 3076 } 3077 3078 /** 3079 * Returns the default BuddyPress type metadata schema. 3080 * 3081 * @since 7.0.0 3082 * 3083 * @param boolean $suppress_filters Whether to suppress filters. Default `false`. 3084 * @param string $type_taxonomy Optional. the Type's taxonomy name. 3085 * @return array The default BuddyPress type metadata schema. 3086 */ 3087 function bp_get_type_metadata_schema( $suppress_filters = false, $type_taxonomy = '' ) { 3088 $schema = array( 3089 'bp_type_singular_name' => array( 3090 'description' => __( 'The name of this type in singular form. ', 'buddypress' ), 3091 'type' => 'string', 3092 'single' => true, 3093 'sanitize_callback' => 'sanitize_text_field', 3094 ), 3095 'bp_type_name' => array( 3096 'description' => __( 'The name of this type in plural form.', 'buddypress' ), 3097 'type' => 'string', 3098 'single' => true, 3099 'sanitize_callback' => 'sanitize_text_field', 3100 ), 3101 'bp_type_has_directory' => array( 3102 'description' => __( 'Make a list matching this type available on the directory.', 'buddypress' ), 3103 'type' => 'boolean', 3104 'single' => true, 3105 'sanitize_callback' => 'absint', 3106 ), 3107 'bp_type_directory_slug' => array( 3108 'label' => __( 'Type slug', 'buddypress' ), 3109 'description' => __( 'Enter if you want the type slug to be different from its ID.', 'buddypress' ), 3110 'type' => 'string', 3111 'single' => true, 3112 'sanitize_callback' => 'sanitize_title', 3113 ), 3114 ); 3115 3116 if ( true === $suppress_filters ) { 3117 return $schema; 3118 } 3119 3120 /** 3121 * Filter here to add new meta to the BuddyPress type metadata. 3122 * 3123 * @since 7.0.0 3124 * 3125 * @param array $schema Associative array (name => arguments). 3126 * @param string $type_taxonomy The Type's taxonomy name. 3127 */ 3128 return apply_filters( 'bp_get_type_metadata_schema', $schema, $type_taxonomy ); 3129 } 3130 3131 /** 3132 * Registers a meta key for BuddyPress types. 3133 * 3134 * @since 7.0.0 3135 * 3136 * @param string $type_tax The BuddyPress type taxonomy. 3137 * @param string $meta_key The meta key to register. 3138 * @param array $args Data used to describe the meta key when registered. See 3139 * {@see register_meta()} for a list of supported arguments. 3140 * @return bool True if the meta key was successfully registered, false if not. 3141 */ 3142 function bp_register_type_meta( $type_tax, $meta_key, array $args ) { 3143 $taxonomies = wp_list_pluck( bp_get_default_taxonomies(), 'component' ); 3144 3145 if ( ! isset( $taxonomies[ $type_tax ] ) ) { 3146 return false; 3147 } 3148 3149 // register_term_meta() was introduced in WP 4.9.8. 3150 if ( ! bp_is_running_wp( '4.9.8' ) ) { 3151 $args['object_subtype'] = $type_tax; 3152 3153 return register_meta( 'term', $meta_key, $args ); 3154 } 3155 3156 return register_term_meta( $type_tax, $meta_key, $args ); 3157 } 3158 3159 /** 3160 * Update a list of metadata for a given type ID and a given taxonomy. 3161 * 3162 * @since 7.0.0 3163 * 3164 * @param integer $type_id The database ID of the BP Type. 3165 * @param string $taxonomy The BP Type taxonomy. 3166 * @param array $type_metas An associative array (meta_key=>meta_value). 3167 * @return boolean False on failure. True otherwise. 3168 */ 3169 function bp_update_type_metadata( $type_id = 0, $taxonomy = '', $type_metas = array() ) { 3170 if ( ! $type_id || ! $taxonomy || ! is_array( $type_metas ) ) { 3171 return false; 3172 } 3173 3174 foreach ( $type_metas as $meta_key => $meta_value ) { 3175 if ( ! registered_meta_key_exists( 'term', $meta_key, $taxonomy ) ) { 3176 continue; 3177 } 3178 3179 update_term_meta( $type_id, $meta_key, $meta_value ); 3180 } 3181 3182 return true; 3183 } 3184 3185 /** 3186 * Get types for a given BP Taxonomy. 3187 * 3188 * @since 7.0.0 3189 * 3190 * @param string $taxonomy The taxonomy to transform terms in types for. 3191 * @param array $types Existing types to merge with the types found into the database. 3192 * For instance this function is used internally to merge Group/Member 3193 * types registered using code with the ones created by the administrator 3194 * from the Group/Member types Administration screen. If not provided, only 3195 * Types created by the administrator will be returned. 3196 * Optional. 3197 * @return array The types of the given taxonomy. 3198 */ 3199 function bp_get_taxonomy_types( $taxonomy = '', $types = array() ) { 3200 if ( ! $taxonomy ) { 3201 return $types; 3202 } 3203 3204 $db_types = wp_cache_get( $taxonomy, 'bp_object_terms' ); 3205 3206 if ( ! $db_types ) { 3207 $terms = bp_get_terms( 3208 array( 3209 'taxonomy' => $taxonomy, 3210 ) 3211 ); 3212 3213 if ( ! is_array( $terms ) || ! $terms ) { 3214 return $types; 3215 } 3216 3217 $type_metadata = array_keys( get_registered_meta_keys( 'term', $taxonomy ) ); 3218 3219 foreach ( $terms as $term ) { 3220 $type_name = $term->name; 3221 $db_types[ $type_name ] = new stdClass(); 3222 $db_types[ $type_name ]->db_id = $term->term_id; 3223 $db_types[ $type_name ]->labels = array(); 3224 $db_types[ $type_name ]->name = $type_name; 3225 3226 if ( $type_metadata ) { 3227 foreach ( $type_metadata as $meta_key ) { 3228 $type_key = str_replace( 'bp_type_', '', $meta_key ); 3229 if ( in_array( $type_key, array( 'name', 'singular_name' ), true ) ) { 3230 $db_types[ $type_name ]->labels[ $type_key ] = get_term_meta( $term->term_id, $meta_key, true ); 3231 } else { 3232 $db_types[ $type_name ]->{$type_key} = get_term_meta( $term->term_id, $meta_key, true ); 3233 } 3234 } 3235 3236 if ( ! empty( $db_types[ $type_name ]->has_directory ) && empty( $db_types[ $type_name ]->directory_slug ) ) { 3237 $db_types[ $type_name ]->directory_slug = $term->slug; 3238 } 3239 } 3240 } 3241 3242 wp_cache_set( $taxonomy, $db_types, 'bp_object_terms' ); 3243 } 3244 3245 if ( is_array( $db_types ) ) { 3246 foreach ( $db_types as $db_type_name => $db_type ) { 3247 // Override props of registered by code types if customized by the admun user. 3248 if ( isset( $types[ $db_type_name ] ) && isset( $types[ $db_type_name ]->code ) && $types[ $db_type_name ]->code ) { 3249 // Merge Labels. 3250 if ( $db_type->labels ) { 3251 foreach ( $db_type->labels as $key_label => $value_label ) { 3252 if ( '' !== $value_label ) { 3253 $types[ $db_type_name ]->labels[ $key_label ] = $value_label; 3254 } 3255 } 3256 } 3257 3258 // Merge other properties. 3259 foreach ( get_object_vars( $types[ $db_type_name ] ) as $key_prop => $value_prop ) { 3260 if ( 'labels' === $key_prop || 'name' === $key_prop ) { 3261 continue; 3262 } 3263 3264 if ( isset( $db_type->{$key_prop} ) && '' !== $db_type->{$key_prop} ) { 3265 $types[ $db_type_name ]->{$key_prop} = $db_type->{$key_prop}; 3266 } 3267 } 3268 3269 unset( $db_types[ $db_type_name ] ); 3270 } 3271 } 3272 } 3273 3274 return array_merge( $types, (array) $db_types ); 3275 } 3276 3277 /** Email *****************************************************************/ 3278 3279 /** 3280 * Get an BP_Email object for the specified email type. 3281 * 3282 * This function pre-populates the object with the subject, content, and template from the appropriate 3283 * email post type item. It does not replace placeholder tokens in the content with real values. 3284 * 3285 * @since 2.5.0 3286 * 3287 * @param string $email_type Unique identifier for a particular type of email. 3288 * @return BP_Email|WP_Error BP_Email object, or WP_Error if there was a problem. 3289 */ 3290 function bp_get_email( $email_type ) { 3291 $switched = false; 3292 3293 // Switch to the root blog, where the email posts live. 3294 if ( ! bp_is_root_blog() ) { 3295 switch_to_blog( bp_get_root_blog_id() ); 3296 $switched = true; 3297 } 3298 3299 $args = array( 3300 'no_found_rows' => true, 3301 'numberposts' => 1, 3302 'post_status' => 'publish', 3303 'post_type' => bp_get_email_post_type(), 3304 'suppress_filters' => false, 3305 3306 'tax_query' => array( 3307 array( 3308 'field' => 'slug', 3309 'taxonomy' => bp_get_email_tax_type(), 3310 'terms' => $email_type, 3311 ) 3312 ), 3313 ); 3314 3315 /** 3316 * Filters arguments used to find an email post type object. 3317 * 3318 * @since 2.5.0 3319 * 3320 * @param array $args Arguments for get_posts() used to fetch a post object. 3321 * @param string $email_type Unique identifier for a particular type of email. 3322 */ 3323 $args = apply_filters( 'bp_get_email_args', $args, $email_type ); 3324 $post = get_posts( $args ); 3325 if ( ! $post ) { 3326 if ( $switched ) { 3327 restore_current_blog(); 3328 } 3329 3330 return new WP_Error( 'missing_email', __FUNCTION__, array( $email_type, $args ) ); 3331 } 3332 3333 /** 3334 * Filters arguments used to create the BP_Email object. 3335 * 3336 * @since 2.5.0 3337 * 3338 * @param WP_Post $post Post object containing the contents of the email. 3339 * @param string $email_type Unique identifier for a particular type of email. 3340 * @param array $args Arguments used with get_posts() to fetch a post object. 3341 * @param WP_Post $post All posts retrieved by get_posts( $args ). May only contain $post. 3342 */ 3343 $post = apply_filters( 'bp_get_email_post', $post[0], $email_type, $args, $post ); 3344 $email = new BP_Email( $email_type ); 3345 3346 3347 /* 3348 * Set some email properties for convenience. 3349 */ 3350 3351 // Post object (sets subject, content, template). 3352 $email->set_post_object( $post ); 3353 3354 /** 3355 * Filters the BP_Email object returned by bp_get_email(). 3356 * 3357 * @since 2.5.0 3358 * 3359 * @param BP_Email $email An object representing a single email, ready for mailing. 3360 * @param string $email_type Unique identifier for a particular type of email. 3361 * @param array $args Arguments used with get_posts() to fetch a post object. 3362 * @param WP_Post $post All posts retrieved by get_posts( $args ). May only contain $post. 3363 */ 3364 $retval = apply_filters( 'bp_get_email', $email, $email_type, $args, $post ); 3365 3366 if ( $switched ) { 3367 restore_current_blog(); 3368 } 3369 3370 return $retval; 3371 } 3372 3373 /** 3374 * Send email, similar to WordPress' wp_mail(). 3375 * 3376 * A true return value does not automatically mean that the user received the 3377 * email successfully. It just only means that the method used was able to 3378 * process the request without any errors. 3379 * 3380 * @since 2.5.0 3381 * 3382 * @param string $email_type Type of email being sent. 3383 * @param string|array|int|WP_User $to Either a email address, user ID, WP_User object, 3384 * or an array containing the address and name. 3385 * @param array $args { 3386 * Optional. Array of extra parameters. 3387 * 3388 * @type array $tokens Optional. Associative arrays of string replacements for the email. 3389 * } 3390 * @return bool|WP_Error True if the email was sent successfully. Otherwise, a WP_Error object 3391 * describing why the email failed to send. The contents will vary based 3392 * on the email delivery class you are using. 3393 */ 3394 function bp_send_email( $email_type, $to, $args = array() ) { 3395 static $is_default_wpmail = null; 3396 static $wp_html_emails = null; 3397 3398 // Has wp_mail() been filtered to send HTML emails? 3399 if ( is_null( $wp_html_emails ) ) { 3400 /** This filter is documented in wp-includes/pluggable.php */ 3401 $wp_html_emails = apply_filters( 'wp_mail_content_type', 'text/plain' ) === 'text/html'; 3402 } 3403 3404 // Since wp_mail() is a pluggable function, has it been re-defined by another plugin? 3405 if ( is_null( $is_default_wpmail ) ) { 3406 try { 3407 $mirror = new ReflectionFunction( 'wp_mail' ); 3408 $is_default_wpmail = substr( $mirror->getFileName(), -strlen( 'pluggable.php' ) ) === 'pluggable.php'; 3409 } catch ( Exception $e ) { 3410 $is_default_wpmail = true; 3411 } 3412 } 3413 3414 $args = bp_parse_args( $args, array( 3415 'tokens' => array(), 3416 ), 'send_email' ); 3417 3418 3419 /* 3420 * Build the email. 3421 */ 3422 3423 $email = bp_get_email( $email_type ); 3424 if ( is_wp_error( $email ) ) { 3425 return $email; 3426 } 3427 3428 // From, subject, content are set automatically. 3429 if ( 'settings-verify-email-change' === $email_type && isset( $args['tokens']['displayname'] ) ) { 3430 $email->set_to( $to, $args['tokens']['displayname'] ); 3431 } else { 3432 $email->set_to( $to ); 3433 } 3434 3435 $email->set_tokens( $args['tokens'] ); 3436 3437 /** 3438 * Gives access to an email before it is sent. 3439 * 3440 * @since 2.8.0 3441 * 3442 * @param BP_Email $email The email (object) about to be sent. 3443 * @param string $email_type Type of email being sent. 3444 * @param string|array|int|WP_User $to Either a email address, user ID, WP_User object, 3445 * or an array containing the address and name. 3446 * @param array $args { 3447 * Optional. Array of extra parameters. 3448 * 3449 * @type array $tokens Optional. Associative arrays of string replacements for the email. 3450 * } 3451 */ 3452 do_action_ref_array( 'bp_send_email', array( &$email, $email_type, $to, $args ) ); 3453 3454 $status = $email->validate(); 3455 if ( is_wp_error( $status ) ) { 3456 return $status; 3457 } 3458 3459 /** 3460 * Filter this to skip BP's email handling and instead send everything to wp_mail(). 3461 * 3462 * This is done if wp_mail_content_type() has been configured for HTML, 3463 * or if wp_mail() has been redeclared (it's a pluggable function). 3464 * 3465 * @since 2.5.0 3466 * 3467 * @param bool $use_wp_mail Whether to fallback to the regular wp_mail() function or not. 3468 */ 3469 $must_use_wpmail = apply_filters( 'bp_email_use_wp_mail', $wp_html_emails || ! $is_default_wpmail ); 3470 3471 if ( $must_use_wpmail ) { 3472 $to = $email->get( 'to' ); 3473 3474 return wp_mail( 3475 array_shift( $to )->get_address(), 3476 $email->get( 'subject', 'replace-tokens' ), 3477 $email->get( 'content_plaintext', 'replace-tokens' ) 3478 ); 3479 } 3480 3481 3482 /* 3483 * Send the email. 3484 */ 3485 3486 /** 3487 * Filter the email delivery class. 3488 * 3489 * Defaults to BP_PHPMailer, which as you can guess, implements PHPMailer. 3490 * 3491 * @since 2.5.0 3492 * 3493 * @param string $deliver_class The email delivery class name. 3494 * @param string $email_type Type of email being sent. 3495 * @param array|string $to Array or comma-separated list of email addresses to the email to. 3496 * @param array $args { 3497 * Optional. Array of extra parameters. 3498 * 3499 * @type array $tokens Optional. Associative arrays of string replacements for the email. 3500 * } 3501 */ 3502 $delivery_class = apply_filters( 'bp_send_email_delivery_class', 'BP_PHPMailer', $email_type, $to, $args ); 3503 if ( ! class_exists( $delivery_class ) ) { 3504 return new WP_Error( 'missing_class', 'No class found by that name', $delivery_class ); 3505 } 3506 3507 $delivery = new $delivery_class(); 3508 $status = $delivery->bp_email( $email ); 3509 3510 if ( is_wp_error( $status ) ) { 3511 3512 /** 3513 * Fires after BuddyPress has tried - and failed - to send an email. 3514 * 3515 * @since 2.5.0 3516 * 3517 * @param WP_Error $status A WP_Error object describing why the email failed to send. The contents 3518 * will vary based on the email delivery class you are using. 3519 * @param BP_Email $email The email we tried to send. 3520 */ 3521 do_action( 'bp_send_email_failure', $status, $email ); 3522 3523 } else { 3524 3525 /** 3526 * Fires after BuddyPress has successfully sent an email. 3527 * 3528 * @since 2.5.0 3529 * 3530 * @param bool $status True if the email was sent successfully. 3531 * @param BP_Email $email The email sent. 3532 */ 3533 do_action( 'bp_send_email_success', $status, $email ); 3534 } 3535 3536 return $status; 3537 } 3538 3539 /** 3540 * Return email appearance settings. 3541 * 3542 * @since 2.5.0 3543 * @since 3.0.0 Added "direction" parameter for LTR/RTL email support, and 3544 * "link_text_color" to override that in the email body. 3545 * 3546 * @return array 3547 */ 3548 function bp_email_get_appearance_settings() { 3549 $footer_text = array( 3550 sprintf( 3551 /* translators: 1. Copyright year, 2. Site name */ 3552 _x( '© %1$s %2$s', 'copyright text for email footers', 'buddypress' ), 3553 date_i18n( 'Y' ), 3554 bp_get_option( 'blogname' ) 3555 ) 3556 ); 3557 3558 if ( bp_is_running_wp( '4.9.6' ) ) { 3559 $privacy_policy_url = get_privacy_policy_url(); 3560 if ( $privacy_policy_url ) { 3561 $footer_text[] = sprintf( 3562 '<a href="%s">%s</a>', 3563 esc_url( $privacy_policy_url ), 3564 esc_html__( 'Privacy Policy', 'buddypress' ) 3565 ); 3566 } 3567 } 3568 3569 $default_args = array( 3570 'body_bg' => '#FFFFFF', 3571 'body_text_color' => '#555555', 3572 'body_text_size' => 15, 3573 'email_bg' => '#F7F3F0', 3574 'footer_bg' => '#F7F3F0', 3575 'footer_text_color' => '#525252', 3576 'footer_text_size' => 12, 3577 'header_bg' => '#F7F3F0', 3578 'highlight_color' => '#D84800', 3579 'header_text_color' => '#000000', 3580 'header_text_size' => 30, 3581 'direction' => is_rtl() ? 'right' : 'left', 3582 3583 'footer_text' => implode( ' · ', $footer_text ), 3584 ); 3585 3586 $options = bp_parse_args( 3587 bp_get_option( 'bp_email_options', array() ), 3588 $default_args, 3589 'email_appearance_settings' 3590 ); 3591 3592 // Link text colour defaults to the highlight colour. 3593 if ( ! isset( $options['link_text_color'] ) ) { 3594 $options['link_text_color'] = $options['highlight_color']; 3595 } 3596 3597 return $options; 3598 } 3599 3600 /** 3601 * Get the paths to possible templates for the specified email object. 3602 * 3603 * @since 2.5.0 3604 * 3605 * @param WP_Post $object Post to get email template for. 3606 * @return array 3607 */ 3608 function bp_email_get_template( WP_Post $object ) { 3609 $single = "single-{$object->post_type}"; 3610 3611 /** 3612 * Filter the possible template paths for the specified email object. 3613 * 3614 * @since 2.5.0 3615 * 3616 * @param array $value Array of possible template paths. 3617 * @param WP_Post $object WP_Post object. 3618 */ 3619 return apply_filters( 'bp_email_get_template', array( 3620 "assets/emails/{$single}-{$object->post_name}.php", 3621 "{$single}-{$object->post_name}.php", 3622 "{$single}.php", 3623 "assets/emails/{$single}.php", 3624 ), $object ); 3625 } 3626 3627 /** 3628 * Replace all tokens in the input text with appropriate values. 3629 * 3630 * Intended for use with the email system introduced in BuddyPress 2.5.0. 3631 * 3632 * @since 2.5.0 3633 * 3634 * @param string $text Text to replace tokens in. 3635 * @param array $tokens Token names and replacement values for the $text. 3636 * @return string 3637 */ 3638 function bp_core_replace_tokens_in_text( $text, $tokens ) { 3639 $unescaped = array(); 3640 $escaped = array(); 3641 3642 foreach ( $tokens as $token => $value ) { 3643 if ( ! is_string( $value ) && is_callable( $value ) ) { 3644 $value = call_user_func( $value ); 3645 } 3646 3647 // Tokens could be objects or arrays. 3648 if ( ! is_scalar( $value ) ) { 3649 continue; 3650 } 3651 3652 $unescaped[ '{{{' . $token . '}}}' ] = $value; 3653 $escaped[ '{{' . $token . '}}' ] = esc_html( $value ); 3654 } 3655 3656 $text = strtr( $text, $unescaped ); // Do first. 3657 $text = strtr( $text, $escaped ); 3658 3659 /** 3660 * Filters text that has had tokens replaced. 3661 * 3662 * @since 2.5.0 3663 * 3664 * @param string $text 3665 * @param array $tokens Token names and replacement values for the $text. 3666 */ 3667 return apply_filters( 'bp_core_replace_tokens_in_text', $text, $tokens ); 3668 } 3669 3670 /** 3671 * Get a list of emails for populating the email post type. 3672 * 3673 * @since 2.5.1 3674 * 3675 * @return array 3676 */ 3677 function bp_email_get_schema() { 3678 3679 /** 3680 * Filters the list of `bp_email_get_schema()` allowing anyone to add/remove emails. 3681 * 3682 * @since 7.0.0 3683 * 3684 * @param array $emails The array of emails schema. 3685 */ 3686 return (array) apply_filters( 'bp_email_get_schema', array( 3687 'activity-comment' => array( 3688 /* translators: do not remove {} brackets or translate its contents. */ 3689 'post_title' => __( '[{{{site.name}}}] {{poster.name}} replied to one of your updates', 'buddypress' ), 3690 /* translators: do not remove {} brackets or translate its contents. */ 3691 'post_content' => __( "{{poster.name}} replied to one of your updates:\n\n<blockquote>"{{usermessage}}"</blockquote>\n\n<a href=\"{{{thread.url}}}\">Go to the discussion</a> to reply or catch up on the conversation.", 'buddypress' ), 3692 /* translators: do not remove {} brackets or translate its contents. */ 3693 'post_excerpt' => __( "{{poster.name}} replied to one of your updates:\n\n\"{{usermessage}}\"\n\nGo to the discussion to reply or catch up on the conversation: {{{thread.url}}}", 'buddypress' ), 3694 ), 3695 'activity-comment-author' => array( 3696 /* translators: do not remove {} brackets or translate its contents. */ 3697 'post_title' => __( '[{{{site.name}}}] {{poster.name}} replied to one of your comments', 'buddypress' ), 3698 /* translators: do not remove {} brackets or translate its contents. */ 3699 'post_content' => __( "{{poster.name}} replied to one of your comments:\n\n<blockquote>"{{usermessage}}"</blockquote>\n\n<a href=\"{{{thread.url}}}\">Go to the discussion</a> to reply or catch up on the conversation.", 'buddypress' ), 3700 /* translators: do not remove {} brackets or translate its contents. */ 3701 'post_excerpt' => __( "{{poster.name}} replied to one of your comments:\n\n\"{{usermessage}}\"\n\nGo to the discussion to reply or catch up on the conversation: {{{thread.url}}}", 'buddypress' ), 3702 ), 3703 'activity-at-message' => array( 3704 /* translators: do not remove {} brackets or translate its contents. */ 3705 'post_title' => __( '[{{{site.name}}}] {{poster.name}} mentioned you in a status update', 'buddypress' ), 3706 /* translators: do not remove {} brackets or translate its contents. */ 3707 'post_content' => __( "{{poster.name}} mentioned you in a status update:\n\n<blockquote>"{{usermessage}}"</blockquote>\n\n<a href=\"{{{mentioned.url}}}\">Go to the discussion</a> to reply or catch up on the conversation.", 'buddypress' ), 3708 /* translators: do not remove {} brackets or translate its contents. */ 3709 'post_excerpt' => __( "{{poster.name}} mentioned you in a status update:\n\n\"{{usermessage}}\"\n\nGo to the discussion to reply or catch up on the conversation: {{{mentioned.url}}}", 'buddypress' ), 3710 ), 3711 'groups-at-message' => array( 3712 /* translators: do not remove {} brackets or translate its contents. */ 3713 'post_title' => __( '[{{{site.name}}}] {{poster.name}} mentioned you in an update', 'buddypress' ), 3714 /* translators: do not remove {} brackets or translate its contents. */ 3715 'post_content' => __( "{{poster.name}} mentioned you in the group \"{{group.name}}\":\n\n<blockquote>"{{usermessage}}"</blockquote>\n\n<a href=\"{{{mentioned.url}}}\">Go to the discussion</a> to reply or catch up on the conversation.", 'buddypress' ), 3716 /* translators: do not remove {} brackets or translate its contents. */ 3717 'post_excerpt' => __( "{{poster.name}} mentioned you in the group \"{{group.name}}\":\n\n\"{{usermessage}}\"\n\nGo to the discussion to reply or catch up on the conversation: {{{mentioned.url}}}", 'buddypress' ), 3718 ), 3719 'core-user-registration' => array( 3720 /* translators: do not remove {} brackets or translate its contents. */ 3721 'post_title' => __( '[{{{site.name}}}] Activate your account', 'buddypress' ), 3722 /* translators: do not remove {} brackets or translate its contents. */ 3723 'post_content' => __( "Thanks for registering!\n\nTo complete the activation of your account, go to the following link and click on the <strong>Activate</strong> button:\n<a href=\"{{{activate.url}}}\">{{{activate.url}}}</a>\n\nIf the 'Activation Key' field is empty, copy and paste the following into the field - {{key}}", 'buddypress' ), 3724 /* translators: do not remove {} brackets or translate its contents. */ 3725 'post_excerpt' => __( "Thanks for registering!\n\nTo complete the activation of your account, go to the following link and click on the 'Activate' button: {{{activate.url}}}\n\nIf the 'Activation Key' field is empty, copy and paste the following into the field - {{key}}", 'buddypress' ) 3726 ), 3727 'core-user-registration-with-blog' => array( 3728 /* translators: do not remove {} brackets or translate its contents. */ 3729 'post_title' => __( '[{{{site.name}}}] Activate {{{user-site.url}}}', 'buddypress' ), 3730 /* translators: do not remove {} brackets or translate its contents. */ 3731 'post_content' => __( "Thanks for registering!\n\nTo complete the activation of your account and site, go to the following link: <a href=\"{{{activate-site.url}}}\">{{{activate-site.url}}}</a>.\n\nAfter you activate, you can visit your site at <a href=\"{{{user-site.url}}}\">{{{user-site.url}}}</a>.", 'buddypress' ), 3732 /* translators: do not remove {} brackets or translate its contents. */ 3733 'post_excerpt' => __( "Thanks for registering!\n\nTo complete the activation of your account and site, go to the following link: {{{activate-site.url}}}\n\nAfter you activate, you can visit your site at {{{user-site.url}}}.", 'buddypress' ), 3734 'args' => array( 3735 'multisite' => true, 3736 ), 3737 ), 3738 'friends-request' => array( 3739 /* translators: do not remove {} brackets or translate its contents. */ 3740 'post_title' => __( '[{{{site.name}}}] New friendship request from {{initiator.name}}', 'buddypress' ), 3741 /* translators: do not remove {} brackets or translate its contents. */ 3742 'post_content' => __( "<a href=\"{{{initiator.url}}}\">{{initiator.name}}</a> wants to add you as a friend.\n\nTo accept this request and manage all of your pending requests, visit: <a href=\"{{{friend-requests.url}}}\">{{{friend-requests.url}}}</a>", 'buddypress' ), 3743 /* translators: do not remove {} brackets or translate its contents. */ 3744 'post_excerpt' => __( "{{initiator.name}} wants to add you as a friend.\n\nTo accept this request and manage all of your pending requests, visit: {{{friend-requests.url}}}\n\nTo view {{initiator.name}}'s profile, visit: {{{initiator.url}}}", 'buddypress' ), 3745 ), 3746 'friends-request-accepted' => array( 3747 /* translators: do not remove {} brackets or translate its contents. */ 3748 'post_title' => __( '[{{{site.name}}}] {{friend.name}} accepted your friendship request', 'buddypress' ), 3749 /* translators: do not remove {} brackets or translate its contents. */ 3750 'post_content' => __( "<a href=\"{{{friendship.url}}}\">{{friend.name}}</a> accepted your friend request.", 'buddypress' ), 3751 /* translators: do not remove {} brackets or translate its contents. */ 3752 'post_excerpt' => __( "{{friend.name}} accepted your friend request.\n\nTo learn more about them, visit their profile: {{{friendship.url}}}", 'buddypress' ), 3753 ), 3754 'groups-details-updated' => array( 3755 /* translators: do not remove {} brackets or translate its contents. */ 3756 'post_title' => __( '[{{{site.name}}}] Group details updated', 'buddypress' ), 3757 /* translators: do not remove {} brackets or translate its contents. */ 3758 'post_content' => __( "Group details for the group "<a href=\"{{{group.url}}}\">{{group.name}}</a>" were updated:\n<blockquote>{{changed_text}}</blockquote>", 'buddypress' ), 3759 /* translators: do not remove {} brackets or translate its contents. */ 3760 'post_excerpt' => __( "Group details for the group \"{{group.name}}\" were updated:\n\n{{changed_text}}\n\nTo view the group, visit: {{{group.url}}}", 'buddypress' ), 3761 ), 3762 'groups-invitation' => array( 3763 /* translators: do not remove {} brackets or translate its contents. */ 3764 'post_title' => __( '[{{{site.name}}}] You have an invitation to the group: "{{group.name}}"', 'buddypress' ), 3765 /* translators: do not remove {} brackets or translate its contents. */ 3766 'post_content' => __( "<a href=\"{{{inviter.url}}}\">{{inviter.name}}</a> has invited you to join the group: "{{group.name}}".\n{{invite.message}}\n<a href=\"{{{invites.url}}}\">Go here to accept your invitation</a> or <a href=\"{{{group.url}}}\">visit the group</a> to learn more.", 'buddypress' ), 3767 /* translators: do not remove {} brackets or translate its contents. */ 3768 'post_excerpt' => __( "{{inviter.name}} has invited you to join the group: \"{{group.name}}\".\n\nTo accept your invitation, visit: {{{invites.url}}}\n\nTo learn more about the group, visit: {{{group.url}}}.\nTo view {{inviter.name}}'s profile, visit: {{{inviter.url}}}", 'buddypress' ), 3769 ), 3770 'groups-member-promoted' => array( 3771 /* translators: do not remove {} brackets or translate its contents. */ 3772 'post_title' => __( '[{{{site.name}}}] You have been promoted in the group: "{{group.name}}"', 'buddypress' ), 3773 /* translators: do not remove {} brackets or translate its contents. */ 3774 'post_content' => __( "You have been promoted to <b>{{promoted_to}}</b> in the group "<a href=\"{{{group.url}}}\">{{group.name}}</a>".", 'buddypress' ), 3775 /* translators: do not remove {} brackets or translate its contents. */ 3776 'post_excerpt' => __( "You have been promoted to {{promoted_to}} in the group: \"{{group.name}}\".\n\nTo visit the group, go to: {{{group.url}}}", 'buddypress' ), 3777 ), 3778 'groups-membership-request' => array( 3779 /* translators: do not remove {} brackets or translate its contents. */ 3780 'post_title' => __( '[{{{site.name}}}] Membership request for group: {{group.name}}', 'buddypress' ), 3781 /* translators: do not remove {} brackets or translate its contents. */ 3782 'post_content' => __( "<a href=\"{{{profile.url}}}\">{{requesting-user.name}}</a> wants to join the group "{{group.name}}".\n {{request.message}}\n As you are an administrator of this group, you must either accept or reject the membership request.\n\n<a href=\"{{{group-requests.url}}}\">Go here to manage this</a> and all other pending requests.", 'buddypress' ), 3783 /* translators: do not remove {} brackets or translate its contents. */ 3784 'post_excerpt' => __( "{{requesting-user.name}} wants to join the group \"{{group.name}}\". As you are the administrator of this group, you must either accept or reject the membership request.\n\nTo manage this and all other pending requests, visit: {{{group-requests.url}}}\n\nTo view {{requesting-user.name}}'s profile, visit: {{{profile.url}}}", 'buddypress' ), 3785 ), 3786 'messages-unread' => array( 3787 /* translators: do not remove {} brackets or translate its contents. */ 3788 'post_title' => __( '[{{{site.name}}}] New message from {{sender.name}}', 'buddypress' ), 3789 /* translators: do not remove {} brackets or translate its contents. */ 3790 'post_content' => __( "{{sender.name}} sent you a new message: "{{usersubject}}"\n\n<blockquote>"{{usermessage}}"</blockquote>\n\n<a href=\"{{{message.url}}}\">Go to the discussion</a> to reply or catch up on the conversation.", 'buddypress' ), 3791 /* translators: do not remove {} brackets or translate its contents. */ 3792 'post_excerpt' => __( "{{sender.name}} sent you a new message: \"{{usersubject}}\"\n\n\"{{usermessage}}\"\n\nGo to the discussion to reply or catch up on the conversation: {{{message.url}}}", 'buddypress' ), 3793 ), 3794 'settings-verify-email-change' => array( 3795 /* translators: do not remove {} brackets or translate its contents. */ 3796 'post_title' => __( '[{{{site.name}}}] Verify your new email address', 'buddypress' ), 3797 /* translators: do not remove {} brackets or translate its contents. */ 3798 'post_content' => __( "You recently changed the email address associated with your account on {{site.name}} to {{user.email}}. If this is correct, <a href=\"{{{verify.url}}}\">go here to confirm the change</a>.\n\nOtherwise, you can safely ignore and delete this email if you have changed your mind, or if you think you have received this email in error.", 'buddypress' ), 3799 /* translators: do not remove {} brackets or translate its contents. */ 3800 'post_excerpt' => __( "You recently changed the email address associated with your account on {{site.name}} to {{user.email}}. If this is correct, go to the following link to confirm the change: {{{verify.url}}}\n\nOtherwise, you can safely ignore and delete this email if you have changed your mind, or if you think you have received this email in error.", 'buddypress' ), 3801 ), 3802 'groups-membership-request-accepted' => array( 3803 /* translators: do not remove {} brackets or translate its contents. */ 3804 'post_title' => __( '[{{{site.name}}}] Membership request for group "{{group.name}}" accepted', 'buddypress' ), 3805 /* translators: do not remove {} brackets or translate its contents. */ 3806 'post_content' => __( "Your membership request for the group "<a href=\"{{{group.url}}}\">{{group.name}}</a>" has been accepted.", 'buddypress' ), 3807 /* translators: do not remove {} brackets or translate its contents. */ 3808 'post_excerpt' => __( "Your membership request for the group \"{{group.name}}\" has been accepted.\n\nTo view the group, visit: {{{group.url}}}", 'buddypress' ), 3809 ), 3810 'groups-membership-request-rejected' => array( 3811 /* translators: do not remove {} brackets or translate its contents. */ 3812 'post_title' => __( '[{{{site.name}}}] Membership request for group "{{group.name}}" rejected', 'buddypress' ), 3813 /* translators: do not remove {} brackets or translate its contents. */ 3814 'post_content' => __( "Your membership request for the group "<a href=\"{{{group.url}}}\">{{group.name}}</a>" has been rejected.", 'buddypress' ), 3815 /* translators: do not remove {} brackets or translate its contents. */ 3816 'post_excerpt' => __( "Your membership request for the group \"{{group.name}}\" has been rejected.\n\nTo request membership again, visit: {{{group.url}}}", 'buddypress' ), 3817 ), 3818 ) ); 3819 } 3820 3821 /** 3822 * Get a list of emails for populating email type taxonomy terms. 3823 * 3824 * @since 2.5.1 3825 * @since 2.7.0 $field argument added. 3826 * 3827 * @param string $field Optional; defaults to "description" for backwards compatibility. Other values: "all". 3828 * @return array { 3829 * The array of email types and their schema. 3830 * 3831 * @type string $description The description of the action which causes this to trigger. 3832 * @type array $unsubscribe { 3833 * Replacing this with false indicates that a user cannot unsubscribe from this type. 3834 * 3835 * @type string $meta_key The meta_key used to toggle the email setting for this notification. 3836 * @type string $message The message shown when the user has successfully unsubscribed. 3837 * } 3838 */ 3839 function bp_email_get_type_schema( $field = 'description' ) { 3840 $activity_comment = array( 3841 'description' => __( 'A member has replied to an activity update that the recipient posted.', 'buddypress' ), 3842 'unsubscribe' => array( 3843 'meta_key' => 'notification_activity_new_reply', 3844 'message' => __( 'You will no longer receive emails when someone replies to an update or comment you posted.', 'buddypress' ), 3845 ), 3846 ); 3847 3848 $activity_comment_author = array( 3849 'description' => __( 'A member has replied to a comment on an activity update that the recipient posted.', 'buddypress' ), 3850 'unsubscribe' => array( 3851 'meta_key' => 'notification_activity_new_reply', 3852 'message' => __( 'You will no longer receive emails when someone replies to an update or comment you posted.', 'buddypress' ), 3853 ), 3854 ); 3855 3856 $activity_at_message = array( 3857 'description' => __( 'Recipient was mentioned in an activity update.', 'buddypress' ), 3858 'unsubscribe' => array( 3859 'meta_key' => 'notification_activity_new_mention', 3860 'message' => __( 'You will no longer receive emails when someone mentions you in an update.', 'buddypress' ), 3861 ), 3862 ); 3863 3864 $groups_at_message = array( 3865 'description' => __( 'Recipient was mentioned in a group activity update.', 'buddypress' ), 3866 'unsubscribe' => array( 3867 'meta_key' => 'notification_activity_new_mention', 3868 'message' => __( 'You will no longer receive emails when someone mentions you in an update.', 'buddypress' ), 3869 ), 3870 ); 3871 3872 $core_user_registration = array( 3873 'description' => __( 'Recipient has registered for an account.', 'buddypress' ), 3874 'unsubscribe' => false, 3875 ); 3876 3877 $core_user_registration_with_blog = array( 3878 'description' => __( 'Recipient has registered for an account and site.', 'buddypress' ), 3879 'unsubscribe' => false, 3880 ); 3881 3882 $friends_request = array( 3883 'description' => __( 'A member has sent a friend request to the recipient.', 'buddypress' ), 3884 'unsubscribe' => array( 3885 'meta_key' => 'notification_friends_friendship_request', 3886 'message' => __( 'You will no longer receive emails when someone sends you a friend request.', 'buddypress' ), 3887 ), 3888 ); 3889 3890 $friends_request_accepted = array( 3891 'description' => __( 'Recipient has had a friend request accepted by a member.', 'buddypress' ), 3892 'unsubscribe' => array( 3893 'meta_key' => 'notification_friends_friendship_accepted', 3894 'message' => __( 'You will no longer receive emails when someone accepts your friendship request.', 'buddypress' ), 3895 ), 3896 ); 3897 3898 $groups_details_updated = array( 3899 'description' => __( "A group's details were updated.", 'buddypress' ), 3900 'unsubscribe' => array( 3901 'meta_key' => 'notification_groups_group_updated', 3902 'message' => __( 'You will no longer receive emails when one of your groups is updated.', 'buddypress' ), 3903 ), 3904 ); 3905 3906 $groups_invitation = array( 3907 'description' => __( 'A member has sent a group invitation to the recipient.', 'buddypress' ), 3908 'unsubscribe' => array( 3909 'meta_key' => 'notification_groups_invite', 3910 'message' => __( 'You will no longer receive emails when you are invited to join a group.', 'buddypress' ), 3911 ), 3912 ); 3913 3914 $groups_member_promoted = array( 3915 'description' => __( "Recipient's status within a group has changed.", 'buddypress' ), 3916 'unsubscribe' => array( 3917 'meta_key' => 'notification_groups_admin_promotion', 3918 'message' => __( 'You will no longer receive emails when you have been promoted in a group.', 'buddypress' ), 3919 ), 3920 ); 3921 3922 $groups_membership_request = array( 3923 'description' => __( 'A member has requested permission to join a group.', 'buddypress' ), 3924 'unsubscribe' => array( 3925 'meta_key' => 'notification_groups_membership_request', 3926 'message' => __( 'You will no longer receive emails when someone requests to be a member of your group.', 'buddypress' ), 3927 ), 3928 ); 3929 3930 $messages_unread = array( 3931 'description' => __( 'Recipient has received a private message.', 'buddypress' ), 3932 'unsubscribe' => array( 3933 'meta_key' => 'notification_messages_new_message', 3934 'message' => __( 'You will no longer receive emails when someone sends you a message.', 'buddypress' ), 3935 ), 3936 ); 3937 3938 $settings_verify_email_change = array( 3939 'description' => __( 'Recipient has changed their email address.', 'buddypress' ), 3940 'unsubscribe' => false, 3941 ); 3942 3943 $groups_membership_request_accepted = array( 3944 'description' => __( 'Recipient had requested to join a group, which was accepted.', 'buddypress' ), 3945 'unsubscribe' => array( 3946 'meta_key' => 'notification_membership_request_completed', 3947 'message' => __( 'You will no longer receive emails when your request to join a group has been accepted or denied.', 'buddypress' ), 3948 ), 3949 ); 3950 3951 $groups_membership_request_rejected = array( 3952 'description' => __( 'Recipient had requested to join a group, which was rejected.', 'buddypress' ), 3953 'unsubscribe' => array( 3954 'meta_key' => 'notification_membership_request_completed', 3955 'message' => __( 'You will no longer receive emails when your request to join a group has been accepted or denied.', 'buddypress' ), 3956 ), 3957 ); 3958 3959 $types = array( 3960 'activity-comment' => $activity_comment, 3961 'activity-comment-author' => $activity_comment_author, 3962 'activity-at-message' => $activity_at_message, 3963 'groups-at-message' => $groups_at_message, 3964 'core-user-registration' => $core_user_registration, 3965 'core-user-registration-with-blog' => $core_user_registration_with_blog, 3966 'friends-request' => $friends_request, 3967 'friends-request-accepted' => $friends_request_accepted, 3968 'groups-details-updated' => $groups_details_updated, 3969 'groups-invitation' => $groups_invitation, 3970 'groups-member-promoted' => $groups_member_promoted, 3971 'groups-membership-request' => $groups_membership_request, 3972 'messages-unread' => $messages_unread, 3973 'settings-verify-email-change' => $settings_verify_email_change, 3974 'groups-membership-request-accepted' => $groups_membership_request_accepted, 3975 'groups-membership-request-rejected' => $groups_membership_request_rejected, 3976 ); 3977 3978 if ( $field !== 'all' ) { 3979 return wp_list_pluck( $types, $field ); 3980 } else { 3981 return $types; 3982 } 3983 } 3984 3985 /** 3986 * Handles unsubscribing user from notification emails. 3987 * 3988 * @since 2.7.0 3989 */ 3990 function bp_email_unsubscribe_handler() { 3991 $emails = bp_email_get_unsubscribe_type_schema(); 3992 $raw_email_type = ! empty( $_GET['nt'] ) ? $_GET['nt'] : ''; 3993 $raw_hash = ! empty( $_GET['nh'] ) ? $_GET['nh'] : ''; 3994 $raw_user_id = ! empty( $_GET['uid'] ) ? absint( $_GET['uid'] ) : 0; 3995 $new_hash = hash_hmac( 'sha1', "{$raw_email_type}:{$raw_user_id}", bp_email_get_salt() ); 3996 3997 // Check required values. 3998 if ( ! $raw_user_id || ! $raw_email_type || ! $raw_hash || ! array_key_exists( $raw_email_type, $emails ) ) { 3999 $redirect_to = wp_login_url(); 4000 $result_msg = __( 'Something has gone wrong.', 'buddypress' ); 4001 $unsub_msg = __( 'Please log in and go to your settings to unsubscribe from notification emails.', 'buddypress' ); 4002 4003 // Check valid hash. 4004 } elseif ( ! hash_equals( $new_hash, $raw_hash ) ) { 4005 $redirect_to = wp_login_url(); 4006 $result_msg = __( 'Something has gone wrong.', 'buddypress' ); 4007 $unsub_msg = __( 'Please log in and go to your settings to unsubscribe from notification emails.', 'buddypress' ); 4008 4009 // Don't let authenticated users unsubscribe other users' email notifications. 4010 } elseif ( is_user_logged_in() && get_current_user_id() !== $raw_user_id ) { 4011 $result_msg = __( 'Something has gone wrong.', 'buddypress' ); 4012 $unsub_msg = __( 'Please go to your notifications settings to unsubscribe from emails.', 'buddypress' ); 4013 4014 if ( bp_is_active( 'settings' ) ) { 4015 $redirect_to = sprintf( 4016 '%s%s/notifications/', 4017 bp_core_get_user_domain( get_current_user_id() ), 4018 bp_get_settings_slug() 4019 ); 4020 } else { 4021 $redirect_to = bp_core_get_user_domain( get_current_user_id() ); 4022 } 4023 4024 } else { 4025 if ( bp_is_active( 'settings' ) ) { 4026 $redirect_to = sprintf( 4027 '%s%s/notifications/', 4028 bp_core_get_user_domain( $raw_user_id ), 4029 bp_get_settings_slug() 4030 ); 4031 } else { 4032 $redirect_to = bp_core_get_user_domain( $raw_user_id ); 4033 } 4034 4035 // Unsubscribe. 4036 $meta_key = $emails[ $raw_email_type ]['unsubscribe']['meta_key']; 4037 bp_update_user_meta( $raw_user_id, $meta_key, 'no' ); 4038 4039 $result_msg = $emails[ $raw_email_type ]['unsubscribe']['message']; 4040 $unsub_msg = __( 'You can change this or any other email notification preferences in your email settings.', 'buddypress' ); 4041 } 4042 4043 $message = sprintf( 4044 '%1$s <a href="%2$s">%3$s</a>', 4045 $result_msg, 4046 esc_url( $redirect_to ), 4047 esc_html( $unsub_msg ) 4048 ); 4049 4050 bp_core_add_message( $message ); 4051 bp_core_redirect( bp_core_get_user_domain( $raw_user_id ) ); 4052 4053 exit; 4054 } 4055 4056 /** 4057 * Creates unsubscribe link for notification emails. 4058 * 4059 * @since 2.7.0 4060 * 4061 * @param string $redirect_to The URL to which the unsubscribe query string is appended. 4062 * @param array $args { 4063 * Used to build unsubscribe query string. 4064 * 4065 * @type string $notification_type Which notification type is being sent. 4066 * @type string $user_id The ID of the user to whom the notification is sent. 4067 * @type string $redirect_to Optional. The url to which the user will be redirected. Default is the activity directory. 4068 * } 4069 * @return string The unsubscribe link. 4070 */ 4071 function bp_email_get_unsubscribe_link( $args ) { 4072 $emails = bp_email_get_unsubscribe_type_schema(); 4073 4074 if ( empty( $args['notification_type'] ) || ! array_key_exists( $args['notification_type'], $emails ) ) { 4075 return wp_login_url(); 4076 } 4077 4078 $email_type = $args['notification_type']; 4079 $redirect_to = ! empty( $args['redirect_to'] ) ? $args['redirect_to'] : site_url(); 4080 $user_id = (int) $args['user_id']; 4081 4082 // Bail out if the activity type is not un-unsubscribable. 4083 if ( empty( $emails[ $email_type ]['unsubscribe'] ) ) { 4084 return ''; 4085 } 4086 4087 $link = add_query_arg( 4088 array( 4089 'action' => 'unsubscribe', 4090 'nh' => hash_hmac( 'sha1', "{$email_type}:{$user_id}", bp_email_get_salt() ), 4091 'nt' => $args['notification_type'], 4092 'uid' => $user_id, 4093 ), 4094 $redirect_to 4095 ); 4096 4097 /** 4098 * Filters the unsubscribe link. 4099 * 4100 * @since 2.7.0 4101 */ 4102 return apply_filters( 'bp_email_get_link', $link, $redirect_to, $args ); 4103 } 4104 4105 /** 4106 * Get a persistent salt for email unsubscribe links. 4107 * 4108 * @since 2.7.0 4109 * 4110 * @return string|null Returns null if value isn't set, otherwise string. 4111 */ 4112 function bp_email_get_salt() { 4113 return bp_get_option( 'bp-emails-unsubscribe-salt', null ); 4114 } 4115 4116 /** 4117 * Get a list of emails for use in our unsubscribe functions. 4118 * 4119 * @since 2.8.0 4120 * 4121 * @see https://buddypress.trac.wordpress.org/ticket/7431 4122 * 4123 * @return array The array of email types and their schema. 4124 */ 4125 function bp_email_get_unsubscribe_type_schema() { 4126 $emails = bp_email_get_type_schema( 'all' ); 4127 4128 /** 4129 * Filters the return of `bp_email_get_type_schema( 'all' )` for use with 4130 * our unsubscribe functionality. 4131 * 4132 * @since 2.8.0 4133 * 4134 * @param array $emails The array of email types and their schema. 4135 */ 4136 return (array) apply_filters( 'bp_email_get_unsubscribe_type_schema', $emails ); 4137 } 4138 4139 /** 4140 * Get BuddyPress content allowed tags. 4141 * 4142 * @since 3.0.0 4143 * 4144 * @global array $allowedtags KSES allowed HTML elements. 4145 * @return array BuddyPress content allowed tags. 4146 */ 4147 function bp_get_allowedtags() { 4148 global $allowedtags; 4149 4150 return array_merge_recursive( $allowedtags, array( 4151 'a' => array( 4152 'aria-label' => array(), 4153 'class' => array(), 4154 'data-bp-tooltip' => array(), 4155 'id' => array(), 4156 'rel' => array(), 4157 ), 4158 'img' => array( 4159 'src' => array(), 4160 'alt' => array(), 4161 'width' => array(), 4162 'height' => array(), 4163 'class' => array(), 4164 'id' => array(), 4165 ), 4166 'span'=> array( 4167 'class' => array(), 4168 'data-livestamp' => array(), 4169 ), 4170 'ul' => array(), 4171 'ol' => array(), 4172 'li' => array(), 4173 ) ); 4174 } 4175 4176 /** 4177 * Remove script and style tags from a string. 4178 * 4179 * @since 3.0.1 4180 * 4181 * @param string $string The string to strip tags from. 4182 * @return string The stripped tags string. 4183 */ 4184 function bp_strip_script_and_style_tags( $string ) { 4185 return preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $string ); 4186 } 4187 4188 /** 4189 * Checks whether the current installation is "large". 4190 * 4191 * By default, an installation counts as "large" if there are 10000 users or more. 4192 * Filter 'bp_is_large_install' to adjust. 4193 * 4194 * @since 4.1.0 4195 * 4196 * @return bool 4197 */ 4198 function bp_is_large_install() { 4199 // Use the Multisite function if available. 4200 if ( function_exists( 'wp_is_large_network' ) ) { 4201 $is_large = wp_is_large_network( 'users' ); 4202 } else { 4203 $is_large = bp_core_get_total_member_count() > 10000; 4204 } 4205 4206 /** 4207 * Filters whether the current installation is "large". 4208 * 4209 * @since 4.1.0 4210 * 4211 * @param bool $is_large True if the network is "large". 4212 */ 4213 return (bool) apply_filters( 'bp_is_large_install', $is_large ); 4214 } 4215 4216 /** 4217 * Returns the upper limit on the "max" item count, for widgets that support it. 4218 * 4219 * @since 5.0.0 4220 * 4221 * @param string $widget_class Optional. Class name of the calling widget. 4222 * @return int 4223 */ 4224 function bp_get_widget_max_count_limit( $widget_class = '' ) { 4225 /** 4226 * Filters the upper limit on the "max" item count, for widgets that support it. 4227 * 4228 * @since 5.0.0 4229 * 4230 * @param int $count Defaults to 50. 4231 * @param string $widget_class Class name of the calling widget. 4232 */ 4233 return apply_filters( 'bp_get_widget_max_count_limit', 50, $widget_class ); 4234 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Jan 28 01:01:36 2021 | Cross-referenced by PHPXref 0.7.1 |