[ Index ]

PHP Cross Reference of BuddyPress

title

Body

[close]

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

   1  <?php
   2  /**
   3   * BuddyPress Activity Functions.
   4   *
   5   * Functions for the Activity Streams component.
   6   *
   7   * @package BuddyPress
   8   * @subpackage ActivityFunctions
   9   * @since 1.5.0
  10   */
  11  
  12  // Exit if accessed directly.
  13  defined( 'ABSPATH' ) || exit;
  14  
  15  /**
  16   * Check whether the $bp global lists an activity directory page.
  17   *
  18   * @since 1.5.0
  19   *
  20   * @return bool True if activity directory page is found, otherwise false.
  21   */
  22  function bp_activity_has_directory() {
  23      return isset( buddypress()->pages->activity->id ) && buddypress()->pages->activity->id;
  24  }
  25  
  26  /**
  27   * Are mentions enabled or disabled?
  28   *
  29   * The Mentions feature does a number of things, all of which will be turned
  30   * off if you disable mentions:
  31   *   - Detecting and auto-linking @username in all BP/WP content.
  32   *   - Sending BP notifications and emails to users when they are mentioned
  33   *     using the @username syntax.
  34   *   - The Public Message button on user profiles.
  35   *
  36   * Mentions are enabled by default. To disable, put the following line in
  37   * bp-custom.php or your theme's functions.php file:
  38   *
  39   *   add_filter( 'bp_activity_do_mentions', '__return_false' );
  40   *
  41   * @since 1.8.0
  42   *
  43   * @return bool $retval True to enable mentions, false to disable.
  44   */
  45  function bp_activity_do_mentions() {
  46  
  47      /**
  48       * Filters whether or not mentions are enabled.
  49       *
  50       * @since 1.8.0
  51       *
  52       * @param bool $enabled True to enable mentions, false to disable.
  53       */
  54      return (bool) apply_filters( 'bp_activity_do_mentions', true );
  55  }
  56  
  57  /**
  58   * Should BuddyPress load the mentions scripts and related assets, including results to prime the
  59   * mentions suggestions?
  60   *
  61   * @since 2.1.0
  62   *
  63   * @return bool True if mentions scripts should be loaded.
  64   */
  65  function bp_activity_maybe_load_mentions_scripts() {
  66      $mentions_enabled = bp_activity_do_mentions() && bp_is_user_active();
  67      $load_mentions    = $mentions_enabled && ( bp_is_activity_component() || is_admin() );
  68  
  69      /**
  70       * Filters whether or not BuddyPress should load mentions scripts and assets.
  71       *
  72       * @since 2.1.0
  73       *
  74       * @param bool $load_mentions    True to load mentions assets, false otherwise.
  75       * @param bool $mentions_enabled True if mentions are enabled.
  76       */
  77      return (bool) apply_filters( 'bp_activity_maybe_load_mentions_scripts', $load_mentions, $mentions_enabled );
  78  }
  79  
  80  /**
  81   * Locate usernames in an activity content string, as designated by an @ sign.
  82   *
  83   * @since 1.5.0
  84   *
  85   * @param string $content The content of the activity, usually found in
  86   *                        $activity->content.
  87   * @return array|bool Associative array with user ID as key and username as
  88   *                    value. Boolean false if no mentions found.
  89   */
  90  function bp_activity_find_mentions( $content ) {
  91  
  92      $pattern = '/[@]+([A-Za-z0-9-_\.@]+)\b/';
  93      preg_match_all( $pattern, $content, $usernames );
  94  
  95      // Make sure there's only one instance of each username.
  96      $usernames = array_unique( $usernames[1] );
  97  
  98      // Bail if no usernames.
  99      if ( empty( $usernames ) ) {
 100          return false;
 101      }
 102  
 103      $mentioned_users = array();
 104  
 105      // We've found some mentions! Check to see if users exist.
 106      foreach( (array) array_values( $usernames ) as $username ) {
 107          $user_id = bp_activity_get_userid_from_mentionname( $username );
 108  
 109          // The user ID exists, so let's add it to our array.
 110          if ( ! empty( $user_id ) ) {
 111              $mentioned_users[ $user_id ] = $username;
 112          }
 113      }
 114  
 115      if ( empty( $mentioned_users ) ) {
 116          return false;
 117      }
 118  
 119      /**
 120       * Filters the mentioned users.
 121       *
 122       * @since 2.5.0
 123       *
 124       * @param array $mentioned_users Associative array with user IDs as keys and usernames as values.
 125       */
 126      return apply_filters( 'bp_activity_mentioned_users', $mentioned_users );
 127  }
 128  
 129  /**
 130   * Reset a user's unread mentions list and count.
 131   *
 132   * @since 1.5.0
 133   *
 134   * @param int $user_id The id of the user whose unread mentions are being reset.
 135   */
 136  function bp_activity_clear_new_mentions( $user_id ) {
 137      bp_delete_user_meta( $user_id, 'bp_new_mention_count' );
 138      bp_delete_user_meta( $user_id, 'bp_new_mentions'      );
 139  
 140      /**
 141       * Fires once mentions has been reset for a given user.
 142       *
 143       * @since  2.5.0
 144       *
 145       * @param  int $user_id The id of the user whose unread mentions are being reset.
 146       */
 147      do_action( 'bp_activity_clear_new_mentions', $user_id );
 148  }
 149  
 150  /**
 151   * Adjusts mention count for mentioned users in activity items.
 152   *
 153   * This function is useful if you only have the activity ID handy and you
 154   * haven't parsed an activity item for @mentions yet.
 155   *
 156   * Currently, only used in {@link bp_activity_delete()}.
 157   *
 158   * @since 1.5.0
 159   *
 160   * @param int    $activity_id The unique id for the activity item.
 161   * @param string $action      Can be 'delete' or 'add'. Defaults to 'add'.
 162   * @return bool
 163   */
 164  function bp_activity_adjust_mention_count( $activity_id = 0, $action = 'add' ) {
 165  
 166      // Bail if no activity ID passed.
 167      if ( empty( $activity_id ) ) {
 168          return false;
 169      }
 170  
 171      // Get activity object.
 172      $activity  = new BP_Activity_Activity( $activity_id );
 173  
 174      // Try to find mentions.
 175      $usernames = bp_activity_find_mentions( strip_tags( $activity->content ) );
 176  
 177      // Still empty? Stop now.
 178      if ( empty( $usernames ) ) {
 179          return false;
 180      }
 181  
 182      // Increment mention count foreach mentioned user.
 183      foreach( (array) array_keys( $usernames ) as $user_id ) {
 184          bp_activity_update_mention_count_for_user( $user_id, $activity_id, $action );
 185      }
 186  }
 187  
 188  /**
 189   * Update the mention count for a given user.
 190   *
 191   * This function should be used when you've already parsed your activity item
 192   * for @mentions.
 193   *
 194   * @since 1.7.0
 195   *
 196   * @param int    $user_id     The user ID.
 197   * @param int    $activity_id The unique ID for the activity item.
 198   * @param string $action      'delete' or 'add'. Default: 'add'.
 199   * @return bool
 200   */
 201  function bp_activity_update_mention_count_for_user( $user_id, $activity_id, $action = 'add' ) {
 202  
 203      if ( empty( $user_id ) || empty( $activity_id ) ) {
 204          return false;
 205      }
 206  
 207      // Adjust the mention list and count for the member.
 208      $new_mention_count = (int) bp_get_user_meta( $user_id, 'bp_new_mention_count', true );
 209      $new_mentions      =       bp_get_user_meta( $user_id, 'bp_new_mentions',      true );
 210  
 211      // Make sure new mentions is an array.
 212      if ( empty( $new_mentions ) ) {
 213          $new_mentions = array();
 214      }
 215  
 216      switch ( $action ) {
 217          case 'delete' :
 218              $key = array_search( $activity_id, $new_mentions );
 219  
 220              if ( $key !== false ) {
 221                  unset( $new_mentions[$key] );
 222              }
 223  
 224              break;
 225  
 226          case 'add' :
 227          default :
 228              if ( !in_array( $activity_id, $new_mentions ) ) {
 229                  $new_mentions[] = (int) $activity_id;
 230              }
 231  
 232              break;
 233      }
 234  
 235      // Get an updated mention count.
 236      $new_mention_count = count( $new_mentions );
 237  
 238      // Resave the user_meta.
 239      bp_update_user_meta( $user_id, 'bp_new_mention_count', $new_mention_count );
 240      bp_update_user_meta( $user_id, 'bp_new_mentions',      $new_mentions );
 241  
 242      return true;
 243  }
 244  
 245  /**
 246   * Determine a user's "mentionname", the name used for that user in @-mentions.
 247   *
 248   * @since 1.9.0
 249   *
 250   * @param int|string $user_id ID of the user to get @-mention name for.
 251   * @return string $mentionname User name appropriate for @-mentions.
 252   */
 253  function bp_activity_get_user_mentionname( $user_id ) {
 254      $mentionname = '';
 255  
 256      $userdata = bp_core_get_core_userdata( $user_id );
 257  
 258      if ( $userdata ) {
 259          if ( bp_is_username_compatibility_mode() ) {
 260              $mentionname = str_replace( ' ', '-', $userdata->user_login );
 261          } else {
 262              $mentionname = $userdata->user_nicename;
 263          }
 264      }
 265  
 266      return $mentionname;
 267  }
 268  
 269  /**
 270   * Get a user ID from a "mentionname", the name used for a user in @-mentions.
 271   *
 272   * @since 1.9.0
 273   *
 274   * @param string $mentionname Username of user in @-mentions.
 275   * @return int|bool ID of the user, if one is found. Otherwise false.
 276   */
 277  function bp_activity_get_userid_from_mentionname( $mentionname ) {
 278      $user_id = false;
 279  
 280      /*
 281       * In username compatibility mode, hyphens are ambiguous between
 282       * actual hyphens and converted spaces.
 283       *
 284       * @todo There is the potential for username clashes between 'foo bar'
 285       * and 'foo-bar' in compatibility mode. Come up with a system for
 286       * unique mentionnames.
 287       */
 288      if ( bp_is_username_compatibility_mode() ) {
 289          // First, try the raw username.
 290          $userdata = get_user_by( 'login', $mentionname );
 291  
 292          // Doing a direct query to use proper regex. Necessary to
 293          // account for hyphens + spaces in the same user_login.
 294          if ( empty( $userdata ) || ! is_a( $userdata, 'WP_User' ) ) {
 295              global $wpdb;
 296              $regex   = esc_sql( str_replace( '-', '[ \-]', $mentionname ) );
 297              $user_id = $wpdb->get_var( "SELECT ID FROM {$wpdb->users} WHERE user_login REGEXP '{$regex}'" );
 298          } else {
 299              $user_id = $userdata->ID;
 300          }
 301  
 302      // When username compatibility mode is disabled, the mentionname is
 303      // the same as the nicename.
 304      } else {
 305          $user_id = bp_core_get_userid_from_nicename( $mentionname );
 306      }
 307  
 308  
 309      return $user_id;
 310  }
 311  
 312  /** Actions ******************************************************************/
 313  
 314  /**
 315   * Register an activity 'type' and its action description/callback.
 316   *
 317   * Activity actions are strings used to describe items in the activity stream,
 318   * such as 'Joe became a registered member' or 'Bill and Susie are now
 319   * friends'. Each activity type (such as 'new_member' or 'friendship_created')
 320   * used by a component should be registered using this function.
 321   *
 322   * While it's possible to post items to the activity stream whose types are
 323   * not registered using bp_activity_set_action(), it is not recommended;
 324   * unregistered types will not be displayed properly in the activity admin
 325   * panel, and dynamic action generation (which is essential for multilingual
 326   * sites, etc) will not work.
 327   *
 328   * @since 1.1.0
 329   *
 330   * @param  string        $component_id    The unique string ID of the component.
 331   * @param  string        $type            The action type.
 332   * @param  string        $description     The action description.
 333   * @param  callable|bool $format_callback Callback for formatting the action string.
 334   * @param  string|bool   $label           String to describe this action in the activity stream filter dropdown.
 335   * @param  array         $context         Optional. Activity stream contexts where the filter should appear.
 336   *                                        Values: 'activity', 'member', 'member_groups', 'group'.
 337   * @param  int           $position        Optional. The position of the action when listed in dropdowns.
 338   * @return bool False if any param is empty, otherwise true.
 339   */
 340  function bp_activity_set_action( $component_id, $type, $description, $format_callback = false, $label = false, $context = array(), $position = 0 ) {
 341      $bp = buddypress();
 342  
 343      // Return false if any of the above values are not set.
 344      if ( empty( $component_id ) || empty( $type ) || empty( $description ) ) {
 345          return false;
 346      }
 347  
 348      // Set activity action.
 349      if ( ! isset( $bp->activity->actions ) || ! is_object( $bp->activity->actions ) ) {
 350          $bp->activity->actions = new stdClass;
 351      }
 352  
 353      // Verify callback.
 354      if ( ! is_callable( $format_callback ) ) {
 355          $format_callback = '';
 356      }
 357  
 358      if ( ! isset( $bp->activity->actions->{$component_id} ) || ! is_object( $bp->activity->actions->{$component_id} ) ) {
 359          $bp->activity->actions->{$component_id} = new stdClass;
 360      }
 361  
 362      /**
 363       * Filters the action type being set for the current activity item.
 364       *
 365       * @since 1.1.0
 366       *
 367       * @param array    $array           Array of arguments for action type being set.
 368       * @param string   $component_id    ID of the current component being set.
 369       * @param string   $type            Action type being set.
 370       * @param string   $description     Action description for action being set.
 371       * @param callable $format_callback Callback for formatting the action string.
 372       * @param string   $label           String to describe this action in the activity stream filter dropdown.
 373       * @param array    $context         Activity stream contexts where the filter should appear. 'activity', 'member',
 374       *                                  'member_groups', 'group'.
 375       */
 376      $bp->activity->actions->{$component_id}->{$type} = apply_filters( 'bp_activity_set_action', array(
 377          'key'             => $type,
 378          'value'           => $description,
 379          'format_callback' => $format_callback,
 380          'label'           => $label,
 381          'context'         => $context,
 382          'position'        => $position,
 383      ), $component_id, $type, $description, $format_callback, $label, $context );
 384  
 385      // Sort the actions of the affected component.
 386      $action_array = (array) $bp->activity->actions->{$component_id};
 387      $action_array = bp_sort_by_key( $action_array, 'position', 'num' );
 388  
 389      // Restore keys.
 390      $bp->activity->actions->{$component_id} = new stdClass;
 391      foreach ( $action_array as $key_ordered ) {
 392          $bp->activity->actions->{$component_id}->{$key_ordered['key']} = $key_ordered;
 393      }
 394  
 395      return true;
 396  }
 397  
 398  /**
 399   * Set tracking arguments for a given post type.
 400   *
 401   * @since 2.2.0
 402   *
 403   * @global $wp_post_types
 404   *
 405   * @param string $post_type The name of the post type, as registered with WordPress. Eg 'post' or 'page'.
 406   * @param array  $args {
 407   *     An associative array of tracking parameters. All items are optional.
 408   *     @type string   $bp_activity_admin_filter String to use in the Dashboard > Activity dropdown.
 409   *     @type string   $bp_activity_front_filter String to use in the front-end dropdown.
 410   *     @type string   $bp_activity_new_post     String format to use for generating the activity action. Should be a
 411   *                                              translatable string where %1$s is replaced by a user link and %2$s is
 412   *                                              the URL of the newly created post.
 413   *     @type string   $bp_activity_new_post_ms  String format to use for generating the activity action on Multisite.
 414   *                                              Should be a translatable string where %1$s is replaced by a user link,
 415   *                                              %2$s is the URL of the newly created post, and %3$s is a link to
 416   *                                              the site.
 417   *     @type string   $component_id             ID of the BuddyPress component to associate the activity item.
 418   *     @type string   $action_id                Value for the 'type' param of the new activity item.
 419   *     @type callable $format_callback          Callback for formatting the activity action string.
 420   *                                              Default: 'bp_activity_format_activity_action_custom_post_type_post'.
 421   *     @type array    $contexts                 The directory contexts in which the filter will show.
 422   *                                              Default: array( 'activity' ).
 423   *     @type array    $position                 Position of the item in filter dropdowns.
 424   *     @type string   $singular                 Singular, translatable name of the post type item. If no value is
 425   *                                              provided, it's pulled from the 'singular_name' of the post type.
 426   *     @type bool     $activity_comment         Whether to allow comments on the activity items. Defaults to true if
 427   *                                              the post type does not natively support comments, otherwise false.
 428   * }
 429   * @return bool
 430   */
 431  function bp_activity_set_post_type_tracking_args( $post_type = '', $args = array() ) {
 432      global $wp_post_types;
 433  
 434      if ( empty( $wp_post_types[ $post_type ] ) || ! post_type_supports( $post_type, 'buddypress-activity' ) || ! is_array( $args ) ) {
 435          return false;
 436      }
 437  
 438      $activity_labels = array(
 439          /* Post labels */
 440          'bp_activity_admin_filter',
 441          'bp_activity_front_filter',
 442          'bp_activity_new_post',
 443          'bp_activity_new_post_ms',
 444          /* Comment labels */
 445          'bp_activity_comments_admin_filter',
 446          'bp_activity_comments_front_filter',
 447          'bp_activity_new_comment',
 448          'bp_activity_new_comment_ms'
 449      );
 450  
 451      // Labels are loaded into the post type object.
 452      foreach ( $activity_labels as $label_type ) {
 453          if ( ! empty( $args[ $label_type ] ) ) {
 454              $wp_post_types[ $post_type ]->labels->{$label_type} = $args[ $label_type ];
 455              unset( $args[ $label_type ] );
 456          }
 457      }
 458  
 459      // If there are any additional args, put them in the bp_activity attribute of the post type.
 460      if ( ! empty( $args ) ) {
 461          $wp_post_types[ $post_type ]->bp_activity = $args;
 462      }
 463  }
 464  
 465  /**
 466   * Get tracking arguments for a specific post type.
 467   *
 468   * @since 2.2.0
 469   * @since 2.5.0 Add post type comments tracking args
 470   *
 471   * @param  string $post_type Name of the post type.
 472   * @return object The tracking arguments of the post type.
 473   */
 474  function bp_activity_get_post_type_tracking_args( $post_type ) {
 475      if ( ! post_type_supports( $post_type, 'buddypress-activity' ) ) {
 476          return false;
 477      }
 478  
 479      $post_type_object           = get_post_type_object( $post_type );
 480      $post_type_support_comments = post_type_supports( $post_type, 'comments' );
 481  
 482      $post_type_activity = array(
 483          'component_id'            => buddypress()->activity->id,
 484          'action_id'               => 'new_' . $post_type,
 485          'format_callback'         => 'bp_activity_format_activity_action_custom_post_type_post',
 486          'front_filter'            => $post_type_object->labels->name,
 487          'contexts'                => array( 'activity' ),
 488          'position'                => 0,
 489          'singular'                => strtolower( $post_type_object->labels->singular_name ),
 490          'activity_comment'        => ! $post_type_support_comments,
 491          'comment_action_id'       => false,
 492          'comment_format_callback' => 'bp_activity_format_activity_action_custom_post_type_comment',
 493      );
 494  
 495      if ( ! empty( $post_type_object->bp_activity ) ) {
 496          $post_type_activity = bp_parse_args(
 497              (array) $post_type_object->bp_activity,
 498              $post_type_activity,
 499              $post_type . '_tracking_args'
 500          );
 501      }
 502  
 503      $post_type_activity = (object) $post_type_activity;
 504  
 505      // Try to get the admin filter from the post type labels.
 506      if ( ! empty( $post_type_object->labels->bp_activity_admin_filter ) ) {
 507          $post_type_activity->admin_filter = $post_type_object->labels->bp_activity_admin_filter;
 508  
 509      // Fall back to a generic name.
 510      } else {
 511          $post_type_activity->admin_filter = _x( 'New item published', 'Post Type generic activity post admin filter', 'buddypress' );
 512      }
 513  
 514      // Check for the front filter in the post type labels.
 515      if ( ! empty( $post_type_object->labels->bp_activity_front_filter ) ) {
 516          $post_type_activity->front_filter = $post_type_object->labels->bp_activity_front_filter;
 517      }
 518  
 519      // Try to get the action for new post type action on non-multisite installations.
 520      if ( ! empty( $post_type_object->labels->bp_activity_new_post ) ) {
 521          $post_type_activity->new_post_type_action = $post_type_object->labels->bp_activity_new_post;
 522      }
 523  
 524      // Try to get the action for new post type action on multisite installations.
 525      if ( ! empty( $post_type_object->labels->bp_activity_new_post_ms ) ) {
 526          $post_type_activity->new_post_type_action_ms = $post_type_object->labels->bp_activity_new_post_ms;
 527      }
 528  
 529      // If the post type supports comments and has a comment action id, build the comments tracking args.
 530      if ( $post_type_support_comments && ! empty( $post_type_activity->comment_action_id ) ) {
 531          // Init a new container for the activity type for comments.
 532          $post_type_activity->comments_tracking = new stdClass();
 533  
 534          // Build the activity type for comments.
 535          $post_type_activity->comments_tracking->component_id = $post_type_activity->component_id;
 536          $post_type_activity->comments_tracking->action_id    = $post_type_activity->comment_action_id;
 537  
 538          // Try to get the comments admin filter from the post type labels.
 539          if ( ! empty( $post_type_object->labels->bp_activity_comments_admin_filter ) ) {
 540              $post_type_activity->comments_tracking->admin_filter = $post_type_object->labels->bp_activity_comments_admin_filter;
 541  
 542          // Fall back to a generic name.
 543          } else {
 544              $post_type_activity->comments_tracking->admin_filter = _x( 'New item comment posted', 'Post Type generic comments activity admin filter', 'buddypress' );
 545          }
 546  
 547          $post_type_activity->comments_tracking->format_callback = $post_type_activity->comment_format_callback;
 548  
 549          // Check for the comments front filter in the post type labels.
 550          if ( ! empty( $post_type_object->labels->bp_activity_comments_front_filter ) ) {
 551              $post_type_activity->comments_tracking->front_filter = $post_type_object->labels->bp_activity_comments_front_filter;
 552  
 553          // Fall back to a generic name.
 554          } else {
 555              $post_type_activity->comments_tracking->front_filter = _x( 'Item comments', 'Post Type generic comments activity front filter', 'buddypress' );
 556          }
 557  
 558          $post_type_activity->comments_tracking->contexts = $post_type_activity->contexts;
 559          $post_type_activity->comments_tracking->position = (int) $post_type_activity->position + 1;
 560  
 561          // Try to get the action for new post type comment action on non-multisite installations.
 562          if ( ! empty( $post_type_object->labels->bp_activity_new_comment ) ) {
 563              $post_type_activity->comments_tracking->new_post_type_comment_action = $post_type_object->labels->bp_activity_new_comment;
 564          }
 565  
 566          // Try to get the action for new post type comment action on multisite installations.
 567          if ( ! empty( $post_type_object->labels->bp_activity_new_comment_ms ) ) {
 568              $post_type_activity->comments_tracking->new_post_type_comment_action_ms = $post_type_object->labels->bp_activity_new_comment_ms;
 569          }
 570      }
 571  
 572      // Finally make sure we'll be able to find the post type this activity type is associated to.
 573      $post_type_activity->post_type = $post_type;
 574  
 575      /**
 576       * Filters tracking arguments for a specific post type.
 577       *
 578       * @since 2.2.0
 579       *
 580       * @param object $post_type_activity The tracking arguments of the post type.
 581       * @param string $post_type          Name of the post type.
 582       */
 583      return apply_filters( 'bp_activity_get_post_type_tracking_args', $post_type_activity, $post_type );
 584  }
 585  
 586  /**
 587   * Get tracking arguments for all post types.
 588   *
 589   * @since 2.2.0
 590   * @since 2.5.0 Include post type comments tracking args if needed
 591   *
 592   * @return array List of post types with their tracking arguments.
 593   */
 594  function bp_activity_get_post_types_tracking_args() {
 595      // Fetch all public post types.
 596      $post_types = get_post_types( array( 'public' => true ), 'names' );
 597  
 598      $post_types_tracking_args = array();
 599  
 600      foreach ( $post_types as $post_type ) {
 601          $track_post_type = bp_activity_get_post_type_tracking_args( $post_type );
 602  
 603          if ( ! empty( $track_post_type ) ) {
 604              // Set the post type comments tracking args.
 605              if ( ! empty( $track_post_type->comments_tracking->action_id ) ) {
 606                  // Used to check support for comment tracking by activity type (new_post_type_comment).
 607                  $track_post_type->comments_tracking->comments_tracking = true;
 608  
 609                  // Used to be able to find the post type this activity type is associated to.
 610                  $track_post_type->comments_tracking->post_type = $post_type;
 611  
 612                  $post_types_tracking_args[ $track_post_type->comments_tracking->action_id ] = $track_post_type->comments_tracking;
 613  
 614                  // Used to check support for comment tracking by activity type (new_post_type).
 615                  $track_post_type->comments_tracking = true;
 616              }
 617  
 618              $post_types_tracking_args[ $track_post_type->action_id ] = $track_post_type;
 619          }
 620  
 621      }
 622  
 623      /**
 624       * Filters tracking arguments for all post types.
 625       *
 626       * @since 2.2.0
 627       *
 628       * @param array $post_types_tracking_args Array of post types with
 629       *                                        their tracking arguments.
 630       */
 631      return apply_filters( 'bp_activity_get_post_types_tracking_args', $post_types_tracking_args );
 632  }
 633  
 634  /**
 635   * Gets the list of activity types name supporting the requested feature.
 636   *
 637   * This function is still a WIP, please don't use it into your plugins or themes.
 638   *
 639   * @since 10.0.0
 640   *
 641   * @access private
 642   * @todo `bp_activity_set_action()` should be improved to include a supports
 643   * argument or best we should create a `bp_register_activity_type()` function
 644   * to mimic the way WordPress registers post types. For now we'll use a non
 645   * extendable workaround.
 646   *
 647   * @param string $feature The feature activity types should support.
 648   * @return array          The list of activity types name supporting the requested feature.
 649   */
 650  function _bp_activity_get_types_by_support( $feature = 'generated-content' ) {
 651      $activity_types = array();
 652  
 653      if ( 'generated-content' === $feature ) {
 654          $activity_types = array( 'new_member', 'new_avatar' );
 655  
 656          if ( bp_is_active( 'friends' ) ) {
 657              array_push( $activity_types, 'friendship_created' );
 658          }
 659  
 660          if ( bp_is_active( 'groups' ) ) {
 661              array_push( $activity_types, 'created_group', 'joined_group' );
 662          }
 663  
 664          if ( bp_is_active( 'xprofile' ) ) {
 665              array_push( $activity_types, 'updated_profile' );
 666          }
 667      }
 668  
 669      $filter_key = str_replace( '-', '_', $feature );
 670  
 671      /**
 672       * Use this filter to add/remove activity types supporting the requested feature.
 673       *
 674       * The dynamic portion of the filter is the name of the requested feature where hyphens are
 675       * replaced by underscores. Eg. use `bp_activity_get_types_supporting_generated_content` to
 676       * edit the list of activities supporting the `generated-content` feature.
 677       *
 678       * @since 10.0.0
 679       *
 680       * @param array $activity_types The list of activity types name supporting the requested feature.
 681       */
 682      return apply_filters( "bp_activity_get_types_supporting_{$filter_key}", $activity_types );
 683  }
 684  
 685  /**
 686   * Check if the *Post Type* activity supports a specific feature.
 687   *
 688   * @since 2.5.0
 689   *
 690   * @param  string $activity_type The activity type to check.
 691   * @param  string $feature       The feature to check. Currently supports:
 692   *                               'post-type-comment-tracking', 'post-type-comment-reply' & 'comment-reply'.
 693   *                               See inline doc for more info.
 694   * @return bool
 695   */
 696  function bp_activity_type_supports( $activity_type = '', $feature = '' ) {
 697      $retval = false;
 698  
 699      $bp = buddypress();
 700  
 701      switch ( $feature ) {
 702          /**
 703           * Does this activity type support comment tracking?
 704           *
 705           * eg. 'new_blog_post' and 'new_blog_comment' will both return true.
 706           */
 707          case 'post-type-comment-tracking' :
 708              // Set the activity track global if not set yet.
 709              if ( empty( $bp->activity->track ) ) {
 710                  $bp->activity->track = bp_activity_get_post_types_tracking_args();
 711              }
 712  
 713              if ( ! empty( $bp->activity->track[ $activity_type ]->comments_tracking ) ) {
 714                  $retval = true;
 715              }
 716              break;
 717  
 718          /**
 719           * Is this a parent activity type that support post comments?
 720           *
 721           * eg. 'new_blog_post' will return true; 'new_blog_comment' will return false.
 722           */
 723          case 'post-type-comment-reply' :
 724              // Set the activity track global if not set yet.
 725              if ( empty( $bp->activity->track ) ) {
 726                  $bp->activity->track = bp_activity_get_post_types_tracking_args();
 727              }
 728  
 729              if ( ! empty( $bp->activity->track[ $activity_type ]->comments_tracking ) && ! empty( $bp->activity->track[ $activity_type ]->comment_action_id ) ) {
 730                  $retval = true;
 731              }
 732              break;
 733  
 734          /**
 735           * Does this activity type support comment & reply?
 736           */
 737          case 'comment-reply' :
 738              // Set the activity track global if not set yet.
 739              if ( empty( $bp->activity->track ) ) {
 740                  $bp->activity->track = bp_activity_get_post_types_tracking_args();
 741              }
 742  
 743              // Post Type activities
 744              if ( ! empty( $bp->activity->track[ $activity_type ] ) ) {
 745                  if ( isset( $bp->activity->track[ $activity_type ]->activity_comment ) ) {
 746                      $retval = $bp->activity->track[ $activity_type ]->activity_comment;
 747                  }
 748  
 749                  // Eventually override with comment synchronization feature.
 750                  if ( isset( $bp->activity->track[ $activity_type ]->comments_tracking ) ) {
 751                      $retval = $bp->activity->track[ $activity_type ]->comments_tracking && ! bp_disable_blogforum_comments();
 752                  }
 753  
 754              // Retired Forums component
 755              } elseif ( 'new_forum_topic' === $activity_type || 'new_forum_post' === $activity_type ) {
 756                  $retval = ! bp_disable_blogforum_comments();
 757  
 758              // By Default, all other activity types are supporting comments.
 759              } else {
 760                  $retval = true;
 761              }
 762              break;
 763  
 764          /**
 765           * Does this activity type support `generated-content`?
 766           */
 767          case 'generated-content' :
 768              $activity_types = _bp_activity_get_types_by_support( 'generated-content' );
 769  
 770              $retval = in_array( $activity_type, $activity_types, true );
 771              break;
 772      }
 773  
 774      return $retval;
 775  }
 776  
 777  /**
 778   * Get a specific tracking argument for a given activity type
 779   *
 780   * @since 2.5.0
 781   *
 782   * @param  string       $activity_type the activity type.
 783   * @param  string       $arg           the key of the tracking argument.
 784   * @return mixed        the value of the tracking arg, false if not found.
 785   */
 786  function bp_activity_post_type_get_tracking_arg( $activity_type, $arg = '' ) {
 787      if ( empty( $activity_type ) || empty( $arg ) ) {
 788          return false;
 789      }
 790  
 791      $bp = buddypress();
 792  
 793      // Set the activity track global if not set yet.
 794      if ( empty( $bp->activity->track ) ) {
 795          $bp->activity->track = bp_activity_get_post_types_tracking_args();
 796      }
 797  
 798      if ( isset( $bp->activity->track[ $activity_type ]->{$arg} ) ) {
 799          return $bp->activity->track[ $activity_type ]->{$arg};
 800      } else {
 801          return false;
 802      }
 803  }
 804  
 805  /**
 806   * Get all components' activity actions, sorted by their position attribute.
 807   *
 808   * @since 2.2.0
 809   *
 810   * @return object Actions ordered by their position.
 811   */
 812  function bp_activity_get_actions() {
 813      $bp = buddypress();
 814  
 815      // Set the activity track global if not set yet.
 816      if ( empty( $bp->activity->track ) ) {
 817          $bp->activity->track = bp_activity_get_post_types_tracking_args();
 818      }
 819  
 820      // Create the actions for the post types, if they haven't already been created.
 821      if ( ! empty( $bp->activity->track ) ) {
 822          foreach ( $bp->activity->track as $post_type ) {
 823              if ( isset( $bp->activity->actions->{$post_type->component_id}->{$post_type->action_id} ) ) {
 824                  continue;
 825              }
 826  
 827              bp_activity_set_action(
 828                  $post_type->component_id,
 829                  $post_type->action_id,
 830                  $post_type->admin_filter,
 831                  $post_type->format_callback,
 832                  $post_type->front_filter,
 833                  $post_type->contexts,
 834                  $post_type->position
 835              );
 836          }
 837      }
 838  
 839      return $bp->activity->actions;
 840  }
 841  
 842  /**
 843   * Retrieve the current action from a component and key.
 844   *
 845   * @since 1.1.0
 846   *
 847   * @param string $component_id The unique string ID of the component.
 848   * @param string $key          The action key.
 849   * @return string|bool Action value if found, otherwise false.
 850   */
 851  function bp_activity_get_action( $component_id, $key ) {
 852  
 853      // Return false if any of the above values are not set.
 854      if ( empty( $component_id ) || empty( $key ) ) {
 855          return false;
 856      }
 857  
 858      $actions = bp_activity_get_actions();
 859      $retval  = false;
 860  
 861      if ( isset( $actions->{$component_id}->{$key} ) ) {
 862          $retval = $actions->{$component_id}->{$key};
 863      }
 864  
 865      /**
 866       * Filters the current action by component and key.
 867       *
 868       * @since 1.1.0
 869       *
 870       * @param string|bool $retval       The action key.
 871       * @param string      $component_id The unique string ID of the component.
 872       * @param string      $key          The action key.
 873       */
 874      return apply_filters( 'bp_activity_get_action', $retval, $component_id, $key );
 875  }
 876  
 877  /**
 878   * Fetch details of all registered activity types.
 879   *
 880   * @since 1.7.0
 881   *
 882   * @return array array( type => description ), ...
 883   */
 884  function bp_activity_get_types() {
 885      $actions  = array();
 886  
 887      // Walk through the registered actions, and build an array of actions/values.
 888      foreach ( bp_activity_get_actions() as $action ) {
 889          $action = array_values( (array) $action );
 890  
 891          for ( $i = 0, $i_count = count( $action ); $i < $i_count; $i++ ) {
 892              $actions[ $action[$i]['key'] ] = $action[$i]['value'];
 893          }
 894      }
 895  
 896      // This was a mis-named activity type from before BP 1.6.
 897      unset( $actions['friends_register_activity_action'] );
 898  
 899      /**
 900       * Filters the available activity types.
 901       *
 902       * @since 1.7.0
 903       *
 904       * @param array $actions Array of registered activity types.
 905       */
 906      return apply_filters( 'bp_activity_get_types', $actions );
 907  }
 908  
 909  /**
 910   * Returns the list of available BuddyPress activity types.
 911   *
 912   * @since 9.0.0
 913   *
 914   * @return array An array of activity type labels keyed by type names.
 915   */
 916  function bp_activity_get_types_list() {
 917      $actions_object = bp_activity_get_actions();
 918      $actions_array  = get_object_vars( $actions_object );
 919  
 920      $types = array();
 921      foreach ( $actions_array as $component => $actions ) {
 922          $new_types = wp_list_pluck( $actions, 'label', 'key' );
 923  
 924          if ( $types ) {
 925              // Makes sure activity types are unique.
 926              $new_types = array_diff_key( $new_types, $types );
 927  
 928              if ( 'friends' === $component ) {
 929                  $new_types = array_diff_key(
 930                      array(
 931                          'friendship_accepted'              => false,
 932                          'friendship_created'               => false,
 933                          'friends_register_activity_action' => false,
 934                      ),
 935                      $new_types
 936                  );
 937  
 938                  $new_types['friendship_accepted,friendship_created'] = __( 'Friendships', 'buddypress' );
 939              }
 940          }
 941  
 942          $types = array_merge( $types, $new_types );
 943      }
 944  
 945      /**
 946       * Filter here to edit the activity types list.
 947       *
 948       * @since 9.0.0
 949       *
 950       * @param array $types An array of activity type labels keyed by type names.
 951       */
 952      return apply_filters( 'bp_activity_get_types_list', $types );
 953  }
 954  
 955  /**
 956   * Gets the current activity context.
 957   *
 958   * The "context" is the current view type, corresponding roughly to the
 959   * current component. Use this context to determine which activity actions
 960   * should be permitted in the filter dropdown.
 961   *
 962   * @since 2.8.0
 963   *
 964   * @return string Activity context. 'member', 'member_groups', 'group', 'activity'.
 965   */
 966  function bp_activity_get_current_context() {
 967      // On member pages, default to 'member', unless this is a user's Groups activity.
 968      if ( bp_is_user() ) {
 969          if ( bp_is_active( 'groups' ) && bp_is_current_action( bp_get_groups_slug() ) ) {
 970              $context = 'member_groups';
 971          } else {
 972              $context = 'member';
 973          }
 974  
 975      // On individual group pages, default to 'group'.
 976      } elseif ( bp_is_active( 'groups' ) && bp_is_group() ) {
 977          $context = 'group';
 978  
 979      // 'activity' everywhere else.
 980      } else {
 981          $context = 'activity';
 982      }
 983  
 984      return $context;
 985  }
 986  
 987  /**
 988   * Gets a flat list of activity actions compatible with a given context.
 989   *
 990   * @since 2.8.0
 991   *
 992   * @param string $context Optional. Name of the context. Defaults to the current context.
 993   * @return array
 994   */
 995  function bp_activity_get_actions_for_context( $context = '' ) {
 996      if ( ! $context ) {
 997          $context = bp_activity_get_current_context();
 998      }
 999  
1000      $actions = array();
1001      foreach ( bp_activity_get_actions() as $component_actions ) {
1002          foreach ( $component_actions as $component_action ) {
1003              if ( in_array( $context, (array) $component_action['context'], true ) ) {
1004                  $actions[] = $component_action;
1005              }
1006          }
1007      }
1008  
1009      return $actions;
1010  }
1011  
1012  /** Favorites ****************************************************************/
1013  
1014  /**
1015   * Get a users favorite activity stream items.
1016   *
1017   * @since 1.2.0
1018   *
1019   * @param int $user_id ID of the user whose favorites are being queried.
1020   * @return array IDs of the user's favorite activity items.
1021   */
1022  function bp_activity_get_user_favorites( $user_id = 0 ) {
1023  
1024      // Fallback to logged in user if no user_id is passed.
1025      if ( empty( $user_id ) ) {
1026          $user_id = bp_displayed_user_id();
1027      }
1028  
1029      // Get favorites for user.
1030      $favs = bp_get_user_meta( $user_id, 'bp_favorite_activities', true );
1031  
1032      /**
1033       * Filters the favorited activity items for a specified user.
1034       *
1035       * @since 1.2.0
1036       *
1037       * @param array $favs Array of user's favorited activity items.
1038       */
1039      return apply_filters( 'bp_activity_get_user_favorites', $favs );
1040  }
1041  
1042  /**
1043   * Add an activity stream item as a favorite for a user.
1044   *
1045   * @since 1.2.0
1046   *
1047   * @param int $activity_id ID of the activity item being favorited.
1048   * @param int $user_id     ID of the user favoriting the activity item.
1049   * @return bool True on success, false on failure.
1050   */
1051  function bp_activity_add_user_favorite( $activity_id, $user_id = 0 ) {
1052  
1053      // Fallback to logged in user if no user_id is passed.
1054      if ( empty( $user_id ) ) {
1055          $user_id = bp_loggedin_user_id();
1056      }
1057  
1058      $my_favs = bp_get_user_meta( $user_id, 'bp_favorite_activities', true );
1059      if ( empty( $my_favs ) || ! is_array( $my_favs ) ) {
1060          $my_favs = array();
1061      }
1062  
1063      // Bail if the user has already favorited this activity item.
1064      if ( in_array( $activity_id, $my_favs ) ) {
1065          return false;
1066      }
1067  
1068      // Add to user's favorites.
1069      $my_favs[] = $activity_id;
1070  
1071      // Update the total number of users who have favorited this activity.
1072      $fav_count = bp_activity_get_meta( $activity_id, 'favorite_count' );
1073      $fav_count = !empty( $fav_count ) ? (int) $fav_count + 1 : 1;
1074  
1075      // Update user meta.
1076      bp_update_user_meta( $user_id, 'bp_favorite_activities', $my_favs );
1077  
1078      // Update activity meta counts.
1079      if ( bp_activity_update_meta( $activity_id, 'favorite_count', $fav_count ) ) {
1080  
1081          /**
1082           * Fires if bp_activity_update_meta() for favorite_count is successful and before returning a true value for success.
1083           *
1084           * @since 1.2.1
1085           *
1086           * @param int $activity_id ID of the activity item being favorited.
1087           * @param int $user_id     ID of the user doing the favoriting.
1088           */
1089          do_action( 'bp_activity_add_user_favorite', $activity_id, $user_id );
1090  
1091          // Success.
1092          return true;
1093  
1094      // Saving meta was unsuccessful for an unknown reason.
1095      } else {
1096  
1097          /**
1098           * Fires if bp_activity_update_meta() for favorite_count is unsuccessful and before returning a false value for failure.
1099           *
1100           * @since 1.5.0
1101           *
1102           * @param int $activity_id ID of the activity item being favorited.
1103           * @param int $user_id     ID of the user doing the favoriting.
1104           */
1105          do_action( 'bp_activity_add_user_favorite_fail', $activity_id, $user_id );
1106  
1107          return false;
1108      }
1109  }
1110  
1111  /**
1112   * Remove an activity stream item as a favorite for a user.
1113   *
1114   * @since 1.2.0
1115   *
1116   * @param int $activity_id ID of the activity item being unfavorited.
1117   * @param int $user_id     ID of the user unfavoriting the activity item.
1118   * @return bool True on success, false on failure.
1119   */
1120  function bp_activity_remove_user_favorite( $activity_id, $user_id = 0 ) {
1121  
1122      // Fallback to logged in user if no user_id is passed.
1123      if ( empty( $user_id ) ) {
1124          $user_id = bp_loggedin_user_id();
1125      }
1126  
1127      $my_favs = bp_get_user_meta( $user_id, 'bp_favorite_activities', true );
1128      $my_favs = array_flip( (array) $my_favs );
1129  
1130      // Bail if the user has not previously favorited the item.
1131      if ( ! isset( $my_favs[ $activity_id ] ) ) {
1132          return false;
1133      }
1134  
1135      // Remove the fav from the user's favs.
1136      unset( $my_favs[$activity_id] );
1137      $my_favs = array_unique( array_flip( $my_favs ) );
1138  
1139      // Update the total number of users who have favorited this activity.
1140      $fav_count = bp_activity_get_meta( $activity_id, 'favorite_count' );
1141      if ( ! empty( $fav_count ) ) {
1142  
1143          // Deduct from total favorites.
1144          if ( bp_activity_update_meta( $activity_id, 'favorite_count', (int) $fav_count - 1 ) ) {
1145  
1146              // Update users favorites.
1147              if ( bp_update_user_meta( $user_id, 'bp_favorite_activities', $my_favs ) ) {
1148  
1149                  /**
1150                   * Fires if bp_update_user_meta() is successful and before returning a true value for success.
1151                   *
1152                   * @since 1.2.1
1153                   *
1154                   * @param int $activity_id ID of the activity item being unfavorited.
1155                   * @param int $user_id     ID of the user doing the unfavoriting.
1156                   */
1157                  do_action( 'bp_activity_remove_user_favorite', $activity_id, $user_id );
1158  
1159                  // Success.
1160                  return true;
1161  
1162              // Error updating.
1163              } else {
1164                  return false;
1165              }
1166  
1167          // Error updating favorite count.
1168          } else {
1169              return false;
1170          }
1171  
1172      // Error getting favorite count.
1173      } else {
1174          return false;
1175      }
1176  }
1177  
1178  /**
1179   * Check whether an activity item exists with a given content string.
1180   *
1181   * @since 1.1.0
1182   *
1183   * @param string $content The content to filter by.
1184   * @return int|null The ID of the located activity item. Null if none is found.
1185   */
1186  function bp_activity_check_exists_by_content( $content ) {
1187  
1188      /**
1189       * Filters the results of the check for whether an activity item exists by specified content.
1190       *
1191       * @since 1.1.0
1192       *
1193       * @param BP_Activity_Activity $value ID of the activity if found, else null.
1194       */
1195      return apply_filters( 'bp_activity_check_exists_by_content', BP_Activity_Activity::check_exists_by_content( $content ) );
1196  }
1197  
1198  /**
1199   * Retrieve the last time activity was updated.
1200   *
1201   * @since 1.0.0
1202   *
1203   * @return string Date last updated.
1204   */
1205  function bp_activity_get_last_updated() {
1206  
1207      /**
1208       * Filters the value for the last updated time for an activity item.
1209       *
1210       * @since 1.1.0
1211       *
1212       * @param BP_Activity_Activity $last_updated Date last updated.
1213       */
1214      return apply_filters( 'bp_activity_get_last_updated', BP_Activity_Activity::get_last_updated() );
1215  }
1216  
1217  /**
1218   * Retrieve the number of favorite activity stream items a user has.
1219   *
1220   * @since 1.2.0
1221   *
1222   * @param int $user_id ID of the user whose favorite count is being requested.
1223   * @return int Total favorite count for the user.
1224   */
1225  function bp_activity_total_favorites_for_user( $user_id = 0 ) {
1226  
1227      // Fallback on displayed user, and then logged in user.
1228      if ( empty( $user_id ) ) {
1229          $user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
1230      }
1231  
1232      return BP_Activity_Activity::total_favorite_count( $user_id );
1233  }
1234  
1235  /** Meta *********************************************************************/
1236  
1237  /**
1238   * Delete a meta entry from the DB for an activity stream item.
1239   *
1240   * @since 1.2.0
1241   *
1242   * @global object $wpdb WordPress database access object.
1243   *
1244   * @param int    $activity_id ID of the activity item whose metadata is being deleted.
1245   * @param string $meta_key    Optional. The key of the metadata being deleted. If
1246   *                            omitted, all metadata associated with the activity
1247   *                            item will be deleted.
1248   * @param string $meta_value  Optional. If present, the metadata will only be
1249   *                            deleted if the meta_value matches this parameter.
1250   * @param bool   $delete_all  Optional. If true, delete matching metadata entries
1251   *                            for all objects, ignoring the specified object_id. Otherwise,
1252   *                            only delete matching metadata entries for the specified
1253   *                            activity item. Default: false.
1254   * @return bool True on success, false on failure.
1255   */
1256  function bp_activity_delete_meta( $activity_id, $meta_key = '', $meta_value = '', $delete_all = false ) {
1257  
1258      // Legacy - if no meta_key is passed, delete all for the item.
1259      if ( empty( $meta_key ) ) {
1260          $all_meta = bp_activity_get_meta( $activity_id );
1261          $keys     = ! empty( $all_meta ) ? array_keys( $all_meta ) : array();
1262  
1263          // With no meta_key, ignore $delete_all.
1264          $delete_all = false;
1265      } else {
1266          $keys = array( $meta_key );
1267      }
1268  
1269      $retval = true;
1270  
1271      add_filter( 'query', 'bp_filter_metaid_column_name' );
1272      foreach ( $keys as $key ) {
1273          $retval = delete_metadata( 'activity', $activity_id, $key, $meta_value, $delete_all );
1274      }
1275      remove_filter( 'query', 'bp_filter_metaid_column_name' );
1276  
1277      return $retval;
1278  }
1279  
1280  /**
1281   * Get metadata for a given activity item.
1282   *
1283   * @since 1.2.0
1284   *
1285   * @param int    $activity_id ID of the activity item whose metadata is being requested.
1286   * @param string $meta_key    Optional. If present, only the metadata matching
1287   *                            that meta key will be returned. Otherwise, all metadata for the
1288   *                            activity item will be fetched.
1289   * @param bool   $single      Optional. If true, return only the first value of the
1290   *                            specified meta_key. This parameter has no effect if meta_key is not
1291   *                            specified. Default: true.
1292   * @return mixed The meta value(s) being requested.
1293   */
1294  function bp_activity_get_meta( $activity_id = 0, $meta_key = '', $single = true ) {
1295      add_filter( 'query', 'bp_filter_metaid_column_name' );
1296      $retval = get_metadata( 'activity', $activity_id, $meta_key, $single );
1297      remove_filter( 'query', 'bp_filter_metaid_column_name' );
1298  
1299      /**
1300       * Filters the metadata for a specified activity item.
1301       *
1302       * @since 1.5.0
1303       *
1304       * @param mixed  $retval      The meta values for the activity item.
1305       * @param int    $activity_id ID of the activity item.
1306       * @param string $meta_key    Meta key for the value being requested.
1307       * @param bool   $single      Whether to return one matched meta key row or all.
1308       */
1309      return apply_filters( 'bp_activity_get_meta', $retval, $activity_id, $meta_key, $single );
1310  }
1311  
1312  /**
1313   * Update a piece of activity meta.
1314   *
1315   * @since 1.2.0
1316   *
1317   * @param int    $activity_id ID of the activity item whose metadata is being updated.
1318   * @param string $meta_key    Key of the metadata being updated.
1319   * @param mixed  $meta_value  Value to be set.
1320   * @param mixed  $prev_value  Optional. If specified, only update existing metadata entries
1321   *                            with the specified value. Otherwise, update all entries.
1322   * @return bool|int Returns false on failure. On successful update of existing
1323   *                  metadata, returns true. On successful creation of new metadata,
1324   *                  returns the integer ID of the new metadata row.
1325   */
1326  function bp_activity_update_meta( $activity_id, $meta_key, $meta_value, $prev_value = '' ) {
1327      add_filter( 'query', 'bp_filter_metaid_column_name' );
1328      $retval = update_metadata( 'activity', $activity_id, $meta_key, $meta_value, $prev_value );
1329      remove_filter( 'query', 'bp_filter_metaid_column_name' );
1330  
1331      return $retval;
1332  }
1333  
1334  /**
1335   * Add a piece of activity metadata.
1336   *
1337   * @since 2.0.0
1338   *
1339   * @param int    $activity_id ID of the activity item.
1340   * @param string $meta_key    Metadata key.
1341   * @param mixed  $meta_value  Metadata value.
1342   * @param bool   $unique      Optional. Whether to enforce a single metadata value for the
1343   *                            given key. If true, and the object already has a value for
1344   *                            the key, no change will be made. Default: false.
1345   * @return int|bool The meta ID on successful update, false on failure.
1346   */
1347  function bp_activity_add_meta( $activity_id, $meta_key, $meta_value, $unique = false ) {
1348      add_filter( 'query', 'bp_filter_metaid_column_name' );
1349      $retval = add_metadata( 'activity', $activity_id, $meta_key, $meta_value, $unique );
1350      remove_filter( 'query', 'bp_filter_metaid_column_name' );
1351  
1352      return $retval;
1353  }
1354  
1355  /** Clean up *****************************************************************/
1356  
1357  /**
1358   * Completely remove a user's activity data.
1359   *
1360   * @since 1.5.0
1361   *
1362   * @param int $user_id ID of the user whose activity is being deleted.
1363   * @return bool
1364   */
1365  function bp_activity_remove_all_user_data( $user_id = 0 ) {
1366      if ( empty( $user_id ) ) {
1367          return false;
1368      }
1369  
1370      // Clear the user's activity from the sitewide stream and clear their activity tables.
1371      bp_activity_delete( array( 'user_id' => $user_id ) );
1372  
1373      // Remove any usermeta.
1374      bp_delete_user_meta( $user_id, 'bp_latest_update'       );
1375      bp_delete_user_meta( $user_id, 'bp_favorite_activities' );
1376  
1377      // Execute additional code
1378      do_action( 'bp_activity_remove_data', $user_id ); // Deprecated! Do not use!
1379  
1380      /**
1381       * Fires after the removal of all of a user's activity data.
1382       *
1383       * @since 1.5.0
1384       *
1385       * @param int $user_id ID of the user being deleted.
1386       */
1387      do_action( 'bp_activity_remove_all_user_data', $user_id );
1388  }
1389  add_action( 'wpmu_delete_user',  'bp_activity_remove_all_user_data' );
1390  
1391  /**
1392   * Deletes user activity data on the 'delete_user' hook.
1393   *
1394   * @since 6.0.0
1395   *
1396   * @param int $user_id The ID of the deleted user.
1397   */
1398  function bp_activity_remove_all_user_data_on_delete_user( $user_id ) {
1399      if ( ! bp_remove_user_data_on_delete_user_hook( 'activity', $user_id ) ) {
1400          return;
1401      }
1402  
1403      bp_activity_remove_all_user_data( $user_id );
1404  }
1405  add_action( 'delete_user', 'bp_activity_remove_all_user_data_on_delete_user' );
1406  
1407  /**
1408   * Mark all of the user's activity as spam.
1409   *
1410   * @since 1.6.0
1411   *
1412   * @global object $wpdb WordPress database access object.
1413   *
1414   * @param int $user_id ID of the user whose activity is being spammed.
1415   * @return bool
1416   */
1417  function bp_activity_spam_all_user_data( $user_id = 0 ) {
1418      global $wpdb;
1419  
1420      // Do not delete user data unless a logged in user says so.
1421      if ( empty( $user_id ) || ! is_user_logged_in() ) {
1422          return false;
1423      }
1424  
1425      // Get all the user's activities.
1426      $activities = bp_activity_get( array(
1427          'display_comments' => 'stream',
1428          'filter'           => array( 'user_id' => $user_id ),
1429          'show_hidden'      => true
1430      ) );
1431  
1432      $bp = buddypress();
1433  
1434      // Mark each as spam.
1435      foreach ( (array) $activities['activities'] as $activity ) {
1436  
1437          // Create an activity object.
1438          $activity_obj = new BP_Activity_Activity;
1439          foreach ( $activity as $k => $v ) {
1440              $activity_obj->$k = $v;
1441          }
1442  
1443          // Mark as spam.
1444          bp_activity_mark_as_spam( $activity_obj );
1445  
1446          /*
1447           * If Akismet is present, update the activity history meta.
1448           *
1449           * This is usually taken care of when BP_Activity_Activity::save() happens, but
1450           * as we're going to be updating all the activity statuses directly, for efficiency,
1451           * we need to update manually.
1452           */
1453          if ( ! empty( $bp->activity->akismet ) ) {
1454              $bp->activity->akismet->update_activity_spam_meta( $activity_obj );
1455          }
1456  
1457          // Tidy up.
1458          unset( $activity_obj );
1459      }
1460  
1461      // Mark all of this user's activities as spam.
1462      $wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET is_spam = 1 WHERE user_id = %d", $user_id ) );
1463  
1464      /**
1465       * Fires after all activity data from a user has been marked as spam.
1466       *
1467       * @since 1.6.0
1468       *
1469       * @param int   $user_id    ID of the user whose activity is being marked as spam.
1470       * @param array $activities Array of activity items being marked as spam.
1471       */
1472      do_action( 'bp_activity_spam_all_user_data', $user_id, $activities['activities'] );
1473  }
1474  add_action( 'bp_make_spam_user', 'bp_activity_spam_all_user_data' );
1475  
1476  /**
1477   * Mark all of the user's activity as ham (not spam).
1478   *
1479   * @since 1.6.0
1480   *
1481   * @global object $wpdb WordPress database access object.
1482   *
1483   * @param int $user_id ID of the user whose activity is being hammed.
1484   * @return bool
1485   */
1486  function bp_activity_ham_all_user_data( $user_id = 0 ) {
1487      global $wpdb;
1488  
1489      // Do not delete user data unless a logged in user says so.
1490      if ( empty( $user_id ) || ! is_user_logged_in() ) {
1491          return false;
1492      }
1493  
1494      // Get all the user's activities.
1495      $activities = bp_activity_get( array(
1496          'display_comments' => 'stream',
1497          'filter'           => array( 'user_id' => $user_id ),
1498          'show_hidden'      => true,
1499          'spam'             => 'all'
1500      ) );
1501  
1502      $bp = buddypress();
1503  
1504      // Mark each as not spam.
1505      foreach ( (array) $activities['activities'] as $activity ) {
1506  
1507          // Create an activity object.
1508          $activity_obj = new BP_Activity_Activity;
1509          foreach ( $activity as $k => $v ) {
1510              $activity_obj->$k = $v;
1511          }
1512  
1513          // Mark as not spam.
1514          bp_activity_mark_as_ham( $activity_obj );
1515  
1516          /*
1517           * If Akismet is present, update the activity history meta.
1518           *
1519           * This is usually taken care of when BP_Activity_Activity::save() happens, but
1520           * as we're going to be updating all the activity statuses directly, for efficiency,
1521           * we need to update manually.
1522           */
1523          if ( ! empty( $bp->activity->akismet ) ) {
1524              $bp->activity->akismet->update_activity_ham_meta( $activity_obj );
1525          }
1526  
1527          // Tidy up.
1528          unset( $activity_obj );
1529      }
1530  
1531      // Mark all of this user's activities as not spam.
1532      $wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET is_spam = 0 WHERE user_id = %d", $user_id ) );
1533  
1534      /**
1535       * Fires after all activity data from a user has been marked as ham.
1536       *
1537       * @since 1.6.0
1538       *
1539       * @param int   $user_id    ID of the user whose activity is being marked as ham.
1540       * @param array $activities Array of activity items being marked as ham.
1541       */
1542      do_action( 'bp_activity_ham_all_user_data', $user_id, $activities['activities'] );
1543  }
1544  add_action( 'bp_make_ham_user', 'bp_activity_ham_all_user_data' );
1545  
1546  /**
1547   * Allow core components and dependent plugins to register activity actions.
1548   *
1549   * @since 1.2.0
1550   */
1551  function bp_register_activity_actions() {
1552      /**
1553       * Fires on bp_init to allow core components and dependent plugins to register activity actions.
1554       *
1555       * @since 1.2.0
1556       */
1557      do_action( 'bp_register_activity_actions' );
1558  }
1559  add_action( 'bp_init', 'bp_register_activity_actions', 8 );
1560  
1561  /**
1562   * Register the activity stream actions for updates.
1563   *
1564   * @since 1.6.0
1565   */
1566  function bp_activity_register_activity_actions() {
1567      $bp = buddypress();
1568  
1569      bp_activity_set_action(
1570          $bp->activity->id,
1571          'activity_update',
1572          __( 'Posted a status update', 'buddypress' ),
1573          'bp_activity_format_activity_action_activity_update',
1574          __( 'Updates', 'buddypress' ),
1575          array( 'activity', 'group', 'member', 'member_groups' )
1576      );
1577  
1578      bp_activity_set_action(
1579          $bp->activity->id,
1580          'activity_comment',
1581          __( 'Replied to a status update', 'buddypress' ),
1582          'bp_activity_format_activity_action_activity_comment',
1583          __( 'Activity Comments', 'buddypress' )
1584      );
1585  
1586      /**
1587       * Fires at the end of the activity actions registration.
1588       *
1589       * Allows plugin authors to add their own activity actions alongside the core actions.
1590       *
1591       * @since 1.6.0
1592       */
1593      do_action( 'bp_activity_register_activity_actions' );
1594  
1595      // Backpat. Don't use this.
1596      do_action( 'updates_register_activity_actions' );
1597  }
1598  add_action( 'bp_register_activity_actions', 'bp_activity_register_activity_actions' );
1599  
1600  /**
1601   * Generate an activity action string for an activity item.
1602   *
1603   * @since 2.0.0
1604   *
1605   * @param object $activity Activity data object.
1606   * @return string|bool Returns false if no callback is found, otherwise returns
1607   *                     the formatted action string.
1608   */
1609  function bp_activity_generate_action_string( $activity ) {
1610  
1611      // Check for valid input.
1612      if ( empty( $activity->component ) || empty( $activity->type ) ) {
1613          return false;
1614      }
1615  
1616      // Check for registered format callback.
1617      $actions = bp_activity_get_actions();
1618      if ( empty( $actions->{$activity->component}->{$activity->type}['format_callback'] ) ) {
1619          return false;
1620      }
1621  
1622      // We apply the format_callback as a filter.
1623      add_filter( 'bp_activity_generate_action_string', $actions->{$activity->component}->{$activity->type}['format_callback'], 10, 2 );
1624  
1625      /**
1626       * Filters the string for the activity action being returned.
1627       *
1628       * @since 2.0.0
1629       *
1630       * @param BP_Activity_Activity $action   Action string being requested.
1631       * @param BP_Activity_Activity $activity Activity item object.
1632       */
1633      $action = apply_filters( 'bp_activity_generate_action_string', $activity->action, $activity );
1634  
1635      // Remove the filter for future activity items.
1636      remove_filter( 'bp_activity_generate_action_string', $actions->{$activity->component}->{$activity->type}['format_callback'], 10 );
1637  
1638      return $action;
1639  }
1640  
1641  /**
1642   * Format 'activity_update' activity actions.
1643   *
1644   * @since 2.0.0
1645   *
1646   * @param string $action   Static activity action.
1647   * @param object $activity Activity data object.
1648   * @return string $action
1649   */
1650  function bp_activity_format_activity_action_activity_update( $action, $activity ) {
1651      $action = sprintf(
1652          /* translators: %s: the activity author user link */
1653          esc_html__( '%s posted an update', 'buddypress' ),
1654          bp_core_get_userlink( $activity->user_id )
1655      );
1656  
1657      /**
1658       * Filters the formatted activity action update string.
1659       *
1660       * @since 1.2.0
1661       *
1662       * @param string               $action   Activity action string value.
1663       * @param BP_Activity_Activity $activity Activity item object.
1664       */
1665      return apply_filters( 'bp_activity_new_update_action', $action, $activity );
1666  }
1667  
1668  /**
1669   * Format 'activity_comment' activity actions.
1670   *
1671   * @since 2.0.0
1672   *
1673   * @param string $action   Static activity action.
1674   * @param object $activity Activity data object.
1675   * @return string $action
1676   */
1677  function bp_activity_format_activity_action_activity_comment( $action, $activity ) {
1678      $action = sprintf(
1679          /* translators: %s: the activity author user link */
1680          esc_html__( '%s posted a new activity comment', 'buddypress' ),
1681          bp_core_get_userlink( $activity->user_id )
1682      );
1683  
1684      /**
1685       * Filters the formatted activity action comment string.
1686       *
1687       * @since 1.2.0
1688       *
1689       * @param string               $action   Activity action string value.
1690       * @param BP_Activity_Activity $activity Activity item object.
1691       */
1692      return apply_filters( 'bp_activity_comment_action', $action, $activity );
1693  }
1694  
1695  /**
1696   * Format activity action strings for custom post types.
1697   *
1698   * @since 2.2.0
1699   *
1700   * @param string $action   Static activity action.
1701   * @param object $activity Activity data object.
1702   * @return string $action
1703   */
1704  function bp_activity_format_activity_action_custom_post_type_post( $action, $activity ) {
1705      $bp = buddypress();
1706  
1707      // Fetch all the tracked post types once.
1708      if ( empty( $bp->activity->track ) ) {
1709          $bp->activity->track = bp_activity_get_post_types_tracking_args();
1710      }
1711  
1712      if ( empty( $activity->type ) || empty( $bp->activity->track[ $activity->type ] ) ) {
1713          return $action;
1714      }
1715  
1716      $user_link = bp_core_get_userlink( $activity->user_id );
1717      $blog_url  = get_home_url( $activity->item_id );
1718  
1719      if ( empty( $activity->post_url ) ) {
1720          $post_url = add_query_arg( 'p', $activity->secondary_item_id, trailingslashit( $blog_url ) );
1721      } else {
1722          $post_url = $activity->post_url;
1723      }
1724  
1725      $post_link = '<a href="' . esc_url( $post_url ) . '">' . esc_html_x( 'item', 'Default text for the post type name', 'buddypress' ) . '</a>';
1726  
1727      if ( is_multisite() ) {
1728          $blog_link = '<a href="' . esc_url( $blog_url ) . '">' . esc_html( get_blog_option( $activity->item_id, 'blogname' ) ) . '</a>';
1729  
1730          if ( ! empty( $bp->activity->track[ $activity->type ]->new_post_type_action_ms ) ) {
1731              $action = sprintf( $bp->activity->track[ $activity->type ]->new_post_type_action_ms, $user_link, esc_url( $post_url ), $blog_link );
1732          } else {
1733              /* translators: 1: the activity author user link. 2: the post link. 3: the blog link. */
1734              $action = sprintf( esc_html_x( '%1$s wrote a new %2$s, on the site %3$s', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, $post_link, $blog_link );
1735          }
1736      } else {
1737          if ( ! empty( $bp->activity->track[ $activity->type ]->new_post_type_action ) ) {
1738              $action = sprintf( $bp->activity->track[ $activity->type ]->new_post_type_action, $user_link, $post_url );
1739          } else {
1740              /* translators: 1: the activity author user link. 2: the post link. */
1741              $action = sprintf( esc_html_x( '%1$s wrote a new %2$s', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, $post_link );
1742          }
1743      }
1744  
1745      /**
1746       * Filters the formatted custom post type activity post action string.
1747       *
1748       * @since 2.2.0
1749       *
1750       * @param string               $action   Activity action string value.
1751       * @param BP_Activity_Activity $activity Activity item object.
1752       */
1753      return apply_filters( 'bp_activity_custom_post_type_post_action', $action, $activity );
1754  }
1755  
1756  /**
1757   * Format activity action strings for custom post types comments.
1758   *
1759   * @since 2.5.0
1760   *
1761   * @param string $action   Static activity action.
1762   * @param object $activity Activity data object.
1763   *
1764   * @return string
1765   */
1766  function bp_activity_format_activity_action_custom_post_type_comment( $action, $activity ) {
1767      $bp = buddypress();
1768  
1769      // Fetch all the tracked post types once.
1770      if ( empty( $bp->activity->track ) ) {
1771          $bp->activity->track = bp_activity_get_post_types_tracking_args();
1772      }
1773  
1774      if ( empty( $activity->type ) || empty( $bp->activity->track[ $activity->type ] ) ) {
1775          return $action;
1776      }
1777  
1778      $user_link = bp_core_get_userlink( $activity->user_id );
1779      $post_link = '<a href="' . esc_url( $activity->primary_link ) . '">' . esc_html_x( 'item', 'Default text for the post type name', 'buddypress' ) . '</a>';
1780  
1781      if ( is_multisite() ) {
1782          $blog_link = '<a href="' . esc_url( get_home_url( $activity->item_id ) ) . '">' . get_blog_option( $activity->item_id, 'blogname' ) . '</a>';
1783  
1784          if ( ! empty( $bp->activity->track[ $activity->type ]->new_post_type_comment_action_ms ) ) {
1785              $action = sprintf( $bp->activity->track[ $activity->type ]->new_post_type_comment_action_ms, $user_link, $activity->primary_link, $blog_link );
1786          } else {
1787              /* translators: 1: the activity author user link. 2: the post link. 3: the blog link. */
1788              $action = sprintf( esc_html_x( '%1$s commented on the %2$s, on the site %3$s', 'Activity Custom Post Type comment action', 'buddypress' ), $user_link, $post_link, $blog_link );
1789          }
1790      } else {
1791          if ( ! empty( $bp->activity->track[ $activity->type ]->new_post_type_comment_action ) ) {
1792              $action = sprintf( $bp->activity->track[ $activity->type ]->new_post_type_comment_action, $user_link, $activity->primary_link );
1793          } else {
1794              /* translators: 1: the activity author user link. 2: the post link. */
1795              $action = sprintf( esc_html_x( '%1$s commented on the %2$s', 'Activity Custom Post Type post comment action', 'buddypress' ), $user_link, $post_link );
1796          }
1797      }
1798  
1799      /**
1800       * Filters the formatted custom post type activity comment action string.
1801       *
1802       * @since 2.5.0
1803       *
1804       * @param string               $action   Activity action string value.
1805       * @param BP_Activity_Activity $activity Activity item object.
1806       */
1807      return apply_filters( 'bp_activity_custom_post_type_comment_action', $action, $activity );
1808  }
1809  
1810  /*
1811   * Business functions are where all the magic happens in BuddyPress. They will
1812   * handle the actual saving or manipulation of information. Usually they will
1813   * hand off to a database class for data access, then return
1814   * true or false on success or failure.
1815   */
1816  
1817  /**
1818   * Retrieve an activity or activities.
1819   *
1820   * The bp_activity_get() function shares all arguments with BP_Activity_Activity::get().
1821   * The following is a list of bp_activity_get() parameters that have different
1822   * default values from BP_Activity_Activity::get() (value in parentheses is
1823   * the default for the bp_activity_get()).
1824   *   - 'per_page' (false)
1825   *
1826   * @since 1.2.0
1827   * @since 2.4.0 Introduced the `$fields` parameter.
1828   *
1829   * @see BP_Activity_Activity::get() For more information on accepted arguments
1830   *      and the format of the returned value.
1831   *
1832   * @param array|string $args See BP_Activity_Activity::get() for description.
1833   * @return array $activity See BP_Activity_Activity::get() for description.
1834   */
1835  function bp_activity_get( $args = '' ) {
1836  
1837      $r = bp_parse_args(
1838          $args,
1839          array(
1840              'max'               => false,        // Maximum number of results to return.
1841              'fields'            => 'all',
1842              'page'              => 1,            // Page 1 without a per_page will result in no pagination.
1843              'per_page'          => false,        // results per page.
1844              'sort'              => 'DESC',       // sort ASC or DESC.
1845              'display_comments'  => false,        // False for no comments. 'stream' for within stream display, 'threaded' for below each activity item.
1846              'search_terms'      => false,        // Pass search terms as a string.
1847              'meta_query'        => false,        // Filter by activity meta. See WP_Meta_Query for format.
1848              'date_query'        => false,        // Filter by date. See first parameter of WP_Date_Query for format.
1849              'filter_query'      => false,
1850              'show_hidden'       => false,        // Show activity items that are hidden site-wide?
1851              'exclude'           => false,        // Comma-separated list of activity IDs to exclude.
1852              'in'                => false,        // Comma-separated list or array of activity IDs to which you want to limit the query.
1853              'spam'              => 'ham_only',   // 'ham_only' (default), 'spam_only' or 'all'.
1854              'update_meta_cache' => true,
1855              'count_total'       => false,
1856              'count_total_only'  => false,
1857              'scope'             => false,
1858  
1859              /**
1860               * Pass filters as an array -- all filter items can be multiple values comma separated:
1861               * array(
1862               *     'user_id'      => false, // User ID to filter on.
1863               *     'object'       => false, // Object to filter on e.g. groups, profile, status, friends.
1864               *     'action'       => false, // Action to filter on e.g. activity_update, profile_updated.
1865               *     'primary_id'   => false, // Object ID to filter on e.g. a group_id or blog_id etc.
1866               *     'secondary_id' => false, // Secondary object ID to filter on e.g. a post_id.
1867               * );
1868               */
1869              'filter' => array()
1870          ),
1871          'activity_get'
1872      );
1873  
1874      $activity = BP_Activity_Activity::get(
1875          array(
1876              'page'              => $r['page'],
1877              'per_page'          => $r['per_page'],
1878              'max'               => $r['max'],
1879              'sort'              => $r['sort'],
1880              'search_terms'      => $r['search_terms'],
1881              'meta_query'        => $r['meta_query'],
1882              'date_query'        => $r['date_query'],
1883              'filter_query'      => $r['filter_query'],
1884              'filter'            => $r['filter'],
1885              'scope'             => $r['scope'],
1886              'display_comments'  => $r['display_comments'],
1887              'show_hidden'       => $r['show_hidden'],
1888              'exclude'           => $r['exclude'],
1889              'in'                => $r['in'],
1890              'spam'              => $r['spam'],
1891              'update_meta_cache' => $r['update_meta_cache'],
1892              'count_total'       => $r['count_total'],
1893              'count_total_only'  => $r['count_total_only'],
1894              'fields'            => $r['fields'],
1895          )
1896      );
1897  
1898      /**
1899       * Filters the requested activity item(s).
1900       *
1901       * @since 1.2.0
1902       *
1903       * @param BP_Activity_Activity $activity Requested activity object.
1904       * @param array                $r        Arguments used for the activity query.
1905       */
1906      return apply_filters_ref_array( 'bp_activity_get', array( &$activity, &$r ) );
1907  }
1908  
1909  /**
1910   * Fetch specific activity items.
1911   *
1912   * @since 1.2.0
1913   *
1914   * @see BP_Activity_Activity::get() For more information on accepted arguments.
1915   *
1916   * @param array|string $args {
1917   *     An array of arguments.
1918   *     All arguments and defaults are shared with BP_Activity_Activity::get(),
1919   *     except for the following:
1920   *     @type string|int|array Single activity ID, comma-separated list of IDs,
1921   *                            or array of IDs.
1922   * }
1923   * @return array See BP_Activity_Activity::get() for description.
1924   */
1925  function bp_activity_get_specific( $args = '' ) {
1926  
1927      $r = bp_parse_args(
1928          $args,
1929          array(
1930              'activity_ids'      => false,      // A single activity_id or array of IDs.
1931              'display_comments'  => false,      // True or false to display threaded comments for these specific activity items.
1932              'max'               => false,      // Maximum number of results to return.
1933              'page'              => 1,          // Page 1 without a per_page will result in no pagination.
1934              'per_page'          => false,      // Results per page.
1935              'show_hidden'       => true,       // When fetching specific items, show all.
1936              'sort'              => 'DESC',     // Sort ASC or DESC.
1937              'spam'              => 'ham_only', // Retrieve items marked as spam.
1938              'update_meta_cache' => true,
1939          ),
1940          'activity_get_specific'
1941      );
1942  
1943      $get_args = array(
1944          'display_comments'  => $r['display_comments'],
1945          'in'                => $r['activity_ids'],
1946          'max'               => $r['max'],
1947          'page'              => $r['page'],
1948          'per_page'          => $r['per_page'],
1949          'show_hidden'       => $r['show_hidden'],
1950          'sort'              => $r['sort'],
1951          'spam'              => $r['spam'],
1952          'update_meta_cache' => $r['update_meta_cache'],
1953      );
1954  
1955      $activity = BP_Activity_Activity::get( $get_args );
1956  
1957      /**
1958       * Filters the requested specific activity item.
1959       *
1960       * @since 1.2.0
1961       *
1962       * @param array $activity The array returned has two keys: total and activity.
1963       * @param array $args     Original passed in arguments.
1964       * @param array $get_args Constructed arguments used with request.
1965       */
1966      return apply_filters( 'bp_activity_get_specific', $activity, $args, $get_args );
1967  }
1968  
1969  /**
1970   * Add an activity item.
1971   *
1972   * @since 1.1.0
1973   * @since 2.6.0 Added 'error_type' parameter to $args.
1974   *
1975   * @param array|string $args {
1976   *     An array of arguments.
1977   *     @type int|bool $id                Pass an activity ID to update an existing item, or
1978   *                                       false to create a new item. Default: false.
1979   *     @type string   $action            Optional. The activity action/description, typically
1980   *                                       something like "Joe posted an update". Values passed to this param
1981   *                                       will be stored in the database and used as a fallback for when the
1982   *                                       activity item's format_callback cannot be found (eg, when the
1983   *                                       component is disabled). As long as you have registered a
1984   *                                       format_callback for your $type, it is unnecessary to include this
1985   *                                       argument - BP will generate it automatically.
1986   *                                       See {@link bp_activity_set_action()}.
1987   *     @type string   $content           Optional. The content of the activity item.
1988   *     @type string   $component         The unique name of the component associated with
1989   *                                       the activity item - 'groups', 'profile', etc.
1990   *     @type string   $type              The specific activity type, used for directory
1991   *                                       filtering. 'new_blog_post', 'activity_update', etc.
1992   *     @type string   $primary_link      Optional. The URL for this item, as used in
1993   *                                       RSS feeds. Defaults to the URL for this activity
1994   *                                       item's permalink page.
1995   *     @type int|bool $user_id           Optional. The ID of the user associated with the activity
1996   *                                       item. May be set to false or 0 if the item is not related
1997   *                                       to any user. Default: the ID of the currently logged-in user.
1998   *     @type int      $item_id           Optional. The ID of the associated item.
1999   *     @type int      $secondary_item_id Optional. The ID of a secondary associated item.
2000   *     @type string   $date_recorded     Optional. The GMT time, in Y-m-d h:i:s format, when
2001   *                                       the item was recorded. Defaults to the current time.
2002   *     @type bool     $hide_sitewide     Should the item be hidden on sitewide streams?
2003   *                                       Default: false.
2004   *     @type bool     $is_spam           Should the item be marked as spam? Default: false.
2005   *     @type string   $error_type        Optional. Error type. Either 'bool' or 'wp_error'. Default: 'bool'.
2006   * }
2007   * @return WP_Error|bool|int The ID of the activity on success. False on error.
2008   */
2009  function bp_activity_add( $args = '' ) {
2010  
2011      $r = bp_parse_args(
2012          $args,
2013          array(
2014              'id'                => false,                  // Pass an existing activity ID to update an existing entry.
2015              'action'            => '',                     // The activity action - e.g. "Jon Doe posted an update".
2016              'content'           => '',                     // Optional: The content of the activity item e.g. "BuddyPress is awesome guys!".
2017              'component'         => false,                  // The name/ID of the component e.g. groups, profile, mycomponent.
2018              'type'              => false,                  // The activity type e.g. activity_update, profile_updated.
2019              'primary_link'      => '',                     // Optional: The primary URL for this item in RSS feeds (defaults to activity permalink).
2020              'user_id'           => bp_loggedin_user_id(),  // Optional: The user to record the activity for, can be false if this activity is not for a user.
2021              'item_id'           => false,                  // Optional: The ID of the specific item being recorded, e.g. a blog_id.
2022              'secondary_item_id' => false,                  // Optional: A second ID used to further filter e.g. a comment_id.
2023              'recorded_time'     => bp_core_current_time(), // The GMT time that this activity was recorded.
2024              'hide_sitewide'     => false,                  // Should this be hidden on the sitewide activity stream?
2025              'is_spam'           => false,                  // Is this activity item to be marked as spam?
2026              'error_type'        => 'bool',
2027          ),
2028          'activity_add'
2029      );
2030  
2031      // Make sure we are backwards compatible.
2032      if ( empty( $r['component'] ) && ! empty( $r['component_name'] ) ) {
2033          $r['component'] = $r['component_name'];
2034      }
2035  
2036      if ( empty( $r['type'] ) && ! empty( $r['component_action'] ) ) {
2037          $r['type'] = $r['component_action'];
2038      }
2039  
2040      // Setup activity to be added.
2041      $activity                    = new BP_Activity_Activity( $r['id'] );
2042      $activity->user_id           = $r['user_id'];
2043      $activity->component         = $r['component'];
2044      $activity->type              = $r['type'];
2045      $activity->content           = $r['content'];
2046      $activity->primary_link      = $r['primary_link'];
2047      $activity->item_id           = $r['item_id'];
2048      $activity->secondary_item_id = $r['secondary_item_id'];
2049      $activity->date_recorded     = $r['recorded_time'];
2050      $activity->hide_sitewide     = $r['hide_sitewide'];
2051      $activity->is_spam           = $r['is_spam'];
2052      $activity->error_type        = $r['error_type'];
2053      $activity->action            = ! empty( $r['action'] )
2054                          ? $r['action']
2055                          : bp_activity_generate_action_string( $activity );
2056  
2057      $save = $activity->save();
2058  
2059      if ( 'wp_error' === $r['error_type'] && is_wp_error( $save ) ) {
2060          return $save;
2061      } elseif ( 'bool' === $r['error_type'] && false === $save ) {
2062          return false;
2063      }
2064  
2065      // If this is an activity comment, rebuild the tree.
2066      if ( 'activity_comment' === $activity->type ) {
2067          // Also clear the comment cache for the parent activity ID.
2068          wp_cache_delete( $activity->item_id, 'bp_activity_comments' );
2069  
2070          BP_Activity_Activity::rebuild_activity_comment_tree( $activity->item_id );
2071      }
2072  
2073      wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
2074  
2075      /**
2076       * Fires at the end of the execution of adding a new activity item, before returning the new activity item ID.
2077       *
2078       * @since 1.1.0
2079       * @since 4.0.0 Added the `$activity_id` parameter.
2080       *
2081       * @param array $r           Array of parsed arguments for the activity item being added.
2082       * @param int   $activity_id The id of the activity item being added.
2083       */
2084      do_action( 'bp_activity_add', $r, $activity->id );
2085  
2086      return $activity->id;
2087  }
2088  
2089  /**
2090   * Post an activity update.
2091   *
2092   * @since 1.2.0
2093   *
2094   * @param array|string $args {
2095   *     An array of arguments.
2096   *     @type string $content    The content of the activity update.
2097   *     @type int    $user_id    Optional. Defaults to the logged-in user.
2098   *     @type string $error_type Optional. Error type to return. Either 'bool' or 'wp_error'. Defaults to
2099   *                              'bool' for boolean. 'wp_error' will return a WP_Error object.
2100   * }
2101   * @return int|bool|WP_Error $activity_id The activity id on success. On failure, either boolean false or WP_Error
2102   *                                        object depending on the 'error_type' $args parameter.
2103   */
2104  function bp_activity_post_update( $args = '' ) {
2105  
2106      $r = bp_parse_args(
2107          $args,
2108          array(
2109              'content'    => false,
2110              'user_id'    => bp_loggedin_user_id(),
2111              'error_type' => 'bool',
2112          )
2113      );
2114  
2115      if ( empty( $r['content'] ) || ! strlen( trim( $r['content'] ) ) ) {
2116          if ( 'wp_error' === $r['error_type'] ) {
2117              return new WP_Error( 'bp_activity_missing_content', __( 'Please enter some content to post.', 'buddypress' ) );
2118          }
2119  
2120          return false;
2121      }
2122  
2123      if ( bp_is_user_inactive( $r['user_id'] ) ) {
2124          if ( 'wp_error' === $r['error_type'] ) {
2125              return new WP_Error( 'bp_activity_inactive_user', __( 'User account has not yet been activated.', 'buddypress' ) );
2126          }
2127  
2128          return false;
2129      }
2130  
2131      // Record this on the user's profile.
2132      $activity_content = $r['content'];
2133      $primary_link     = bp_core_get_userlink( $r['user_id'], false, true );
2134  
2135      /**
2136       * Filters the new activity content for current activity item.
2137       *
2138       * @since 1.2.0
2139       *
2140       * @param string $activity_content Activity content posted by user.
2141       */
2142      $add_content = apply_filters( 'bp_activity_new_update_content', $activity_content );
2143  
2144      /**
2145       * Filters the activity primary link for current activity item.
2146       *
2147       * @since 1.2.0
2148       *
2149       * @param string $primary_link Link to the profile for the user who posted the activity.
2150       */
2151      $add_primary_link = apply_filters( 'bp_activity_new_update_primary_link', $primary_link );
2152  
2153      // Now write the values.
2154      $activity_id = bp_activity_add( array(
2155          'user_id'      => $r['user_id'],
2156          'content'      => $add_content,
2157          'primary_link' => $add_primary_link,
2158          'component'    => buddypress()->activity->id,
2159          'type'         => 'activity_update',
2160          'error_type'   => $r['error_type']
2161      ) );
2162  
2163      // Bail on failure.
2164      if ( false === $activity_id || is_wp_error( $activity_id ) ) {
2165          return $activity_id;
2166      }
2167  
2168      /**
2169       * Filters the latest update content for the activity item.
2170       *
2171       * @since 1.6.0
2172       *
2173       * @param string $r                Content of the activity update.
2174       * @param string $activity_content Content of the activity update.
2175       */
2176      $activity_content = apply_filters( 'bp_activity_latest_update_content', $r['content'], $activity_content );
2177  
2178      // Add this update to the "latest update" usermeta so it can be fetched anywhere.
2179      bp_update_user_meta( bp_loggedin_user_id(), 'bp_latest_update', array(
2180          'id'      => $activity_id,
2181          'content' => $activity_content
2182      ) );
2183  
2184      /**
2185       * Fires at the end of an activity post update, before returning the updated activity item ID.
2186       *
2187       * @since 1.2.0
2188       *
2189       * @param string $content     Content of the activity post update.
2190       * @param int    $user_id     ID of the user posting the activity update.
2191       * @param int    $activity_id ID of the activity item being updated.
2192       */
2193      do_action( 'bp_activity_posted_update', $r['content'], $r['user_id'], $activity_id );
2194  
2195      return $activity_id;
2196  }
2197  
2198  /**
2199   * Create an activity item for a newly published post type post.
2200   *
2201   * @since 2.2.0
2202   *
2203   * @param int          $post_id ID of the new post.
2204   * @param WP_Post|null $post    Post object.
2205   * @param int          $user_id ID of the post author.
2206   * @return null|WP_Error|bool|int The ID of the activity on success. False on error.
2207   */
2208  function bp_activity_post_type_publish( $post_id = 0, $post = null, $user_id = 0 ) {
2209  
2210      if ( ! is_a( $post, 'WP_Post' ) ) {
2211          return;
2212      }
2213  
2214      // Get the post type tracking args.
2215      $activity_post_object = bp_activity_get_post_type_tracking_args( $post->post_type );
2216  
2217      if ( 'publish' != $post->post_status || ! empty( $post->post_password ) || empty( $activity_post_object->action_id ) ) {
2218          return;
2219      }
2220  
2221      if ( empty( $post_id ) ) {
2222          $post_id = $post->ID;
2223      }
2224  
2225      $blog_id = get_current_blog_id();
2226  
2227      if ( empty( $user_id ) ) {
2228          $user_id = (int) $post->post_author;
2229      }
2230  
2231      // Bail if an activity item already exists for this post.
2232      $existing = bp_activity_get( array(
2233          'filter' => array(
2234              'action'       => $activity_post_object->action_id,
2235              'primary_id'   => $blog_id,
2236              'secondary_id' => $post_id,
2237          )
2238      ) );
2239  
2240      if ( ! empty( $existing['activities'] ) ) {
2241          return;
2242      }
2243  
2244      /**
2245       * Filters whether or not to post the activity.
2246       *
2247       * This is a variable filter, dependent on the post type,
2248       * that lets components or plugins bail early if needed.
2249       *
2250       * @since 2.2.0
2251       *
2252       * @param bool $value   Whether or not to continue.
2253       * @param int  $blog_id ID of the current site.
2254       * @param int  $post_id ID of the current post being published.
2255       * @param int  $user_id ID of the current user or post author.
2256       */
2257      if ( false === apply_filters( "bp_activity_{$post->post_type}_pre_publish", true, $blog_id, $post_id, $user_id ) ) {
2258          return;
2259      }
2260  
2261      // Record this in activity streams.
2262      $blog_url = get_home_url( $blog_id );
2263      $post_url = add_query_arg(
2264          'p',
2265          $post_id,
2266          trailingslashit( $blog_url )
2267      );
2268  
2269      // Backward compatibility filters for the 'blogs' component.
2270      if ( 'blogs' == $activity_post_object->component_id )  {
2271          $activity_content      = apply_filters( 'bp_blogs_activity_new_post_content', $post->post_content, $post, $post_url, $post->post_type );
2272          $activity_primary_link = apply_filters( 'bp_blogs_activity_new_post_primary_link', $post_url, $post_id, $post->post_type );
2273      } else {
2274          $activity_content      = $post->post_content;
2275          $activity_primary_link = $post_url;
2276      }
2277  
2278      $activity_args = array(
2279          'user_id'           => $user_id,
2280          'content'           => $activity_content,
2281          'primary_link'      => $activity_primary_link,
2282          'component'         => $activity_post_object->component_id,
2283          'type'              => $activity_post_object->action_id,
2284          'item_id'           => $blog_id,
2285          'secondary_item_id' => $post_id,
2286          'recorded_time'     => $post->post_date_gmt,
2287      );
2288  
2289      if ( ! empty( $activity_args['content'] ) ) {
2290          // Create the excerpt.
2291          $activity_summary = bp_activity_create_summary( $activity_args['content'], $activity_args );
2292  
2293          // Backward compatibility filter for blog posts.
2294          if ( 'blogs' == $activity_post_object->component_id )  {
2295              $activity_args['content'] = apply_filters( 'bp_blogs_record_activity_content', $activity_summary, $activity_args['content'], $activity_args, $post->post_type );
2296          } else {
2297              $activity_args['content'] = $activity_summary;
2298          }
2299      }
2300  
2301      // Set up the action by using the format functions.
2302      $action_args = array_merge( $activity_args, array(
2303          'post_title' => $post->post_title,
2304          'post_url'   => $post_url,
2305      ) );
2306  
2307      $activity_args['action'] = call_user_func_array( $activity_post_object->format_callback, array( '', (object) $action_args ) );
2308  
2309      // Make sure the action is set.
2310      if ( empty( $activity_args['action'] ) ) {
2311          return;
2312      } else {
2313          // Backward compatibility filter for the blogs component.
2314          if ( 'blogs' == $activity_post_object->component_id )  {
2315              $activity_args['action'] = apply_filters( 'bp_blogs_record_activity_action', $activity_args['action'] );
2316          }
2317      }
2318  
2319      $activity_id = bp_activity_add( $activity_args );
2320  
2321      /**
2322       * Fires after the publishing of an activity item for a newly published post type post.
2323       *
2324       * @since 2.2.0
2325       *
2326       * @param int     $activity_id   ID of the newly published activity item.
2327       * @param WP_Post $post          Post object.
2328       * @param array   $activity_args Array of activity arguments.
2329       */
2330      do_action( 'bp_activity_post_type_published', $activity_id, $post, $activity_args );
2331  
2332      return $activity_id;
2333  }
2334  
2335  /**
2336   * Update the activity item for a custom post type entry.
2337   *
2338   * @since 2.2.0
2339   *
2340   * @param WP_Post|null $post Post item.
2341   * @return null|WP_Error|bool True on success, false on failure.
2342   */
2343  function bp_activity_post_type_update( $post = null ) {
2344  
2345      if ( ! is_a( $post, 'WP_Post' ) ) {
2346          return;
2347      }
2348  
2349      // Get the post type tracking args.
2350      $activity_post_object = bp_activity_get_post_type_tracking_args( $post->post_type );
2351  
2352      if ( empty( $activity_post_object->action_id ) ) {
2353          return;
2354      }
2355  
2356      $activity_id = bp_activity_get_activity_id( array(
2357          'component'         => $activity_post_object->component_id,
2358          'item_id'           => get_current_blog_id(),
2359          'secondary_item_id' => $post->ID,
2360          'type'              => $activity_post_object->action_id,
2361      ) );
2362  
2363      // Activity ID doesn't exist, so stop!
2364      if ( empty( $activity_id ) ) {
2365          return;
2366      }
2367  
2368      // Delete the activity if the post was updated with a password.
2369      if ( ! empty( $post->post_password ) ) {
2370          bp_activity_delete( array( 'id' => $activity_id ) );
2371      }
2372  
2373      // Update the activity entry.
2374      $activity = new BP_Activity_Activity( $activity_id );
2375  
2376      // Check if the Post author has changed.
2377      if ( (int) $post->post_author !== (int) $activity->user_id ) {
2378          $activity->user_id = (int) $post->post_author;
2379      }
2380  
2381      if ( ! empty( $post->post_content ) ) {
2382          $activity_summary = bp_activity_create_summary( $post->post_content, (array) $activity );
2383  
2384          // Backward compatibility filter for the blogs component.
2385          if ( 'blogs' == $activity_post_object->component_id ) {
2386              $activity->content = apply_filters( 'bp_blogs_record_activity_content', $activity_summary, $post->post_content, (array) $activity, $post->post_type );
2387          } else {
2388              $activity->content = $activity_summary;
2389          }
2390      }
2391  
2392      // Save the updated activity.
2393      $updated = $activity->save();
2394  
2395      /**
2396       * Fires after the updating of an activity item for a custom post type entry.
2397       *
2398       * @since 2.2.0
2399       * @since 2.5.0 Add the post type tracking args parameter
2400       *
2401       * @param WP_Post              $post                 Post object.
2402       * @param BP_Activity_Activity $activity             Activity object.
2403       * @param object               $activity_post_object The post type tracking args object.
2404       */
2405      do_action( 'bp_activity_post_type_updated', $post, $activity, $activity_post_object );
2406  
2407      return $updated;
2408  }
2409  
2410  /**
2411   * Unpublish an activity for the custom post type.
2412   *
2413   * @since 2.2.0
2414   *
2415   * @param int          $post_id ID of the post being unpublished.
2416   * @param WP_Post|null $post    Post object.
2417   * @return bool True on success, false on failure.
2418   */
2419  function bp_activity_post_type_unpublish( $post_id = 0, $post = null ) {
2420  
2421      if ( ! is_a( $post, 'WP_Post' ) ) {
2422          return;
2423      }
2424  
2425      // Get the post type tracking args.
2426      $activity_post_object = bp_activity_get_post_type_tracking_args( $post->post_type );
2427  
2428      if ( empty( $activity_post_object->action_id ) ) {
2429          return;
2430      }
2431  
2432      if ( empty( $post_id ) ) {
2433          $post_id = $post->ID;
2434      }
2435  
2436      $delete_activity_args = array(
2437          'item_id'           => get_current_blog_id(),
2438          'secondary_item_id' => $post_id,
2439          'component'         => $activity_post_object->component_id,
2440          'type'              => $activity_post_object->action_id,
2441          'user_id'           => false,
2442      );
2443  
2444      $deleted = bp_activity_delete_by_item_id( $delete_activity_args );
2445  
2446      /**
2447       * Fires after the unpublishing for the custom post type.
2448       *
2449       * @since 2.2.0
2450       *
2451       * @param array   $delete_activity_args Array of arguments for activity deletion.
2452       * @param WP_Post $post                 Post object.
2453       * @param bool    $activity             Whether or not the activity was successfully deleted.
2454       */
2455      do_action( 'bp_activity_post_type_unpublished', $delete_activity_args, $post, $deleted );
2456  
2457      return $deleted;
2458  }
2459  
2460  /**
2461   * Create an activity item for a newly posted post type comment.
2462   *
2463   * @since 2.5.0
2464   *
2465   * @param  int         $comment_id           ID of the comment.
2466   * @param  bool        $is_approved          Whether the comment is approved or not.
2467   * @param  object|null $activity_post_object The post type tracking args object.
2468   * @return null|WP_Error|bool|int The ID of the activity on success. False on error.
2469   */
2470  function bp_activity_post_type_comment( $comment_id = 0, $is_approved = true, $activity_post_object = null ) {
2471      // Get the users comment.
2472      $post_type_comment = get_comment( $comment_id );
2473  
2474      // Don't record activity if the comment hasn't been approved.
2475      if ( empty( $is_approved ) ) {
2476          return false;
2477      }
2478  
2479      // Don't record activity if no email address has been included.
2480      if ( empty( $post_type_comment->comment_author_email ) ) {
2481          return false;
2482      }
2483  
2484      // Don't record activity if the comment has already been marked as spam.
2485      if ( 'spam' === $is_approved ) {
2486          return false;
2487      }
2488  
2489      // Get the user by the comment author email.
2490      $user = get_user_by( 'email', $post_type_comment->comment_author_email );
2491  
2492      // If user isn't registered, don't record activity.
2493      if ( empty( $user ) ) {
2494          return false;
2495      }
2496  
2497      // Get the user_id.
2498      $user_id = (int) $user->ID;
2499  
2500      // Get blog and post data.
2501      $blog_id = get_current_blog_id();
2502  
2503      // Get the post.
2504      $post_type_comment->post = get_post( $post_type_comment->comment_post_ID );
2505  
2506      if ( ! is_a( $post_type_comment->post, 'WP_Post' ) ) {
2507          return false;
2508      }
2509  
2510      /**
2511       * Filters whether to publish activities about the comment regarding the post status
2512       *
2513       * @since 2.5.0
2514       *
2515       * @param bool true to bail, false otherwise.
2516       */
2517      $is_post_status_not_allowed = (bool) apply_filters( 'bp_activity_post_type_is_post_status_allowed', 'publish' !== $post_type_comment->post->post_status || ! empty( $post_type_comment->post->post_password ) );
2518  
2519      // If this is a password protected post, or not a public post don't record the comment.
2520      if ( $is_post_status_not_allowed ) {
2521          return false;
2522      }
2523  
2524      // Set post type.
2525      $post_type = $post_type_comment->post->post_type;
2526  
2527      if ( empty( $activity_post_object ) ) {
2528          // Get the post type tracking args.
2529          $activity_post_object = bp_activity_get_post_type_tracking_args( $post_type );
2530  
2531          // Bail if the activity type does not exist.
2532          if ( empty( $activity_post_object->comments_tracking->action_id ) ) {
2533              return false;
2534          }
2535      }
2536  
2537      // Set the $activity_comment_object.
2538      $activity_comment_object = $activity_post_object->comments_tracking;
2539  
2540      /**
2541       * Filters whether or not to post the activity about the comment.
2542       *
2543       * This is a variable filter, dependent on the post type,
2544       * that lets components or plugins bail early if needed.
2545       *
2546       * @since 2.5.0
2547       *
2548       * @param bool $value      Whether or not to continue.
2549       * @param int  $blog_id    ID of the current site.
2550       * @param int  $post_id    ID of the current post being commented.
2551       * @param int  $user_id    ID of the current user.
2552       * @param int  $comment_id ID of the current comment being posted.
2553       */
2554      if ( false === apply_filters( "bp_activity_{$post_type}_pre_comment", true, $blog_id, $post_type_comment->post->ID, $user_id, $comment_id ) ) {
2555          return false;
2556      }
2557  
2558      // Is this an update ?
2559      $activity_id = bp_activity_get_activity_id( array(
2560          'user_id'           => $user_id,
2561          'component'         => $activity_comment_object->component_id,
2562          'type'              => $activity_comment_object->action_id,
2563          'item_id'           => $blog_id,
2564          'secondary_item_id' => $comment_id,
2565      ) );
2566  
2567      // Record this in activity streams.
2568      $comment_link = get_comment_link( $post_type_comment->comment_ID );
2569  
2570      // Backward compatibility filters for the 'blogs' component.
2571      if ( 'blogs' == $activity_comment_object->component_id )  {
2572          $activity_content      = apply_filters_ref_array( 'bp_blogs_activity_new_comment_content',      array( $post_type_comment->comment_content, &$post_type_comment, $comment_link ) );
2573          $activity_primary_link = apply_filters_ref_array( 'bp_blogs_activity_new_comment_primary_link', array( $comment_link, &$post_type_comment ) );
2574      } else {
2575          $activity_content      = $post_type_comment->comment_content;
2576          $activity_primary_link = $comment_link;
2577      }
2578  
2579      $activity_args = array(
2580          'id'            => $activity_id,
2581          'user_id'       => $user_id,
2582          'content'       => $activity_content,
2583          'primary_link'  => $activity_primary_link,
2584          'component'     => $activity_comment_object->component_id,
2585          'recorded_time' => $post_type_comment->comment_date_gmt,
2586      );
2587  
2588      if ( bp_disable_blogforum_comments() ) {
2589          $blog_url = get_home_url( $blog_id );
2590          $post_url = add_query_arg(
2591              'p',
2592              $post_type_comment->post->ID,
2593              trailingslashit( $blog_url )
2594          );
2595  
2596          $activity_args['type']              = $activity_comment_object->action_id;
2597          $activity_args['item_id']           = $blog_id;
2598          $activity_args['secondary_item_id'] = $post_type_comment->comment_ID;
2599  
2600          if ( ! empty( $activity_args['content'] ) ) {
2601              // Create the excerpt.
2602              $activity_summary = bp_activity_create_summary( $activity_args['content'], $activity_args );
2603  
2604              // Backward compatibility filter for blog comments.
2605              if ( 'blogs' == $activity_post_object->component_id )  {
2606                  $activity_args['content'] = apply_filters( 'bp_blogs_record_activity_content', $activity_summary, $activity_args['content'], $activity_args, $post_type );
2607              } else {
2608                  $activity_args['content'] = $activity_summary;
2609              }
2610          }
2611  
2612          // Set up the action by using the format functions.
2613          $action_args = array_merge( $activity_args, array(
2614              'post_title' => $post_type_comment->post->post_title,
2615              'post_url'   => $post_url,
2616              'blog_url'   => $blog_url,
2617              'blog_name'  => get_blog_option( $blog_id, 'blogname' ),
2618          ) );
2619  
2620          $activity_args['action'] = call_user_func_array( $activity_comment_object->format_callback, array( '', (object) $action_args ) );
2621  
2622          // Make sure the action is set.
2623          if ( empty( $activity_args['action'] ) ) {
2624              return;
2625          } else {
2626              // Backward compatibility filter for the blogs component.
2627              if ( 'blogs' === $activity_post_object->component_id )  {
2628                  $activity_args['action'] = apply_filters( 'bp_blogs_record_activity_action', $activity_args['action'] );
2629              }
2630          }
2631  
2632          $activity_id = bp_activity_add( $activity_args );
2633      }
2634  
2635      /**
2636       * Fires after the publishing of an activity item for a newly published post type post.
2637       *
2638       * @since 2.5.0
2639       *
2640       * @param int        $activity_id          ID of the newly published activity item.
2641       * @param WP_Comment $post_type_comment    Comment object.
2642       * @param array      $activity_args        Array of activity arguments.
2643       * @param object     $activity_post_object the post type tracking args object.
2644       */
2645      do_action_ref_array( 'bp_activity_post_type_comment', array( &$activity_id, $post_type_comment, $activity_args, $activity_post_object ) );
2646  
2647      return $activity_id;
2648  }
2649  add_action( 'comment_post', 'bp_activity_post_type_comment', 10, 2 );
2650  add_action( 'edit_comment', 'bp_activity_post_type_comment', 10    );
2651  
2652  /**
2653   * Remove an activity item when a comment about a post type is deleted.
2654   *
2655   * @since 2.5.0
2656   *
2657   * @param  int         $comment_id           ID of the comment.
2658   * @param  object|null $activity_post_object The post type tracking args object.
2659   * @return bool True on success. False on error.
2660   */
2661  function bp_activity_post_type_remove_comment( $comment_id = 0, $activity_post_object = null ) {
2662      if ( empty( $activity_post_object ) ) {
2663          $comment = get_comment( $comment_id );
2664          if ( ! $comment ) {
2665              return;
2666          }
2667  
2668          $post_type = get_post_type( $comment->comment_post_ID );
2669          if ( ! $post_type ) {
2670              return;
2671          }
2672  
2673          // Get the post type tracking args.
2674          $activity_post_object = bp_activity_get_post_type_tracking_args( $post_type );
2675  
2676          // Bail if the activity type does not exist.
2677          if ( empty( $activity_post_object->comments_tracking->action_id ) ) {
2678              return false;
2679          }
2680      }
2681  
2682      // Set the $activity_comment_object.
2683      $activity_comment_object = $activity_post_object->comments_tracking;
2684  
2685      if ( empty( $activity_comment_object->action_id ) ) {
2686          return false;
2687      }
2688  
2689      $deleted = false;
2690  
2691      if ( bp_disable_blogforum_comments() ) {
2692          $deleted = bp_activity_delete_by_item_id( array(
2693              'item_id'           => get_current_blog_id(),
2694              'secondary_item_id' => $comment_id,
2695              'component'         => $activity_comment_object->component_id,
2696              'type'              => $activity_comment_object->action_id,
2697              'user_id'           => false,
2698          ) );
2699      }
2700  
2701      /**
2702       * Fires after the custom post type comment activity was removed.
2703       *
2704       * @since 2.5.0
2705       *
2706       * @param bool       $deleted              True if the activity was deleted false otherwise
2707       * @param WP_Comment $comment              Comment object.
2708       * @param object     $activity_post_object The post type tracking args object.
2709       * @param string     $value                The post type comment activity type.
2710       */
2711      do_action( 'bp_activity_post_type_remove_comment', $deleted, $comment_id, $activity_post_object, $activity_comment_object->action_id );
2712  
2713      return $deleted;
2714  }
2715  add_action( 'delete_comment', 'bp_activity_post_type_remove_comment', 10, 1 );
2716  
2717  /**
2718   * Add an activity comment.
2719   *
2720   * @since 1.2.0
2721   * @since 2.5.0 Add a new possible parameter $skip_notification for the array of arguments.
2722   *              Add the $primary_link parameter for the array of arguments.
2723   * @since 2.6.0 Added 'error_type' parameter to $args.
2724   *
2725   * @param array|string $args {
2726   *     An array of arguments.
2727   *     @type int    $id                Optional. Pass an ID to update an existing comment.
2728   *     @type string $content           The content of the comment.
2729   *     @type int    $user_id           Optional. The ID of the user making the comment.
2730   *                                     Defaults to the ID of the logged-in user.
2731   *     @type int    $activity_id       The ID of the "root" activity item, ie the oldest
2732   *                                     ancestor of the comment.
2733   *     @type int    $parent_id         Optional. The ID of the parent activity item, ie the item to
2734   *                                     which the comment is an immediate reply. If not provided,
2735   *                                     this value defaults to the $activity_id.
2736   *     @type string $primary_link      Optional. the primary link for the comment.
2737   *                                     Defaults to an empty string.
2738   *     @type bool   $skip_notification Optional. false to send a comment notification, false otherwise.
2739   *                                     Defaults to false.
2740   *     @type string $error_type        Optional. Error type. Either 'bool' or 'wp_error'. Default: 'bool'.
2741   * }
2742   * @return WP_Error|bool|int The ID of the comment on success, otherwise false.
2743   */
2744  function bp_activity_new_comment( $args = '' ) {
2745      $bp = buddypress();
2746      $r  = bp_parse_args(
2747          $args,
2748          array(
2749              'id'                => false,
2750              'content'           => false,
2751              'user_id'           => bp_loggedin_user_id(),
2752              'activity_id'       => false, // ID of the root activity item.
2753              'parent_id'         => false, // ID of a parent comment (optional).
2754              'primary_link'      => '',
2755              'skip_notification' => false,
2756              'error_type'        => 'bool',
2757          )
2758      );
2759  
2760      // Error type is boolean; need to initialize some variables for backpat.
2761      if ( 'bool' === $r['error_type'] ) {
2762          if ( empty( $bp->activity->errors ) ) {
2763              $bp->activity->errors = array();
2764          }
2765      }
2766  
2767      // Default error message.
2768      $feedback = __( 'There was an error posting your reply. Please try again.', 'buddypress' );
2769  
2770      // Bail if missing necessary data.
2771      if ( empty( $r['content'] ) || empty( $r['user_id'] ) || empty( $r['activity_id'] ) ) {
2772          $error = new WP_Error( 'missing_data', $feedback );
2773  
2774          if ( 'wp_error' === $r['error_type'] ) {
2775              return $error;
2776  
2777          // Backpat.
2778          } else {
2779              $bp->activity->errors['new_comment'] = $error;
2780              return false;
2781          }
2782      }
2783  
2784      // Maybe set current activity ID as the parent.
2785      if ( empty( $r['parent_id'] ) ) {
2786          $r['parent_id'] = $r['activity_id'];
2787      }
2788  
2789      $activity_id = $r['activity_id'];
2790  
2791      // Get the parent activity.
2792      $activity  = new BP_Activity_Activity( $activity_id );
2793  
2794      // Bail if the parent activity does not exist.
2795      if ( empty( $activity->date_recorded ) ) {
2796          $error = new WP_Error( 'missing_activity', __( 'The item you were replying to no longer exists.', 'buddypress' ) );
2797  
2798          if ( 'wp_error' === $r['error_type'] ) {
2799              return $error;
2800  
2801          // Backpat.
2802          } else {
2803              $bp->activity->errors['new_comment'] = $error;
2804              return false;
2805          }
2806  
2807      }
2808  
2809      // Check to see if the parent activity is hidden, and if so, hide this comment publicly.
2810      $is_hidden = $activity->hide_sitewide ? 1 : 0;
2811  
2812      /**
2813       * Filters the content of a new comment.
2814       *
2815       * @since 1.2.0
2816       * @since 3.0.0 Added $context parameter to disambiguate from bp_get_activity_comment_content().
2817       *
2818       * @param string $r       Content for the newly posted comment.
2819       * @param string $context This filter's context ("new").
2820       */
2821      $comment_content = apply_filters( 'bp_activity_comment_content', $r['content'], 'new' );
2822  
2823      // Insert the activity comment.
2824      $comment_id = bp_activity_add( array(
2825          'id'                => $r['id'],
2826          'content'           => $comment_content,
2827          'component'         => $bp->activity->id,
2828          'type'              => 'activity_comment',
2829          'primary_link'      => $r['primary_link'],
2830          'user_id'           => $r['user_id'],
2831          'item_id'           => $activity_id,
2832          'secondary_item_id' => $r['parent_id'],
2833          'hide_sitewide'     => $is_hidden,
2834          'error_type'        => $r['error_type']
2835      ) );
2836  
2837      // Bail on failure.
2838      if ( false === $comment_id || is_wp_error( $comment_id ) ) {
2839          return $comment_id;
2840      }
2841  
2842      // Comment caches are stored only with the top-level item.
2843      wp_cache_delete( $activity_id, 'bp_activity_comments' );
2844  
2845      // Walk the tree to clear caches for all parent items.
2846      $clear_id = $r['parent_id'];
2847      while ( $clear_id != $activity_id ) {
2848          $clear_object = new BP_Activity_Activity( $clear_id );
2849          wp_cache_delete( $clear_id, 'bp_activity' );
2850          $clear_id = intval( $clear_object->secondary_item_id );
2851      }
2852      wp_cache_delete( $activity_id, 'bp_activity' );
2853  
2854      if ( empty( $r[ 'skip_notification' ] ) ) {
2855          /**
2856           * Fires near the end of an activity comment posting, before the returning of the comment ID.
2857           * Sends a notification to the user @see bp_activity_new_comment_notification_helper().
2858           *
2859           * @since 1.2.0
2860           *
2861           * @param int                  $comment_id ID of the newly posted activity comment.
2862           * @param array                $r          Array of parsed comment arguments.
2863           * @param BP_Activity_Activity $activity   Activity item being commented on.
2864           */
2865          do_action( 'bp_activity_comment_posted', $comment_id, $r, $activity );
2866      } else {
2867          /**
2868           * Fires near the end of an activity comment posting, before the returning of the comment ID.
2869           * without sending a notification to the user
2870           *
2871           * @since 2.5.0
2872           *
2873           * @param int                  $comment_id ID of the newly posted activity comment.
2874           * @param array                $r          Array of parsed comment arguments.
2875           * @param BP_Activity_Activity $activity   Activity item being commented on.
2876           */
2877          do_action( 'bp_activity_comment_posted_notification_skipped', $comment_id, $r, $activity );
2878      }
2879  
2880      if ( empty( $comment_id ) ) {
2881          $error = new WP_Error( 'comment_failed', $feedback );
2882  
2883          if ( 'wp_error' === $r['error_type'] ) {
2884              return $error;
2885  
2886          // Backpat.
2887          } else {
2888              $bp->activity->errors['new_comment'] = $error;
2889          }
2890      }
2891  
2892      return $comment_id;
2893  }
2894  
2895  /**
2896   * Fetch the activity_id for an existing activity entry in the DB.
2897   *
2898   * @since 1.2.0
2899   *
2900   * @see BP_Activity_Activity::get() For more information on accepted arguments.
2901   *
2902   * @param array|string $args See BP_Activity_Activity::get() for description.
2903   * @return int $activity_id The ID of the activity item found.
2904   */
2905  function bp_activity_get_activity_id( $args = '' ) {
2906  
2907      $r = bp_parse_args(
2908          $args,
2909          array(
2910              'user_id'           => false,
2911              'component'         => false,
2912              'type'              => false,
2913              'item_id'           => false,
2914              'secondary_item_id' => false,
2915              'action'            => false,
2916              'content'           => false,
2917              'date_recorded'     => false,
2918          )
2919      );
2920  
2921      /**
2922       * Filters the activity ID being requested.
2923       *
2924       * @since 1.2.0
2925       * @since 2.5.0 Added the `$r` and `$args` parameters.
2926       *
2927       * @param BP_Activity_Activity $value ID returned by BP_Activity_Activity get_id() method with provided arguments.
2928       * @param array                $r     Parsed function arguments.
2929       * @param array                $args  Arguments passed to the function.
2930       */
2931      return apply_filters( 'bp_activity_get_activity_id', BP_Activity_Activity::get_id( $r ), $r, $args );
2932  }
2933  
2934  /**
2935   * Delete activity item(s).
2936   *
2937   * If you're looking to hook into one action that provides the ID(s) of
2938   * the activity/activities deleted, then use:
2939   *
2940   * add_action( 'bp_activity_deleted_activities', 'my_function' );
2941   *
2942   * The action passes one parameter that is a single activity ID or an
2943   * array of activity IDs depending on the number deleted.
2944   *
2945   * If you are deleting an activity comment please use bp_activity_delete_comment();
2946   *
2947   * @since 1.0.0
2948   *
2949   * @see BP_Activity_Activity::get() For more information on accepted arguments.
2950   *
2951   * @param array|string $args To delete specific activity items, use
2952   *                           $args = array( 'id' => $ids ); Otherwise, to use
2953   *                           filters for item deletion, the argument format is
2954   *                           the same as BP_Activity_Activity::get().
2955   *                           See that method for a description.
2956   * @return bool True on success, false on failure.
2957   */
2958  function bp_activity_delete( $args = '' ) {
2959  
2960      // Pass one or more the of following variables to delete by those variables.
2961      $args = bp_parse_args(
2962          $args,
2963          array(
2964              'id'                => false,
2965              'action'            => false,
2966              'content'           => false,
2967              'component'         => false,
2968              'type'              => false,
2969              'primary_link'      => false,
2970              'user_id'           => false,
2971              'item_id'           => false,
2972              'secondary_item_id' => false,
2973              'date_recorded'     => false,
2974              'hide_sitewide'     => false,
2975          )
2976      );
2977  
2978      /**
2979       * Fires before an activity item proceeds to be deleted.
2980       *
2981       * @since 1.5.0
2982       *
2983       * @param array $args Array of arguments to be used with the activity deletion.
2984       */
2985      do_action( 'bp_before_activity_delete', $args );
2986  
2987      // Adjust the new mention count of any mentioned member.
2988      bp_activity_adjust_mention_count( $args['id'], 'delete' );
2989  
2990      $activity_ids_deleted = BP_Activity_Activity::delete( $args );
2991      if ( empty( $activity_ids_deleted ) ) {
2992          return false;
2993      }
2994  
2995      // Check if the user's latest update has been deleted.
2996      $user_id = empty( $args['user_id'] )
2997          ? bp_loggedin_user_id()
2998          : $args['user_id'];
2999  
3000      $latest_update = bp_get_user_meta( $user_id, 'bp_latest_update', true );
3001      if ( !empty( $latest_update ) ) {
3002          if ( in_array( (int) $latest_update['id'], (array) $activity_ids_deleted ) ) {
3003              bp_delete_user_meta( $user_id, 'bp_latest_update' );
3004          }
3005      }
3006  
3007      /**
3008       * Fires after the activity item has been deleted.
3009       *
3010       * @since 1.0.0
3011       *
3012       * @param array $args Array of arguments used with the activity deletion.
3013       */
3014      do_action( 'bp_activity_delete', $args );
3015  
3016      /**
3017       * Fires after the activity item has been deleted.
3018       *
3019       * @since 1.2.0
3020       *
3021       * @param array $activity_ids_deleted Array of affected activity item IDs.
3022       */
3023      do_action( 'bp_activity_deleted_activities', $activity_ids_deleted );
3024  
3025      wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
3026  
3027      return true;
3028  }
3029  
3030      /**
3031       * Delete an activity item by activity id.
3032       *
3033       * You should use bp_activity_delete() instead.
3034       *
3035       * @since 1.1.0
3036       * @deprecated 1.2.0
3037       *
3038       * @param array|string $args See BP_Activity_Activity::get for a
3039       *                           description of accepted arguments.
3040       * @return bool True on success, false on failure.
3041       */
3042  	function bp_activity_delete_by_item_id( $args = '' ) {
3043  
3044          $r = bp_parse_args(
3045              $args,
3046              array(
3047                  'item_id'           => false,
3048                  'component'         => false,
3049                  'type'              => false,
3050                  'user_id'           => false,
3051                  'secondary_item_id' => false,
3052              )
3053          );
3054  
3055          return bp_activity_delete( $r );
3056      }
3057  
3058      /**
3059       * Delete an activity item by activity id.
3060       *
3061       * @since 1.1.0
3062       *
3063       *
3064       * @param int $activity_id ID of the activity item to be deleted.
3065       * @return bool True on success, false on failure.
3066       */
3067  	function bp_activity_delete_by_activity_id( $activity_id ) {
3068          return bp_activity_delete( array( 'id' => $activity_id ) );
3069      }
3070  
3071      /**
3072       * Delete an activity item by its content.
3073       *
3074       * You should use bp_activity_delete() instead.
3075       *
3076       * @since 1.1.0
3077       * @deprecated 1.2.0
3078       *
3079       *
3080       * @param int    $user_id   The user id.
3081       * @param string $content   The activity id.
3082       * @param string $component The activity component.
3083       * @param string $type      The activity type.
3084       * @return bool True on success, false on failure.
3085       */
3086  	function bp_activity_delete_by_content( $user_id, $content, $component, $type ) {
3087          return bp_activity_delete( array(
3088              'user_id'   => $user_id,
3089              'content'   => $content,
3090              'component' => $component,
3091              'type'      => $type
3092          ) );
3093      }
3094  
3095      /**
3096       * Delete a user's activity for a component.
3097       *
3098       * You should use bp_activity_delete() instead.
3099       *
3100       * @since 1.1.0
3101       * @deprecated 1.2.0
3102       *
3103       *
3104       * @param int    $user_id   The user id.
3105       * @param string $component The activity component.
3106       * @return bool True on success, false on failure.
3107       */
3108  	function bp_activity_delete_for_user_by_component( $user_id, $component ) {
3109          return bp_activity_delete( array(
3110              'user_id'   => $user_id,
3111              'component' => $component
3112          ) );
3113      }
3114  
3115  /**
3116   * Delete an activity comment.
3117   *
3118   * @since 1.2.0
3119   *
3120   * @todo Why is an activity id required? We could look this up.
3121   * @todo Why do we encourage users to call this function directly? We could just
3122   *       as easily examine the activity type in bp_activity_delete() and then
3123   *       call this function with the proper arguments if necessary.
3124   *
3125   * @param int $activity_id The ID of the "root" activity, ie the comment's
3126   *                         oldest ancestor.
3127   * @param int $comment_id  The ID of the comment to be deleted.
3128   * @return bool True on success, false on failure.
3129   */
3130  function bp_activity_delete_comment( $activity_id, $comment_id ) {
3131      $deleted = false;
3132  
3133      /**
3134       * Filters whether BuddyPress should delete an activity comment or not.
3135       *
3136       * You may want to hook into this filter if you want to override this function and
3137       * handle the deletion of child comments differently. Make sure you return false.
3138       *
3139       * @since 1.2.0
3140       * @since 2.5.0 Add the deleted parameter (passed by reference)
3141       *
3142       * @param bool $value       Whether BuddyPress should continue or not.
3143       * @param int  $activity_id ID of the root activity item being deleted.
3144       * @param int  $comment_id  ID of the comment being deleted.
3145       * @param bool $deleted     Whether the activity comment has been deleted or not.
3146       */
3147      if ( ! apply_filters_ref_array( 'bp_activity_delete_comment_pre', array( true, $activity_id, $comment_id, &$deleted ) ) ) {
3148          return $deleted;
3149      }
3150  
3151      // Check if comment still exists.
3152      $comment = new BP_Activity_Activity( $comment_id );
3153      if ( empty( $comment->id ) ) {
3154          return false;
3155      }
3156  
3157      // Delete any children of this comment.
3158      bp_activity_delete_children( $activity_id, $comment_id );
3159  
3160      // Delete the actual comment.
3161      if ( ! bp_activity_delete( array( 'id' => $comment_id, 'type' => 'activity_comment' ) ) ) {
3162          return false;
3163      } else {
3164          $deleted = true;
3165      }
3166  
3167      // Purge comment cache for the root activity update.
3168      wp_cache_delete( $activity_id, 'bp_activity_comments' );
3169  
3170      // Recalculate the comment tree.
3171      BP_Activity_Activity::rebuild_activity_comment_tree( $activity_id );
3172  
3173      /**
3174       * Fires at the end of the deletion of an activity comment, before returning success.
3175       *
3176       * @since 1.2.0
3177       *
3178       * @param int $activity_id ID of the activity that has had a comment deleted from.
3179       * @param int $comment_id  ID of the comment that was deleted.
3180       */
3181      do_action( 'bp_activity_delete_comment', $activity_id, $comment_id );
3182  
3183      return $deleted;
3184  }
3185  
3186      /**
3187       * Delete an activity comment's children.
3188       *
3189       * @since 1.2.0
3190       *
3191       *
3192       * @param int $activity_id The ID of the "root" activity, ie the
3193       *                         comment's oldest ancestor.
3194       * @param int $comment_id  The ID of the comment to be deleted.
3195       */
3196  	function bp_activity_delete_children( $activity_id, $comment_id ) {
3197          // Check if comment still exists.
3198          $comment = new BP_Activity_Activity( $comment_id );
3199          if ( empty( $comment->id ) ) {
3200              return;
3201          }
3202  
3203          // Get activity children to delete.
3204          $children = BP_Activity_Activity::get_child_comments( $comment_id );
3205  
3206          // Recursively delete all children of this comment.
3207          if ( ! empty( $children ) ) {
3208              foreach( (array) $children as $child ) {
3209                  bp_activity_delete_children( $activity_id, $child->id );
3210              }
3211          }
3212  
3213          // Delete the comment itself.
3214          bp_activity_delete( array(
3215              'secondary_item_id' => $comment_id,
3216              'type'              => 'activity_comment',
3217              'item_id'           => $activity_id
3218          ) );
3219      }
3220  
3221  /**
3222   * Get the permalink for a single activity item.
3223   *
3224   * When only the $activity_id param is passed, BP has to instantiate a new
3225   * BP_Activity_Activity object. To save yourself some processing overhead,
3226   * be sure to pass the full $activity_obj parameter as well, if you already
3227   * have it available.
3228   *
3229   * @since 1.2.0
3230   *
3231   * @param int         $activity_id  The unique id of the activity object.
3232   * @param object|bool $activity_obj Optional. The activity object.
3233   * @return string $link Permalink for the activity item.
3234   */
3235  function bp_activity_get_permalink( $activity_id, $activity_obj = false ) {
3236      $bp = buddypress();
3237  
3238      if ( empty( $activity_obj ) ) {
3239          $activity_obj = new BP_Activity_Activity( $activity_id );
3240      }
3241  
3242      if ( isset( $activity_obj->current_comment ) ) {
3243          $activity_obj = $activity_obj->current_comment;
3244      }
3245  
3246      $use_primary_links = array(
3247          'new_blog_post',
3248          'new_blog_comment',
3249          'new_forum_topic',
3250          'new_forum_post',
3251      );
3252  
3253      if ( ! empty( $bp->activity->track ) ) {
3254          $use_primary_links = array_merge( $use_primary_links, array_keys( $bp->activity->track ) );
3255      }
3256  
3257      if ( false !== array_search( $activity_obj->type, $use_primary_links ) ) {
3258          $link = $activity_obj->primary_link;
3259      } else {
3260          if ( 'activity_comment' == $activity_obj->type ) {
3261              $link = bp_get_root_domain() . '/' . bp_get_activity_root_slug() . '/p/' . $activity_obj->item_id . '/#acomment-' . $activity_obj->id;
3262          } else {
3263              $link = bp_get_root_domain() . '/' . bp_get_activity_root_slug() . '/p/' . $activity_obj->id . '/';
3264          }
3265      }
3266  
3267      /**
3268       * Filters the activity permalink for the specified activity item.
3269       *
3270       * @since 1.2.0
3271       *
3272       * @param array $array Array holding activity permalink and activity item object.
3273       */
3274      return apply_filters_ref_array( 'bp_activity_get_permalink', array( $link, &$activity_obj ) );
3275  }
3276  
3277  /**
3278   * Can a user see a particular activity item?
3279   *
3280   * @since 3.0.0
3281   *
3282   * @param  BP_Activity_Activity $activity Activity object.
3283   * @param  integer              $user_id  User ID.
3284   * @return boolean True on success, false on failure.
3285   */
3286  function bp_activity_user_can_read( $activity, $user_id = 0 ) {
3287      $retval = true;
3288  
3289      // Fallback.
3290      if ( empty( $user_id ) ) {
3291          $user_id = bp_loggedin_user_id();
3292      }
3293  
3294      // If activity is from a group, do extra cap checks.
3295      if ( bp_is_active( 'groups' ) && buddypress()->groups->id === $activity->component ) {
3296          // Check to see if the user has access to the activity's parent group.
3297          $group = groups_get_group( $activity->item_id );
3298          if ( $group ) {
3299              // For logged-in user, we can check against the 'user_has_access' prop.
3300              if ( bp_loggedin_user_id() === $user_id ) {
3301                  $retval = $group->user_has_access;
3302  
3303              // Manually check status.
3304              } elseif ( 'private' === $group->status || 'hidden' === $group->status ) {
3305                  // Only group members that are not banned can view.
3306                  if ( ! groups_is_user_member( $user_id, $activity->item_id ) || groups_is_user_banned( $user_id, $activity->item_id ) ) {
3307                      $retval = false;
3308                  }
3309              }
3310          }
3311      }
3312  
3313      // Spammed items are not visible to the public.
3314      if ( $activity->is_spam ) {
3315          $retval = false;
3316      }
3317  
3318      // Site moderators can view anything.
3319      if ( bp_current_user_can( 'bp_moderate' ) ) {
3320          $retval = true;
3321      }
3322  
3323      /**
3324       * Filters whether the current user has access to an activity item.
3325       *
3326       * @since 3.0.0
3327       *
3328       * @param bool                 $retval   Return value.
3329       * @param int                  $user_id  Current user ID.
3330       * @param BP_Activity_Activity $activity Activity object.
3331       */
3332      return apply_filters( 'bp_activity_user_can_read', $retval, $user_id, $activity );
3333  }
3334  
3335  /**
3336   * Hide a user's activity.
3337   *
3338   * @since 1.2.0
3339   *
3340   * @param int $user_id The ID of the user whose activity is being hidden.
3341   * @return bool True on success, false on failure.
3342   */
3343  function bp_activity_hide_user_activity( $user_id ) {
3344      return BP_Activity_Activity::hide_all_for_user( $user_id );
3345  }
3346  
3347  /**
3348   * Take content, remove images, and replace them with a single thumbnail image.
3349   *
3350   * The format of items in the activity stream is such that we do not want to
3351   * allow an arbitrary number of arbitrarily large images to be rendered.
3352   * However, the activity stream is built to elegantly display a single
3353   * thumbnail corresponding to the activity comment. This function looks
3354   * through the content, grabs the first image and converts it to a thumbnail,
3355   * and removes the rest of the images from the string.
3356   *
3357   * As of BuddyPress 2.3, this function is no longer in use.
3358   *
3359   * @since 1.2.0
3360   *
3361   * @param string      $content The content of the activity item.
3362   * @param string|bool $link    Optional. The unescaped URL that the image should link
3363   *                             to. If absent, the image will not be a link.
3364   * @param array|bool  $args    Optional. The args passed to the activity
3365   *                             creation function (eg bp_blogs_record_activity()).
3366   * @return string $content The content with images stripped and replaced with a
3367   *                         single thumb.
3368   */
3369  function bp_activity_thumbnail_content_images( $content, $link = false, $args = false ) {
3370  
3371      preg_match_all( '/<img[^>]*>/Ui', $content, $matches );
3372  
3373      // Remove <img> tags. Also remove caption shortcodes and caption text if present.
3374      $content = preg_replace('|(\[caption(.*?)\])?<img[^>]*>([^\[\[]*\[\/caption\])?|', '', $content );
3375  
3376      if ( !empty( $matches ) && !empty( $matches[0] ) ) {
3377  
3378          // Get the SRC value.
3379          preg_match( '/<img.*?(src\=[\'|"]{0,1}.*?[\'|"]{0,1})[\s|>]{1}/i',    $matches[0][0], $src    );
3380  
3381          // Get the width and height.
3382          preg_match( '/<img.*?(height\=[\'|"]{0,1}.*?[\'|"]{0,1})[\s|>]{1}/i', $matches[0][0], $height );
3383          preg_match( '/<img.*?(width\=[\'|"]{0,1}.*?[\'|"]{0,1})[\s|>]{1}/i',  $matches[0][0], $width  );
3384  
3385          if ( ! empty( $src ) ) {
3386              $src = substr( substr( str_replace( 'src=', '', $src[1] ), 0, -1 ), 1 );
3387  
3388              if ( isset( $width[1] ) ) {
3389                  $width = substr( substr( str_replace( 'width=', '', $width[1] ), 0, -1 ), 1 );
3390              }
3391  
3392              if ( isset( $height[1] ) ) {
3393                  $height = substr( substr( str_replace( 'height=', '', $height[1] ), 0, -1 ), 1 );
3394              }
3395  
3396              if ( empty( $width ) || empty( $height ) ) {
3397                  $width  = 100;
3398                  $height = 100;
3399              }
3400  
3401              $ratio      = (int) $width / (int) $height;
3402              $new_height = (int) $height >= 100 ? 100 : $height;
3403              $new_width  = $new_height * $ratio;
3404              $image      = '<img src="' . esc_url( $src ) . '" width="' . absint( $new_width ) . '" height="' . absint( $new_height ) . '" alt="' . __( 'Thumbnail', 'buddypress' ) . '" class="align-left thumbnail" />';
3405  
3406              if ( !empty( $link ) ) {
3407                  $image = '<a href="' . esc_url( $link ) . '">' . $image . '</a>';
3408              }
3409  
3410              $content = $image . $content;
3411          }
3412      }
3413  
3414      /**
3415       * Filters the activity content that had a thumbnail replace images.
3416       *
3417       * @since 1.2.0
3418       *
3419       * @param string $content Activity content that had images replaced in.
3420       * @param array  $matches Array of all image tags found in the posted content.
3421       * @param array  $args    Arguments passed into function creating the activity update.
3422       */
3423      return apply_filters( 'bp_activity_thumbnail_content_images', $content, $matches, $args );
3424  }
3425  
3426  /**
3427   * Gets the excerpt length for activity items.
3428   *
3429   * @since 2.8.0
3430   *
3431   * @return int Character length for activity excerpts.
3432   */
3433  function bp_activity_get_excerpt_length() {
3434      /**
3435       * Filters the excerpt length for the activity excerpt.
3436       *
3437       * @since 1.5.0
3438       *
3439       * @param int Character length for activity excerpts.
3440       */
3441      return (int) apply_filters( 'bp_activity_excerpt_length', 358 );
3442  }
3443  
3444  /**
3445   * Create a rich summary of an activity item for the activity stream.
3446   *
3447   * More than just a simple excerpt, the summary could contain oEmbeds and other types of media.
3448   * Currently, it's only used for blog post items, but it will probably be used for all types of
3449   * activity in the future.
3450   *
3451   * @since 2.3.0
3452   *
3453   * @param string $content  The content of the activity item.
3454   * @param array  $activity The data passed to bp_activity_add() or the values
3455   *                         from an Activity obj.
3456   * @return string $summary
3457   */
3458  function bp_activity_create_summary( $content, $activity ) {
3459      $args = array(
3460          'width' => isset( $GLOBALS['content_width'] ) ? (int) $GLOBALS['content_width'] : 'medium',
3461      );
3462  
3463      // Get the WP_Post object if this activity type is a blog post.
3464      if ( $activity['type'] === 'new_blog_post' ) {
3465          $content = get_post( $activity['secondary_item_id'] );
3466      }
3467  
3468      /**
3469       * Filter the class name of the media extractor when creating an Activity summary.
3470       *
3471       * Use this filter to change the media extractor used to extract media info for the activity item.
3472       *
3473       * @since 2.3.0
3474       *
3475       * @param string $extractor Class name.
3476       * @param string $content   The content of the activity item.
3477       * @param array  $activity  The data passed to bp_activity_add() or the values from an Activity obj.
3478       */
3479      $extractor = apply_filters( 'bp_activity_create_summary_extractor_class', 'BP_Media_Extractor', $content, $activity );
3480      $extractor = new $extractor;
3481  
3482      /**
3483       * Filter the arguments passed to the media extractor when creating an Activity summary.
3484       *
3485       * @since 2.3.0
3486       *
3487       * @param array              $args      Array of bespoke data for the media extractor.
3488       * @param string             $content   The content of the activity item.
3489       * @param array              $activity  The data passed to bp_activity_add() or the values from an Activity obj.
3490       * @param BP_Media_Extractor $extractor The media extractor object.
3491       */
3492      $args = apply_filters( 'bp_activity_create_summary_extractor_args', $args, $content, $activity, $extractor );
3493  
3494  
3495      // Extract media information from the $content.
3496      $media = $extractor->extract( $content, BP_Media_Extractor::ALL, $args );
3497  
3498      // If we converted $content to an object earlier, flip it back to a string.
3499      if ( is_a( $content, 'WP_Post' ) ) {
3500          $content = $content->post_content;
3501      }
3502  
3503      $para_count     = substr_count( strtolower( wpautop( $content ) ), '<p>' );
3504      $has_audio      = ! empty( $media['has']['audio'] )           && $media['has']['audio'];
3505      $has_videos     = ! empty( $media['has']['videos'] )          && $media['has']['videos'];
3506      $has_feat_image = ! empty( $media['has']['featured_images'] ) && $media['has']['featured_images'];
3507      $has_galleries  = ! empty( $media['has']['galleries'] )       && $media['has']['galleries'];
3508      $has_images     = ! empty( $media['has']['images'] )          && $media['has']['images'];
3509      $has_embeds     = false;
3510  
3511      // Embeds must be subtracted from the paragraph count.
3512      if ( ! empty( $media['has']['embeds'] ) ) {
3513          $has_embeds = $media['has']['embeds'] > 0;
3514          $para_count -= $media['has']['embeds'];
3515      }
3516  
3517      $extracted_media = array();
3518      $use_media_type  = '';
3519      $image_source    = '';
3520  
3521      // If it's a short article and there's an embed/audio/video, use it.
3522      if ( $para_count <= 3 ) {
3523          if ( $has_embeds ) {
3524              $use_media_type = 'embeds';
3525          } elseif ( $has_audio ) {
3526              $use_media_type = 'audio';
3527          } elseif ( $has_videos ) {
3528              $use_media_type = 'videos';
3529          }
3530      }
3531  
3532      // If not, or in any other situation, try to use an image.
3533      if ( ! $use_media_type && $has_images ) {
3534          $use_media_type = 'images';
3535          $image_source   = 'html';
3536  
3537          // Featured Image > Galleries > inline <img>.
3538          if ( $has_feat_image ) {
3539              $image_source = 'featured_images';
3540  
3541          } elseif ( $has_galleries ) {
3542              $image_source = 'galleries';
3543          }
3544      }
3545  
3546      // Extract an item from the $media results.
3547      if ( $use_media_type ) {
3548          if ( $use_media_type === 'images' ) {
3549              $extracted_media = wp_list_filter( $media[ $use_media_type ], array( 'source' => $image_source ) );
3550              $extracted_media = array_shift( $extracted_media );
3551          } else {
3552              $extracted_media = array_shift( $media[ $use_media_type ] );
3553          }
3554  
3555          /**
3556           * Filter the results of the media extractor when creating an Activity summary.
3557           *
3558           * @since 2.3.0
3559           *
3560           * @param array  $extracted_media Extracted media item. See {@link BP_Media_Extractor::extract()} for format.
3561           * @param string $content         Content of the activity item.
3562           * @param array  $activity        The data passed to bp_activity_add() or the values from an Activity obj.
3563           * @param array  $media           All results from the media extraction.
3564           *                                See {@link BP_Media_Extractor::extract()} for format.
3565           * @param string $use_media_type  The kind of media item that was preferentially extracted.
3566           * @param string $image_source    If $use_media_type was "images", the preferential source of the image.
3567           *                                Otherwise empty.
3568           */
3569          $extracted_media = apply_filters(
3570              'bp_activity_create_summary_extractor_result',
3571              $extracted_media,
3572              $content,
3573              $activity,
3574              $media,
3575              $use_media_type,
3576              $image_source
3577          );
3578      }
3579  
3580      // Generate a text excerpt for this activity item (and remove any oEmbeds URLs).
3581      $summary = bp_create_excerpt( html_entity_decode( $content ), 225, array(
3582          'html' => false,
3583          'filter_shortcodes' => true,
3584          'strip_tags'        => true,
3585          'remove_links'      => true
3586      ) );
3587  
3588      if ( $use_media_type === 'embeds' ) {
3589          $summary .= PHP_EOL . PHP_EOL . $extracted_media['url'];
3590      } elseif ( $use_media_type === 'images' ) {
3591          $extracted_media_url = isset( $extracted_media['url'] ) ? $extracted_media['url'] : '';
3592          $summary .= sprintf( ' <img src="%s">', esc_url( $extracted_media_url ) );
3593      } elseif ( in_array( $use_media_type, array( 'audio', 'videos' ), true ) ) {
3594          $summary .= PHP_EOL . PHP_EOL . $extracted_media['original'];  // Full shortcode.
3595      }
3596  
3597      /**
3598       * Filters the newly-generated summary for the activity item.
3599       *
3600       * @since 2.3.0
3601       *
3602       * @param string $summary         Activity summary HTML.
3603       * @param string $content         Content of the activity item.
3604       * @param array  $activity        The data passed to bp_activity_add() or the values from an Activity obj.
3605       * @param array  $extracted_media Media item extracted. See {@link BP_Media_Extractor::extract()} for format.
3606       */
3607      return apply_filters( 'bp_activity_create_summary', $summary, $content, $activity, $extracted_media );
3608  }
3609  
3610  /**
3611   * Fetch whether the current user is allowed to mark items as spam.
3612   *
3613   * @since 1.6.0
3614   *
3615   * @return bool True if user is allowed to mark activity items as spam.
3616   */
3617  function bp_activity_user_can_mark_spam() {
3618  
3619      /**
3620       * Filters whether the current user should be able to mark items as spam.
3621       *
3622       * @since 1.6.0
3623       *
3624       * @param bool $moderate Whether or not the current user has bp_moderate capability.
3625       */
3626      return apply_filters( 'bp_activity_user_can_mark_spam', bp_current_user_can( 'bp_moderate' ) );
3627  }
3628  
3629  /**
3630   * Mark an activity item as spam.
3631   *
3632   * @since 1.6.0
3633   *
3634   * @todo We should probably save $source to activity meta.
3635   *
3636   * @param BP_Activity_Activity $activity The activity item to be spammed.
3637   * @param string               $source   Optional. Default is "by_a_person" (ie, a person has
3638   *                                       manually marked the activity as spam). BP core also
3639   *                                       accepts 'by_akismet'.
3640   */
3641  function bp_activity_mark_as_spam( &$activity, $source = 'by_a_person' ) {
3642      $bp = buddypress();
3643  
3644      $activity->is_spam = 1;
3645  
3646      // Clear the activity stream first page cache.
3647      wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
3648  
3649      if ( 'activity_comment' === $activity->type ) {
3650          $activity_id = $activity->item_id;
3651      } else {
3652          $activity_id = $activity->id;
3653      }
3654  
3655      // Clear the activity comment cache for this activity item.
3656      wp_cache_delete( $activity_id, 'bp_activity_comments' );
3657  
3658      // If Akismet is active, and this was a manual spam/ham request, stop Akismet checking the activity.
3659      if ( 'by_a_person' == $source && !empty( $bp->activity->akismet ) ) {
3660          remove_action( 'bp_activity_before_save', array( $bp->activity->akismet, 'check_activity' ), 4 );
3661  
3662          // Build data package for Akismet.
3663          $activity_data = BP_Akismet::build_akismet_data_package( $activity );
3664  
3665          // Tell Akismet this is spam.
3666          $activity_data = $bp->activity->akismet->send_akismet_request( $activity_data, 'submit', 'spam' );
3667  
3668          // Update meta.
3669          add_action( 'bp_activity_after_save', array( $bp->activity->akismet, 'update_activity_spam_meta' ), 1, 1 );
3670      }
3671  
3672      /**
3673       * Fires at the end of the process to mark an activity item as spam.
3674       *
3675       * @since 1.6.0
3676       *
3677       * @param BP_Activity_Activity $activity Activity item being marked as spam.
3678       * @param string               $source   Source of determination of spam status. For example
3679       *                                       "by_a_person" or "by_akismet".
3680       */
3681      do_action( 'bp_activity_mark_as_spam', $activity, $source );
3682  }
3683  
3684  /**
3685   * Mark an activity item as ham.
3686   *
3687   * @since 1.6.0
3688   *
3689   * @param BP_Activity_Activity $activity The activity item to be hammed. Passed by reference.
3690   * @param string               $source   Optional. Default is "by_a_person" (ie, a person has
3691   *                                       manually marked the activity as spam). BP core also accepts
3692   *                                       'by_akismet'.
3693   */
3694  function bp_activity_mark_as_ham( &$activity, $source = 'by_a_person' ) {
3695      $bp = buddypress();
3696  
3697      $activity->is_spam = 0;
3698  
3699      // Clear the activity stream first page cache.
3700      wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
3701  
3702      if ( 'activity_comment' === $activity->type ) {
3703          $activity_id = $activity->item_id;
3704      } else {
3705          $activity_id = $activity->id;
3706      }
3707  
3708      // Clear the activity comment cache for this activity item.
3709      wp_cache_delete( $activity_id, 'bp_activity_comments' );
3710  
3711      // If Akismet is active, and this was a manual spam/ham request, stop Akismet checking the activity.
3712      if ( 'by_a_person' == $source && !empty( $bp->activity->akismet ) ) {
3713          remove_action( 'bp_activity_before_save', array( $bp->activity->akismet, 'check_activity' ), 4 );
3714  
3715          // Build data package for Akismet.
3716          $activity_data = BP_Akismet::build_akismet_data_package( $activity );
3717  
3718          // Tell Akismet this is spam.
3719          $activity_data = $bp->activity->akismet->send_akismet_request( $activity_data, 'submit', 'ham' );
3720  
3721          // Update meta.
3722          add_action( 'bp_activity_after_save', array( $bp->activity->akismet, 'update_activity_ham_meta' ), 1, 1 );
3723      }
3724  
3725      /**
3726       * Fires at the end of the process to mark an activity item as ham.
3727       *
3728       * @since 1.6.0
3729       *
3730       * @param BP_Activity_Activity $activity Activity item being marked as ham.
3731       * @param string               $source   Source of determination of ham status. For example
3732       *                                       "by_a_person" or "by_akismet".
3733       */
3734      do_action( 'bp_activity_mark_as_ham', $activity, $source );
3735  }
3736  
3737  /* Emails *********************************************************************/
3738  
3739  /**
3740   * Send email and BP notifications when a user is mentioned in an update.
3741   *
3742   * @since 1.2.0
3743   *
3744   * @param int $activity_id      The ID of the activity update.
3745   * @param int $receiver_user_id The ID of the user who is receiving the update.
3746   */
3747  function bp_activity_at_message_notification( $activity_id, $receiver_user_id ) {
3748      $notifications = BP_Core_Notification::get_all_for_user( $receiver_user_id, 'all' );
3749  
3750      // Don't leave multiple notifications for the same activity item.
3751      foreach( $notifications as $notification ) {
3752          if ( $activity_id == $notification->item_id ) {
3753              return;
3754          }
3755      }
3756  
3757      $activity     = new BP_Activity_Activity( $activity_id );
3758      $email_type   = 'activity-at-message';
3759      $group_name   = '';
3760      $message_link = bp_activity_get_permalink( $activity_id );
3761      $poster_name  = bp_core_get_user_displayname( $activity->user_id );
3762  
3763      remove_filter( 'bp_get_activity_content_body', 'convert_smilies' );
3764      remove_filter( 'bp_get_activity_content_body', 'wpautop' );
3765      remove_filter( 'bp_get_activity_content_body', 'bp_activity_truncate_entry', 5 );
3766  
3767      /** This filter is documented in bp-activity/bp-activity-template.php */
3768      $content = apply_filters_ref_array( 'bp_get_activity_content_body', array( $activity->content, &$activity ) );
3769  
3770      add_filter( 'bp_get_activity_content_body', 'convert_smilies' );
3771      add_filter( 'bp_get_activity_content_body', 'wpautop' );
3772      add_filter( 'bp_get_activity_content_body', 'bp_activity_truncate_entry', 5 );
3773  
3774      // Now email the user with the contents of the message (if they have enabled email notifications).
3775      if ( 'no' != bp_get_user_meta( $receiver_user_id, 'notification_activity_new_mention', true ) ) {
3776          if ( bp_is_active( 'groups' ) && bp_is_group() ) {
3777              $email_type = 'groups-at-message';
3778              $group_name = bp_get_current_group_name();
3779          }
3780  
3781          $unsubscribe_args = array(
3782              'user_id'           => $receiver_user_id,
3783              'notification_type' => $email_type,
3784          );
3785  
3786          $args = array(
3787              'tokens' => array(
3788                  'activity'         => $activity,
3789                  'usermessage'      => wp_strip_all_tags( $content ),
3790                  'group.name'       => $group_name,
3791                  'mentioned.url'    => $message_link,
3792                  'poster.name'      => $poster_name,
3793                  'receiver-user.id' => $receiver_user_id,
3794                  'unsubscribe'        => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
3795              ),
3796          );
3797  
3798          bp_send_email( $email_type, $receiver_user_id, $args );
3799      }
3800  
3801      /**
3802       * Fires after the sending of an @mention email notification.
3803       *
3804       * @since 1.5.0
3805       * @since 2.5.0 $subject, $message, $content arguments unset and deprecated.
3806       *
3807       * @param BP_Activity_Activity $activity         Activity Item object.
3808       * @param string               $deprecated       Removed in 2.5; now an empty string.
3809       * @param string               $deprecated       Removed in 2.5; now an empty string.
3810       * @param string               $deprecated       Removed in 2.5; now an empty string.
3811       * @param int                  $receiver_user_id The ID of the user who is receiving the update.
3812       */
3813      do_action( 'bp_activity_sent_mention_email', $activity, '', '', '', $receiver_user_id );
3814  }
3815  
3816  /**
3817   * Send email and BP notifications when an activity item receives a comment.
3818   *
3819   * @since 1.2.0
3820   * @since 2.5.0 Updated to use new email APIs.
3821   *
3822   * @param int   $comment_id   The comment id.
3823   * @param int   $commenter_id The ID of the user who posted the comment.
3824   * @param array $params       {@link bp_activity_new_comment()}.
3825   */
3826  function bp_activity_new_comment_notification( $comment_id = 0, $commenter_id = 0, $params = array() ) {
3827      $original_activity = new BP_Activity_Activity( $params['activity_id'] );
3828      $poster_name       = bp_core_get_user_displayname( $commenter_id );
3829      $thread_link       = bp_activity_get_permalink( $params['activity_id'] );
3830  
3831      remove_filter( 'bp_get_activity_content_body', 'convert_smilies' );
3832      remove_filter( 'bp_get_activity_content_body', 'wpautop' );
3833      remove_filter( 'bp_get_activity_content_body', 'bp_activity_truncate_entry', 5 );
3834  
3835      /** This filter is documented in bp-activity/bp-activity-template.php */
3836      $content = apply_filters_ref_array( 'bp_get_activity_content_body', array( $params['content'], &$original_activity ) );
3837  
3838      add_filter( 'bp_get_activity_content_body', 'convert_smilies' );
3839      add_filter( 'bp_get_activity_content_body', 'wpautop' );
3840      add_filter( 'bp_get_activity_content_body', 'bp_activity_truncate_entry', 5 );
3841  
3842      if ( $original_activity->user_id != $commenter_id ) {
3843  
3844          // Send an email if the user hasn't opted-out.
3845          if ( 'no' != bp_get_user_meta( $original_activity->user_id, 'notification_activity_new_reply', true ) ) {
3846  
3847              $unsubscribe_args = array(
3848                  'user_id'           => $original_activity->user_id,
3849                  'notification_type' => 'activity-comment',
3850              );
3851  
3852              $args = array(
3853                  'tokens' => array(
3854                      'comment.id'                => $comment_id,
3855                      'commenter.id'              => $commenter_id,
3856                      'usermessage'               => wp_strip_all_tags( $content ),
3857                      'original_activity.user_id' => $original_activity->user_id,
3858                      'poster.name'               => $poster_name,
3859                      'thread.url'                => esc_url( $thread_link ),
3860                      'unsubscribe'               => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
3861                  ),
3862              );
3863  
3864              bp_send_email( 'activity-comment', $original_activity->user_id, $args );
3865          }
3866  
3867          /**
3868           * Fires at the point that notifications should be sent for activity comments.
3869           *
3870           * @since 2.6.0
3871           *
3872           * @param BP_Activity_Activity $original_activity The original activity.
3873           * @param int                  $comment_id        ID for the newly received comment.
3874           * @param int                  $commenter_id      ID of the user who made the comment.
3875           * @param array                $params            Arguments used with the original activity comment.
3876           */
3877          do_action( 'bp_activity_sent_reply_to_update_notification', $original_activity, $comment_id, $commenter_id, $params );
3878      }
3879  
3880  
3881      /*
3882       * If this is a reply to another comment, send an email notification to the
3883       * author of the immediate parent comment.
3884       */
3885      if ( empty( $params['parent_id'] ) || ( $params['activity_id'] == $params['parent_id'] ) ) {
3886          return;
3887      }
3888  
3889      $parent_comment = new BP_Activity_Activity( $params['parent_id'] );
3890  
3891      if ( $parent_comment->user_id != $commenter_id && $original_activity->user_id != $parent_comment->user_id ) {
3892  
3893          // Send an email if the user hasn't opted-out.
3894          if ( 'no' != bp_get_user_meta( $parent_comment->user_id, 'notification_activity_new_reply', true ) ) {
3895  
3896              $unsubscribe_args = array(
3897                  'user_id'           => $parent_comment->user_id,
3898                  'notification_type' => 'activity-comment-author',
3899              );
3900  
3901              $args = array(
3902                  'tokens' => array(
3903                      'comment.id'             => $comment_id,
3904                      'commenter.id'           => $commenter_id,
3905                      'usermessage'            => wp_strip_all_tags( $content ),
3906                      'parent-comment-user.id' => $parent_comment->user_id,
3907                      'poster.name'            => $poster_name,
3908                      'thread.url'             => esc_url( $thread_link ),
3909                      'unsubscribe'            => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
3910                  ),
3911              );
3912  
3913              bp_send_email( 'activity-comment-author', $parent_comment->user_id, $args );
3914          }
3915  
3916          /**
3917           * Fires at the point that notifications should be sent for comments on activity replies.
3918           *
3919           * @since 2.6.0
3920           *
3921           * @param BP_Activity_Activity $parent_comment The parent activity.
3922           * @param int                  $comment_id     ID for the newly received comment.
3923           * @param int                  $commenter_id   ID of the user who made the comment.
3924           * @param array                $params         Arguments used with the original activity comment.
3925           */
3926          do_action( 'bp_activity_sent_reply_to_reply_notification', $parent_comment, $comment_id, $commenter_id, $params );
3927      }
3928  }
3929  
3930  /**
3931   * Helper method to map action arguments to function parameters.
3932   *
3933   * @since 1.9.0
3934   *
3935   * @param int   $comment_id ID of the comment being notified about.
3936   * @param array $params     Parameters to use with notification.
3937   */
3938  function bp_activity_new_comment_notification_helper( $comment_id, $params ) {
3939      bp_activity_new_comment_notification( $comment_id, $params['user_id'], $params );
3940  }
3941  add_action( 'bp_activity_comment_posted', 'bp_activity_new_comment_notification_helper', 10, 2 );
3942  
3943  /** Embeds *******************************************************************/
3944  
3945  /**
3946   * Set up activity oEmbed cache during the activity loop.
3947   *
3948   * During an activity loop, this function sets up the hooks necessary to grab
3949   * each item's embeds from the cache, or put them in the cache if they are
3950   * not there yet.
3951   *
3952   * This does not cover recursive activity comments, as they do not use a real loop.
3953   * For that, see {@link bp_activity_comment_embed()}.
3954   *
3955   * @since 1.5.0
3956   *
3957   * @see BP_Embed
3958   * @see bp_embed_activity_cache()
3959   * @see bp_embed_activity_save_cache()
3960   *
3961   */
3962  function bp_activity_embed() {
3963      add_filter( 'embed_post_id',         'bp_get_activity_id'                  );
3964      add_filter( 'oembed_dataparse',      'bp_activity_oembed_dataparse', 10, 2 );
3965      add_filter( 'bp_embed_get_cache',    'bp_embed_activity_cache',      10, 3 );
3966      add_action( 'bp_embed_update_cache', 'bp_embed_activity_save_cache', 10, 3 );
3967  }
3968  add_action( 'activity_loop_start', 'bp_activity_embed' );
3969  
3970  /**
3971   * Cache full oEmbed response from oEmbed.
3972   *
3973   * @since 2.6.0
3974   *
3975   * @param string $retval Current oEmbed result.
3976   * @param object $data   Full oEmbed response.
3977   * @param string $url    URL used for the oEmbed request.
3978   * @return string
3979   */
3980  function bp_activity_oembed_dataparse( $retval, $data ) {
3981      buddypress()->activity->oembed_response = $data;
3982  
3983      return $retval;
3984  }
3985  
3986  /**
3987   * Set up activity oEmbed cache while recursing through activity comments.
3988   *
3989   * While crawling through an activity comment tree
3990   * ({@link bp_activity_recurse_comments}), this function sets up the hooks
3991   * necessary to grab each comment's embeds from the cache, or put them in
3992   * the cache if they are not there yet.
3993   *
3994   * @since 1.5.0
3995   *
3996   * @see BP_Embed
3997   * @see bp_embed_activity_cache()
3998   * @see bp_embed_activity_save_cache()
3999   *
4000   */
4001  function bp_activity_comment_embed() {
4002      add_filter( 'embed_post_id',         'bp_get_activity_comment_id'          );
4003      add_filter( 'bp_embed_get_cache',    'bp_embed_activity_cache',      10, 3 );
4004      add_action( 'bp_embed_update_cache', 'bp_embed_activity_save_cache', 10, 3 );
4005  }
4006  add_action( 'bp_before_activity_comment', 'bp_activity_comment_embed' );
4007  
4008  /**
4009   * When a user clicks on a "Read More" item, make sure embeds are correctly parsed and shown for the expanded content.
4010   *
4011   * @since 1.5.0
4012   *
4013   * @see BP_Embed
4014   *
4015   * @param object $activity The activity that is being expanded.
4016   */
4017  function bp_dtheme_embed_read_more( $activity ) {
4018      buddypress()->activity->read_more_id = $activity->id;
4019  
4020      add_filter( 'embed_post_id',         function() { return buddypress()->activity->read_more_id; } );
4021      add_filter( 'bp_embed_get_cache',    'bp_embed_activity_cache',      10, 3 );
4022      add_action( 'bp_embed_update_cache', 'bp_embed_activity_save_cache', 10, 3 );
4023  }
4024  add_action( 'bp_dtheme_get_single_activity_content',       'bp_dtheme_embed_read_more' );
4025  add_action( 'bp_legacy_theme_get_single_activity_content', 'bp_dtheme_embed_read_more' );
4026  
4027  /**
4028   * Clean up 'embed_post_id' filter after comment recursion.
4029   *
4030   * This filter must be removed so that the non-comment filters take over again
4031   * once the comments are done being processed.
4032   *
4033   * @since 1.5.0
4034   *
4035   * @see bp_activity_comment_embed()
4036   */
4037  function bp_activity_comment_embed_after_recurse() {
4038      remove_filter( 'embed_post_id', 'bp_get_activity_comment_id' );
4039  }
4040  add_action( 'bp_after_activity_comment', 'bp_activity_comment_embed_after_recurse' );
4041  
4042  /**
4043   * Fetch an activity item's cached embeds.
4044   *
4045   * Used during {@link BP_Embed::parse_oembed()} via {@link bp_activity_embed()}.
4046   *
4047   * @since 1.5.0
4048   *
4049   * @see BP_Embed::parse_oembed()
4050   *
4051   * @param string $cache    An empty string passed by BP_Embed::parse_oembed() for
4052   *                         functions like this one to filter.
4053   * @param int    $id       The ID of the activity item.
4054   * @param string $cachekey The cache key generated in BP_Embed::parse_oembed().
4055   * @return mixed The cached embeds for this activity item.
4056   */
4057  function bp_embed_activity_cache( $cache, $id, $cachekey ) {
4058      return bp_activity_get_meta( $id, $cachekey );
4059  }
4060  
4061  /**
4062   * Set an activity item's embed cache.
4063   *
4064   * Used during {@link BP_Embed::parse_oembed()} via {@link bp_activity_embed()}.
4065   *
4066   * @since 1.5.0
4067   *
4068   * @see BP_Embed::parse_oembed()
4069   *
4070   * @param string $cache    An empty string passed by BP_Embed::parse_oembed() for
4071   *                         functions like this one to filter.
4072   * @param string $cachekey The cache key generated in BP_Embed::parse_oembed().
4073   * @param int    $id       The ID of the activity item.
4074   */
4075  function bp_embed_activity_save_cache( $cache, $cachekey, $id ) {
4076      bp_activity_update_meta( $id, $cachekey, $cache );
4077  
4078      // Cache full oEmbed response.
4079      if ( true === isset( buddypress()->activity->oembed_response ) ) {
4080          $cachekey = str_replace( '_oembed', '_oembed_response', $cachekey );
4081          bp_activity_update_meta( $id, $cachekey, buddypress()->activity->oembed_response );
4082      }
4083  }
4084  
4085  /**
4086   * Should we use Heartbeat to refresh activities?
4087   *
4088   * @since 2.0.0
4089   *
4090   * @return bool True if activity heartbeat is enabled, otherwise false.
4091   */
4092  function bp_activity_do_heartbeat() {
4093      $retval = false;
4094  
4095      if ( bp_is_activity_heartbeat_active() && ( bp_is_activity_directory() || bp_is_group_activity() ) ) {
4096          $retval = true;
4097      }
4098  
4099      /**
4100       * Filters whether the heartbeat feature in the activity stream should be active.
4101       *
4102       * @since 2.8.0
4103       *
4104       * @param bool $retval Whether or not activity heartbeat is active.
4105       */
4106      return (bool) apply_filters( 'bp_activity_do_heartbeat', $retval );
4107  }
4108  
4109  /**
4110   * Detect a change in post type status, and initiate an activity update if necessary.
4111   *
4112   * @since 2.2.0
4113   *
4114   * @todo Support untrashing better.
4115   *
4116   * @param string $new_status New status for the post.
4117   * @param string $old_status Old status for the post.
4118   * @param object $post       Post data.
4119   */
4120  function bp_activity_catch_transition_post_type_status( $new_status, $old_status, $post ) {
4121      if ( ! post_type_supports( $post->post_type, 'buddypress-activity' ) ) {
4122          return;
4123      }
4124  
4125      // This is an edit.
4126      if ( $new_status === $old_status ) {
4127          // An edit of an existing post should update the existing activity item.
4128          if ( $new_status == 'publish' ) {
4129              $edit = bp_activity_post_type_update( $post );
4130  
4131              // Post was never recorded into activity stream, so record it now!
4132              if ( null === $edit ) {
4133                  bp_activity_post_type_publish( $post->ID, $post );
4134              }
4135  
4136          // Allow plugins to eventually deal with other post statuses.
4137          } else {
4138              /**
4139               * Fires when editing the post and the new status is not 'publish'.
4140               *
4141               * This is a variable filter that is dependent on the post type
4142               * being untrashed.
4143               *
4144               * @since 2.5.0
4145               *
4146               * @param WP_Post $post Post data.
4147               * @param string $new_status New status for the post.
4148               * @param string $old_status Old status for the post.
4149               */
4150              do_action( 'bp_activity_post_type_edit_' . $post->post_type, $post, $new_status, $old_status );
4151          }
4152  
4153          return;
4154      }
4155  
4156      // Publishing a previously unpublished post.
4157      if ( 'publish' === $new_status ) {
4158          // Untrashing the post type - nothing here yet.
4159          if ( 'trash' == $old_status ) {
4160  
4161              /**
4162               * Fires if untrashing post in a post type.
4163               *
4164               * This is a variable filter that is dependent on the post type
4165               * being untrashed.
4166               *
4167               * @since 2.2.0
4168               *
4169               * @param WP_Post $post Post data.
4170               */
4171              do_action( 'bp_activity_post_type_untrash_' . $post->post_type, $post );
4172          } else {
4173              // Record the post.
4174              bp_activity_post_type_publish( $post->ID, $post );
4175          }
4176  
4177      // Unpublishing a previously published post.
4178      } elseif ( 'publish' === $old_status ) {
4179          // Some form of pending status - only remove the activity entry.
4180          bp_activity_post_type_unpublish( $post->ID, $post );
4181  
4182      // For any other cases, allow plugins to eventually deal with it.
4183      } else {
4184          /**
4185           * Fires when the old and the new post status are not 'publish'.
4186           *
4187           * This is a variable filter that is dependent on the post type
4188           * being untrashed.
4189           *
4190           * @since 2.5.0
4191           *
4192           * @param WP_Post $post Post data.
4193           * @param string $new_status New status for the post.
4194           * @param string $old_status Old status for the post.
4195           */
4196          do_action( 'bp_activity_post_type_transition_status_' . $post->post_type, $post, $new_status, $old_status );
4197      }
4198  }
4199  add_action( 'transition_post_status', 'bp_activity_catch_transition_post_type_status', 10, 3 );
4200  
4201  /**
4202   * When a post type comment status transition occurs, update the relevant activity's status.
4203   *
4204   * @since 2.5.0
4205   *
4206   * @param string     $new_status New comment status.
4207   * @param string     $old_status Previous comment status.
4208   * @param WP_Comment $comment Comment data.
4209   */
4210  function bp_activity_transition_post_type_comment_status( $new_status, $old_status, $comment ) {
4211      $post_type = get_post_type( $comment->comment_post_ID );
4212      if ( ! $post_type ) {
4213          return;
4214      }
4215  
4216      // Get the post type tracking args.
4217      $activity_post_object = bp_activity_get_post_type_tracking_args( $post_type );
4218  
4219      // Bail if the activity type does not exist.
4220      if ( empty( $activity_post_object->comments_tracking->action_id ) ) {
4221          return false;
4222  
4223      // Set the $activity_comment_object.
4224      } else {
4225          $activity_comment_object = $activity_post_object->comments_tracking;
4226      }
4227  
4228      // Init an empty activity ID.
4229      $activity_id = 0;
4230  
4231      /**
4232       * Activity currently doesn't have any concept of a trash, or an unapproved/approved state.
4233       *
4234       * If a blog comment transitions to a "delete" or "hold" status, delete the activity item.
4235       * If a blog comment transitions to trashed, or spammed, mark the activity as spam.
4236       * If a blog comment transitions to approved (and the activity exists), mark the activity as ham.
4237       * If a blog comment transitions to unapproved (and the activity exists), mark the activity as spam.
4238       * Otherwise, record the comment into the activity stream.
4239       */
4240  
4241      // This clause handles delete/hold.
4242      if ( in_array( $new_status, array( 'delete', 'hold' ) ) ) {
4243          return bp_activity_post_type_remove_comment( $comment->comment_ID, $activity_post_object );
4244  
4245      // These clauses handle trash, spam, and un-spams.
4246      } elseif ( in_array( $new_status, array( 'trash', 'spam', 'unapproved' ) ) ) {
4247          $action = 'spam_activity';
4248      } elseif ( 'approved' == $new_status ) {
4249          $action = 'ham_activity';
4250      }
4251  
4252      // Get the activity.
4253      if ( bp_disable_blogforum_comments() ) {
4254          $activity_id = bp_activity_get_activity_id( array(
4255              'component'         => $activity_comment_object->component_id,
4256              'item_id'           => get_current_blog_id(),
4257              'secondary_item_id' => $comment->comment_ID,
4258              'type'              => $activity_comment_object->action_id,
4259          ) );
4260      } else {
4261          $activity_id = get_comment_meta( $comment->comment_ID, 'bp_activity_comment_id', true );
4262      }
4263  
4264      /**
4265       * Leave a chance to plugins to manage activity comments differently.
4266       *
4267       * @since  2.5.0
4268       *
4269       * @param bool        $value       True to override BuddyPress management.
4270       * @param string      $post_type   The post type name.
4271       * @param int         $activity_id The post type activity (0 if not found).
4272       * @param string      $new_status  The new status of the post type comment.
4273       * @param string      $old_status  The old status of the post type comment.
4274       * @param WP_Comment  $comment Comment data.
4275       */
4276      if ( true === apply_filters( 'bp_activity_pre_transition_post_type_comment_status', false, $post_type, $activity_id, $new_status, $old_status, $comment ) ) {
4277          return false;
4278      }
4279  
4280      // Check activity item exists.
4281      if ( empty( $activity_id ) ) {
4282          // If no activity exists, but the comment has been approved, record it into the activity table.
4283          if ( 'approved' == $new_status ) {
4284              return bp_activity_post_type_comment( $comment->comment_ID, true, $activity_post_object );
4285          }
4286  
4287          return;
4288      }
4289  
4290      // Create an activity object.
4291      $activity = new BP_Activity_Activity( $activity_id );
4292      if ( empty( $activity->component ) ) {
4293          return;
4294      }
4295  
4296      // Spam/ham the activity if it's not already in that state.
4297      if ( 'spam_activity' === $action && ! $activity->is_spam ) {
4298          bp_activity_mark_as_spam( $activity );
4299      } elseif ( 'ham_activity' == $action) {
4300          bp_activity_mark_as_ham( $activity );
4301      }
4302  
4303      // Add "new_post_type_comment" to the allowed activity types, so that the activity's Akismet history is generated.
4304      $post_type_comment_action = $activity_comment_object->action_id;
4305      $comment_akismet_history = function ( $activity_types ) use ( $post_type_comment_action ) {
4306          $activity_types[] = $post_type_comment_action;
4307  
4308          return $activity_types;
4309      };
4310      add_filter( 'bp_akismet_get_activity_types', $comment_akismet_history );
4311  
4312      // Make sure the activity change won't edit the comment if sync is on.
4313      remove_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
4314  
4315      // Save the updated activity.
4316      $activity->save();
4317  
4318      // Restore the action.
4319      add_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
4320  
4321      // Remove the dynamic permitting of the "new_blog_comment" activity type so we don't break anything.
4322      remove_filter( 'bp_akismet_get_activity_types', $comment_akismet_history );
4323  }
4324  add_action( 'transition_comment_status', 'bp_activity_transition_post_type_comment_status', 10, 3 );
4325  
4326  /**
4327   * Finds and exports personal data associated with an email address from the Activity tables.
4328   *
4329   * @since 4.0.0
4330   *
4331   * @param string $email_address  The user's email address.
4332   * @param int    $page           Batch number.
4333   * @return array An array of personal data.
4334   */
4335  function bp_activity_personal_data_exporter( $email_address, $page ) {
4336      $number = 50;
4337  
4338      $email_address = trim( $email_address );
4339  
4340      $data_to_export = array();
4341  
4342      $user = get_user_by( 'email', $email_address );
4343  
4344      if ( ! $user ) {
4345          return array(
4346              'data' => array(),
4347              'done' => true,
4348          );
4349      }
4350  
4351      $activities = bp_activity_get( array(
4352          'display_comments' => 'stream',
4353          'per_page'         => $number,
4354          'page'             => $page,
4355          'show_hidden'      => true,
4356          'filter'           => array(
4357              'user_id' => $user->ID,
4358          ),
4359      ) );
4360  
4361      $user_data_to_export = array();
4362      $activity_actions    = bp_activity_get_actions();
4363  
4364      foreach ( $activities['activities'] as $activity ) {
4365          if ( ! empty( $activity_actions->{$activity->component}->{$activity->type}['format_callback'] ) ) {
4366              $description = call_user_func( $activity_actions->{$activity->component}->{$activity->type}['format_callback'], '', $activity );
4367          } elseif ( ! empty( $activity->action ) ) {
4368              $description = $activity->action;
4369          } else {
4370              $description = $activity->type;
4371          }
4372  
4373          $item_data = array(
4374              array(
4375                  'name'  => __( 'Activity Date', 'buddypress' ),
4376                  'value' => $activity->date_recorded,
4377              ),
4378              array(
4379                  'name'  => __( 'Activity Description', 'buddypress' ),
4380                  'value' => $description,
4381              ),
4382              array(
4383                  'name'  => __( 'Activity URL', 'buddypress' ),
4384                  'value' => bp_activity_get_permalink( $activity->id, $activity ),
4385              ),
4386          );
4387  
4388          if ( ! empty( $activity->content ) ) {
4389              $item_data[] = array(
4390                  'name'  => __( 'Activity Content', 'buddypress' ),
4391                  'value' => $activity->content,
4392              );
4393          }
4394  
4395          /**
4396           * Filters the data associated with an activity item when assembled for a WP personal data export.
4397           *
4398           * Plugins that register activity types whose `action` string doesn't adequately
4399           * describe the activity item for the purposes of data export may filter the activity
4400           * item data here.
4401           *
4402           * @since 4.0.0
4403           *
4404           * @param array                $item_data Array of data describing the activity item.
4405           * @param BP_Activity_Activity $activity  Activity item.
4406           */
4407          $item_data = apply_filters( 'bp_activity_personal_data_export_item_data', $item_data, $activity );
4408  
4409          $data_to_export[] = array(
4410              'group_id'    => 'bp_activity',
4411              'group_label' => __( 'Activity', 'buddypress' ),
4412              'item_id'     => "bp-activity-{$activity->id}",
4413              'data'        => $item_data,
4414          );
4415      }
4416  
4417      // Tell core if we have more items to process.
4418      $done = count( $activities['activities'] ) < $number;
4419  
4420      return array(
4421          'data' => $data_to_export,
4422          'done' => $done,
4423      );
4424  }
4425  
4426  /**
4427   * Checks whether an activity feed is enabled.
4428   *
4429   * @since 8.0.0
4430   *
4431   * @param string $feed_id The feed identifier. Possible values are:
4432   *                        'sitewide', 'personal', 'friends', 'mygroups', 'mentions', 'favorites'.
4433   */
4434  function bp_activity_is_feed_enable( $feed_id = '' ) {
4435      /**
4436       * Filters if BuddyPress should consider feeds enabled. If disabled, it will return early.
4437       *
4438       * @since 1.8.0
4439       * @since 8.0.0 Adds the `$feed_id` parameter.
4440       *
4441       * @param bool   $value   Defaults to true aka feeds are enabled.
4442       * @param string $feed_id The feed identifier.
4443       */
4444      return (bool) apply_filters( 'bp_activity_enable_feeds', true, $feed_id );
4445  }


Generated: Thu Dec 5 01:00:58 2024 Cross-referenced by PHPXref 0.7.1