[ Index ]

PHP Cross Reference of BuddyPress

title

Body

[close]

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

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


Generated: Fri Apr 23 01:01:41 2021 Cross-referenced by PHPXref 0.7.1