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


Generated: Sat Sep 21 01:01:46 2019 Cross-referenced by PHPXref 0.7.1