[ Index ]

PHP Cross Reference of BuddyPress

title

Body

[close]

/src/bp-core/ -> bp-core-functions.php (source)

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


Generated: Tue Nov 19 01:01:36 2019 Cross-referenced by PHPXref 0.7.1