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


Generated: Fri Oct 30 01:01:33 2020 Cross-referenced by PHPXref 0.7.1