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


Generated: Thu Nov 21 01:00:57 2024 Cross-referenced by PHPXref 0.7.1