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


Generated: Tue Oct 19 01:00:57 2021 Cross-referenced by PHPXref 0.7.1