[ Index ]

PHP Cross Reference of BuddyPress

title

Body

[close]

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

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


Generated: Sun May 31 01:01:27 2020 Cross-referenced by PHPXref 0.7.1