Skip to:
Content

BuddyPress.org


Ignore:
Timestamp:
02/15/2015 12:48:56 AM (10 years ago)
Author:
djpaul
Message:

Split each component's classes file, and move each individual class into its own file.

While historically manageable, the previous approach of having the majority of each component's classes in the same file is growing unwieldly with each new version of BuddyPress, and causing an avoidable increase in code complexity.

The existing -classes.php files are now a loading wrapper for the components' classes.

This change only affect classes that were explicitly declared within the -classes.php files. Any other classes, for example those in some components' template functions files, remain untouched for now.

Fixes #6083

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-activity/bp-activity-classes.php

    r9385 r9485  
    1010defined( 'ABSPATH' ) || exit;
    1111
    12 /**
    13  * Database interaction class for the BuddyPress activity component.
    14  *
    15  * Instance methods are available for creating/editing an activity,
    16  * static methods for querying activities.
    17  *
    18  * @since BuddyPress (1.0)
    19  */
    20 class BP_Activity_Activity {
    21 
    22     /** Properties ************************************************************/
    23 
    24     /**
    25      * ID of the activity item.
    26      *
    27      * @var int
    28      */
    29     var $id;
    30 
    31     /**
    32      * ID of the associated item.
    33      *
    34      * @var int
    35      */
    36     var $item_id;
    37 
    38     /**
    39      * ID of the associated secondary item.
    40      *
    41      * @var int
    42      */
    43     var $secondary_item_id;
    44 
    45     /**
    46      * ID of user associated with the activity item.
    47      *
    48      * @var int
    49      */
    50     var $user_id;
    51 
    52     /**
    53      * The primary URL for the activity in RSS feeds.
    54      *
    55      * @var string
    56      */
    57     var $primary_link;
    58 
    59     /**
    60      * BuddyPress component the activity item relates to.
    61      *
    62      * @var string
    63      */
    64     var $component;
    65 
    66     /**
    67      * Activity type, eg 'new_blog_post'.
    68      *
    69      * @var string
    70      */
    71     var $type;
    72 
    73     /**
    74      * Description of the activity, eg 'Alex updated his profile.'
    75      *
    76      * @var string
    77      */
    78     var $action;
    79 
    80     /**
    81      * The content of the activity item.
    82      *
    83      * @var string
    84      */
    85     var $content;
    86 
    87     /**
    88      * The date the activity item was recorded, in 'Y-m-d h:i:s' format.
    89      *
    90      * @var string
    91      */
    92     var $date_recorded;
    93 
    94     /**
    95      * Whether the item should be hidden in sitewide streams.
    96      *
    97      * @var int
    98      */
    99     var $hide_sitewide = false;
    100 
    101     /**
    102      * Node boundary start for activity or activity comment.
    103      *
    104      * @var int
    105      */
    106     var $mptt_left;
    107 
    108     /**
    109      * Node boundary end for activity or activity comment.
    110      *
    111      * @var int
    112      */
    113     var $mptt_right;
    114 
    115     /**
    116      * Whether this item is marked as spam.
    117      *
    118      * @var int
    119      */
    120     var $is_spam;
    121 
    122     /**
    123      * Constructor method.
    124      *
    125      * @param int $id Optional. The ID of a specific activity item.
    126      */
    127     public function __construct( $id = false ) {
    128         if ( !empty( $id ) ) {
    129             $this->id = $id;
    130             $this->populate();
    131         }
    132     }
    133 
    134     /**
    135      * Populate the object with data about the specific activity item.
    136      */
    137     public function populate() {
    138         global $wpdb;
    139 
    140         $row = wp_cache_get( $this->id, 'bp_activity' );
    141 
    142         if ( false === $row ) {
    143             $bp  = buddypress();
    144             $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->activity->table_name} WHERE id = %d", $this->id ) );
    145 
    146             wp_cache_set( $this->id, $row, 'bp_activity' );
    147         }
    148 
    149         if ( ! empty( $row ) ) {
    150             $this->id                = (int) $row->id;
    151             $this->item_id           = (int) $row->item_id;
    152             $this->secondary_item_id = (int) $row->secondary_item_id;
    153             $this->user_id           = (int) $row->user_id;
    154             $this->primary_link      = $row->primary_link;
    155             $this->component         = $row->component;
    156             $this->type              = $row->type;
    157             $this->action            = $row->action;
    158             $this->content           = $row->content;
    159             $this->date_recorded     = $row->date_recorded;
    160             $this->hide_sitewide     = $row->hide_sitewide;
    161             $this->mptt_left         = (int) $row->mptt_left;
    162             $this->mptt_right        = (int) $row->mptt_right;
    163             $this->is_spam           = $row->is_spam;
    164         }
    165 
    166         // Generate dynamic 'action' when possible
    167         $action = bp_activity_generate_action_string( $this );
    168         if ( false !== $action ) {
    169             $this->action = $action;
    170 
    171         // If no callback is available, use the literal string from
    172         // the database row
    173         } elseif ( ! empty( $row->action ) ) {
    174             $this->action = $row->action;
    175 
    176         // Provide a fallback to avoid PHP notices
    177         } else {
    178             $this->action = '';
    179         }
    180     }
    181 
    182     /**
    183      * Save the activity item to the database.
    184      *
    185      * @return bool True on success.
    186      */
    187     public function save() {
    188         global $wpdb;
    189 
    190         $bp = buddypress();
    191 
    192         $this->id                = apply_filters_ref_array( 'bp_activity_id_before_save',                array( $this->id,                &$this ) );
    193         $this->item_id           = apply_filters_ref_array( 'bp_activity_item_id_before_save',           array( $this->item_id,           &$this ) );
    194         $this->secondary_item_id = apply_filters_ref_array( 'bp_activity_secondary_item_id_before_save', array( $this->secondary_item_id, &$this ) );
    195         $this->user_id           = apply_filters_ref_array( 'bp_activity_user_id_before_save',           array( $this->user_id,           &$this ) );
    196         $this->primary_link      = apply_filters_ref_array( 'bp_activity_primary_link_before_save',      array( $this->primary_link,      &$this ) );
    197         $this->component         = apply_filters_ref_array( 'bp_activity_component_before_save',         array( $this->component,         &$this ) );
    198         $this->type              = apply_filters_ref_array( 'bp_activity_type_before_save',              array( $this->type,              &$this ) );
    199         $this->action            = apply_filters_ref_array( 'bp_activity_action_before_save',            array( $this->action,            &$this ) );
    200         $this->content           = apply_filters_ref_array( 'bp_activity_content_before_save',           array( $this->content,           &$this ) );
    201         $this->date_recorded     = apply_filters_ref_array( 'bp_activity_date_recorded_before_save',     array( $this->date_recorded,     &$this ) );
    202         $this->hide_sitewide     = apply_filters_ref_array( 'bp_activity_hide_sitewide_before_save',     array( $this->hide_sitewide,     &$this ) );
    203         $this->mptt_left         = apply_filters_ref_array( 'bp_activity_mptt_left_before_save',         array( $this->mptt_left,         &$this ) );
    204         $this->mptt_right        = apply_filters_ref_array( 'bp_activity_mptt_right_before_save',        array( $this->mptt_right,        &$this ) );
    205         $this->is_spam           = apply_filters_ref_array( 'bp_activity_is_spam_before_save',           array( $this->is_spam,           &$this ) );
    206 
    207         /**
    208          * Fires before the current activity item gets saved.
    209          *
    210          * Please use this hook to filter the properties above. Each part will be passed in.
    211          *
    212          * @since BuddyPress (1.0.0)
    213          *
    214          * @param BP_Activity_Activity Current instance of the activity item being saved.
    215          */
    216         do_action_ref_array( 'bp_activity_before_save', array( &$this ) );
    217 
    218         if ( empty( $this->component ) || empty( $this->type ) ) {
    219             return false;
    220         }
    221 
    222         if ( empty( $this->primary_link ) ) {
    223             $this->primary_link = bp_loggedin_user_domain();
    224         }
    225 
    226         // If we have an existing ID, update the activity item, otherwise insert it.
    227         if ( ! empty( $this->id ) ) {
    228             $q = $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET user_id = %d, component = %s, type = %s, action = %s, content = %s, primary_link = %s, date_recorded = %s, item_id = %d, secondary_item_id = %d, hide_sitewide = %d, is_spam = %d WHERE id = %d", $this->user_id, $this->component, $this->type, $this->action, $this->content, $this->primary_link, $this->date_recorded, $this->item_id, $this->secondary_item_id, $this->hide_sitewide, $this->is_spam, $this->id );
    229         } else {
    230             $q = $wpdb->prepare( "INSERT INTO {$bp->activity->table_name} ( user_id, component, type, action, content, primary_link, date_recorded, item_id, secondary_item_id, hide_sitewide, is_spam ) VALUES ( %d, %s, %s, %s, %s, %s, %s, %d, %d, %d, %d )", $this->user_id, $this->component, $this->type, $this->action, $this->content, $this->primary_link, $this->date_recorded, $this->item_id, $this->secondary_item_id, $this->hide_sitewide, $this->is_spam );
    231         }
    232 
    233         if ( false === $wpdb->query( $q ) ) {
    234             return false;
    235         }
    236 
    237         // If this is a new activity item, set the $id property
    238         if ( empty( $this->id ) ) {
    239             $this->id = $wpdb->insert_id;
    240 
    241         // If an existing activity item, prevent any changes to the content generating new @mention notifications.
    242         } else {
    243             add_filter( 'bp_activity_at_name_do_notifications', '__return_false' );
    244         }
    245 
    246         /**
    247          * Fires after an activity item has been saved to the database.
    248          *
    249          * @since BuddyPress (1.0.0)
    250          *
    251          * @param BP_Activity_Activity Reference to current instance of activity being saved.
    252          */
    253         do_action_ref_array( 'bp_activity_after_save', array( &$this ) );
    254 
    255         return true;
    256     }
    257 
    258     /** Static Methods ***************************************************/
    259 
    260     /**
    261      * Get activity items, as specified by parameters
    262      *
    263      * @see BP_Activity_Activity::get_filter_sql() for a description of the
    264      *      'filter' parameter.
    265      * @see WP_Meta_Query::queries for a description of the 'meta_query'
    266      *      parameter format.
    267      *
    268      * @param array $args {
    269      *     An array of arguments. All items are optional.
    270      *
    271      *     @type int          $page              Which page of results to fetch. Using page=1 without per_page will result
    272      *                                           in no pagination. Default: 1.
    273      *     @type int|bool     $per_page          Number of results per page. Default: 25.
    274      *     @type int|bool     $max               Maximum number of results to return. Default: false (unlimited).
    275      *     @type string       $sort              ASC or DESC. Default: 'DESC'.
    276      *     @type array        $exclude           Array of activity IDs to exclude. Default: false.
    277      *     @type array        $in                Array of ids to limit query by (IN). Default: false.
    278      *     @type array        $meta_query        Array of meta_query conditions. See WP_Meta_Query::queries.
    279      *     @type array        $date_query        Array of date_query conditions. See first parameter of
    280      *                                           WP_Date_Query::__construct().
    281      *     @type array        $filter_query      Array of advanced query conditions. See BP_Activity_Query::__construct().
    282      *     @type string|array $scope             Pre-determined set of activity arguments.
    283      *     @type array        $filter            See BP_Activity_Activity::get_filter_sql().
    284      *     @type string       $search_terms      Limit results by a search term. Default: false.
    285      *     @type bool         $display_comments  Whether to include activity comments. Default: false.
    286      *     @type bool         $show_hidden       Whether to show items marked hide_sitewide. Default: false.
    287      *     @type string       $spam              Spam status. Default: 'ham_only'.
    288      *     @type bool         $update_meta_cache Whether to pre-fetch metadata for queried activity items. Default: true.
    289      *     @type string|bool  $count_total       If true, an additional DB query is run to count the total activity items
    290      *                                           for the query. Default: false.
    291      * }
    292      * @return array The array returned has two keys:
    293      *     - 'total' is the count of located activities
    294      *     - 'activities' is an array of the located activities
    295      */
    296     public static function get( $args = array() ) {
    297         global $wpdb;
    298 
    299         // Backward compatibility with old method of passing arguments
    300         if ( !is_array( $args ) || func_num_args() > 1 ) {
    301             _deprecated_argument( __METHOD__, '1.6', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
    302 
    303             $old_args_keys = array(
    304                 0 => 'max',
    305                 1 => 'page',
    306                 2 => 'per_page',
    307                 3 => 'sort',
    308                 4 => 'search_terms',
    309                 5 => 'filter',
    310                 6 => 'display_comments',
    311                 7 => 'show_hidden',
    312                 8 => 'exclude',
    313                 9 => 'in',
    314                 10 => 'spam'
    315             );
    316 
    317             $func_args = func_get_args();
    318             $args      = bp_core_parse_args_array( $old_args_keys, $func_args );
    319         }
    320 
    321         $bp = buddypress();
    322         $r  = wp_parse_args( $args, array(
    323             'page'              => 1,          // The current page
    324             'per_page'          => 25,         // Activity items per page
    325             'max'               => false,      // Max number of items to return
    326             'sort'              => 'DESC',     // ASC or DESC
    327             'exclude'           => false,      // Array of ids to exclude
    328             'in'                => false,      // Array of ids to limit query by (IN)
    329             'meta_query'        => false,      // Filter by activitymeta
    330             'date_query'        => false,      // Filter by date
    331             'filter_query'      => false,      // Advanced filtering - see BP_Activity_Query
    332             'filter'            => false,      // See self::get_filter_sql()
    333             'scope'             => false,      // Preset activity arguments
    334             'search_terms'      => false,      // Terms to search by
    335             'display_comments'  => false,      // Whether to include activity comments
    336             'show_hidden'       => false,      // Show items marked hide_sitewide
    337             'spam'              => 'ham_only', // Spam status
    338             'update_meta_cache' => true,
    339             'count_total'       => false,
    340         ) );
    341 
    342         // Select conditions
    343         $select_sql = "SELECT DISTINCT a.id";
    344 
    345         $from_sql   = " FROM {$bp->activity->table_name} a";
    346 
    347         $join_sql   = '';
    348 
    349         // Where conditions
    350         $where_conditions = array();
    351 
    352         // Excluded types
    353         $excluded_types = array();
    354 
    355         // Scope takes precedence
    356         if ( ! empty( $r['scope'] ) ) {
    357             $scope_query = self::get_scope_query_sql( $r['scope'], $r );
    358 
    359             // Add our SQL conditions if matches were found
    360             if ( ! empty( $scope_query['sql'] ) ) {
    361                 $where_conditions['scope_query_sql'] = $scope_query['sql'];
    362             }
    363 
    364             // override some arguments if needed
    365             if ( ! empty( $scope_query['override'] ) ) {
    366                 $r = self::array_replace_recursive( $r, $scope_query['override'] );
    367             }
    368 
    369         // Advanced filtering
    370         } elseif ( ! empty( $r['filter_query'] ) ) {
    371             $filter_query = new BP_Activity_Query( $r['filter_query'] );
    372             $sql          = $filter_query->get_sql();
    373             if ( ! empty( $sql ) ) {
    374                 $where_conditions['filter_query_sql'] = $sql;
    375             }
    376         }
    377 
    378         // Regular filtering
    379         if ( $r['filter'] && $filter_sql = BP_Activity_Activity::get_filter_sql( $r['filter'] ) ) {
    380             $where_conditions['filter_sql'] = $filter_sql;
    381         }
    382 
    383         // Spam
    384         if ( 'ham_only' == $r['spam'] ) {
    385             $where_conditions['spam_sql'] = 'a.is_spam = 0';
    386         } elseif ( 'spam_only' == $r['spam'] ) {
    387             $where_conditions['spam_sql'] = 'a.is_spam = 1';
    388         }
    389 
    390         // Searching
    391         if ( $r['search_terms'] ) {
    392             $search_terms_like = '%' . bp_esc_like( $r['search_terms'] ) . '%';
    393             $where_conditions['search_sql'] = $wpdb->prepare( 'a.content LIKE %s', $search_terms_like );
    394         }
    395 
    396         // Sorting
    397         $sort = $r['sort'];
    398         if ( $sort != 'ASC' && $sort != 'DESC' ) {
    399             $sort = 'DESC';
    400         }
    401 
    402         // Hide Hidden Items?
    403         if ( ! $r['show_hidden'] ) {
    404             $where_conditions['hidden_sql'] = "a.hide_sitewide = 0";
    405         }
    406 
    407         // Exclude specified items
    408         if ( ! empty( $r['exclude'] ) ) {
    409             $exclude = implode( ',', wp_parse_id_list( $r['exclude'] ) );
    410             $where_conditions['exclude'] = "a.id NOT IN ({$exclude})";
    411         }
    412 
    413         // The specific ids to which you want to limit the query
    414         if ( ! empty( $r['in'] ) ) {
    415             $in = implode( ',', wp_parse_id_list( $r['in'] ) );
    416             $where_conditions['in'] = "a.id IN ({$in})";
    417         }
    418 
    419         // Process meta_query into SQL
    420         $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] );
    421 
    422         if ( ! empty( $meta_query_sql['join'] ) ) {
    423             $join_sql .= $meta_query_sql['join'];
    424         }
    425 
    426         if ( ! empty( $meta_query_sql['where'] ) ) {
    427             $where_conditions[] = $meta_query_sql['where'];
    428         }
    429 
    430         // Process date_query into SQL
    431         $date_query_sql = self::get_date_query_sql( $r['date_query'] );
    432 
    433         if ( ! empty( $date_query_sql ) ) {
    434             $where_conditions['date'] = $date_query_sql;
    435         }
    436 
    437         // Alter the query based on whether we want to show activity item
    438         // comments in the stream like normal comments or threaded below
    439         // the activity.
    440         if ( false === $r['display_comments'] || 'threaded' === $r['display_comments'] ) {
    441             $excluded_types[] = 'activity_comment';
    442         }
    443 
    444         // Exclude 'last_activity' items unless the 'action' filter has
    445         // been explicitly set
    446         if ( empty( $r['filter']['object'] ) ) {
    447             $excluded_types[] = 'last_activity';
    448         }
    449 
    450         // Build the excluded type sql part
    451         if ( ! empty( $excluded_types ) ) {
    452             $not_in = "'" . implode( "', '", esc_sql( $excluded_types ) ) . "'";
    453             $where_conditions['excluded_types'] = "a.type NOT IN ({$not_in})";
    454         }
    455 
    456         /**
    457          * Filters the MySQL WHERE conditions for the Activity items get method.
    458          *
    459          * @since BuddyPress (1.9.0)
    460          *
    461          * @param array  $where_conditions Current conditions for MySQL WHERE statement.
    462          * @param array  $r Parsed arguments passed into method.
    463          * @param string $select_sql Current SELECT MySQL statement at point of execution.
    464          * @param string $from_sql Current FROM MySQL statement at point of execution.
    465          * @param string $join_sql Current INNER JOIN MySQL statement at point of execution.
    466          */
    467         $where_conditions = apply_filters( 'bp_activity_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql );
    468 
    469         // Join the where conditions together
    470         $where_sql = 'WHERE ' . join( ' AND ', $where_conditions );
    471 
    472         /**
    473          * Filters the preferred order of indexes for activity item.
    474          *
    475          * @since BuddyPress (1.6.0)
    476          *
    477          * @param array Array of indexes in preferred order.
    478          */
    479         $indexes = apply_filters( 'bp_activity_preferred_index_order', array( 'user_id', 'item_id', 'secondary_item_id', 'date_recorded', 'component', 'type', 'hide_sitewide', 'is_spam' ) );
    480 
    481         foreach( $indexes as $key => $index ) {
    482             if ( false !== strpos( $where_sql, $index ) ) {
    483                 $the_index = $index;
    484                 break; // Take the first one we find
    485             }
    486         }
    487 
    488         if ( !empty( $the_index ) ) {
    489             $index_hint_sql = "USE INDEX ({$the_index})";
    490         } else {
    491             $index_hint_sql = '';
    492         }
    493 
    494         // Sanitize page and per_page parameters
    495         $page     = absint( $r['page']     );
    496         $per_page = absint( $r['per_page'] );
    497 
    498         $retval = array(
    499             'activities'     => null,
    500             'total'          => null,
    501             'has_more_items' => null,
    502         );
    503 
    504         /**
    505          * Filters if BuddyPress should use legacy query structure over current structure for version 2.0+.
    506          *
    507          * It is not recommended to use the legacy structure, but allowed to if needed.
    508          *
    509          * @since BuddyPress (2.0.0)
    510          *
    511          * @param bool                 Whether to use legacy structure or not.
    512          * @param BP_Activity_Activity Current method being called.
    513          * @param array                $r Parsed arguments passed into method.
    514          */
    515         if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $r ) ) {
    516 
    517             // Legacy queries joined against the user table
    518             $select_sql = "SELECT DISTINCT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name";
    519             $from_sql   = " FROM {$bp->activity->table_name} a LEFT JOIN {$wpdb->users} u ON a.user_id = u.ID";
    520 
    521             if ( ! empty( $page ) && ! empty( $per_page ) ) {
    522                 $pag_sql    = $wpdb->prepare( "LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page );
    523 
    524                 /** this filter is documented in bp-activity/bp-activity-classes.php */
    525                 $activities = $wpdb->get_results( apply_filters( 'bp_activity_get_user_join_filter', "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort} {$pag_sql}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ) );
    526             } else {
    527                 $pag_sql    = '';
    528 
    529                 /**
    530                  * Filters the legacy MySQL query statement so plugins can alter before results are fetched.
    531                  *
    532                  * @since BuddyPress (1.5.0)
    533                  *
    534                  * @param string Concatenated MySQL statement pieces to be query results with for legacy query.
    535                  * @param string $select_sql Final SELECT MySQL statement portion for legacy query.
    536                  * @param string $from_sql Final FROM MySQL statement portion for legacy query.
    537                  * @param string $where_sql Final WHERE MySQL statement portion for legacy query.
    538                  * @param string $sort Final sort direction for legacy query.
    539                  */
    540                 $activities = $wpdb->get_results( apply_filters( 'bp_activity_get_user_join_filter', "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ) );
    541             }
    542 
    543         } else {
    544             // Query first for activity IDs
    545             $activity_ids_sql = "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}";
    546 
    547             if ( ! empty( $per_page ) && ! empty( $page ) ) {
    548                 // We query for $per_page + 1 items in order to
    549                 // populate the has_more_items flag
    550                 $activity_ids_sql .= $wpdb->prepare( " LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page + 1 );
    551             }
    552 
    553             /**
    554              * Filters the paged activities MySQL statement.
    555              *
    556              * @since BuddyPress (2.0.0)
    557              *
    558              * @param string $activity_ids_sql MySQL statement used to query for Activity IDs.
    559              * @param array  $r Array of arguments passed into method.
    560              */
    561             $activity_ids_sql = apply_filters( 'bp_activity_paged_activities_sql', $activity_ids_sql, $r );
    562 
    563             $activity_ids = $wpdb->get_col( $activity_ids_sql );
    564 
    565             $retval['has_more_items'] = ! empty( $per_page ) && count( $activity_ids ) > $per_page;
    566 
    567             // If we've fetched more than the $per_page value, we
    568             // can discard the extra now
    569             if ( ! empty( $per_page ) && count( $activity_ids ) === $per_page + 1 ) {
    570                 array_pop( $activity_ids );
    571             }
    572 
    573             $activities = self::get_activity_data( $activity_ids );
    574         }
    575 
    576         // Get the fullnames of users so we don't have to query in the loop
    577         $activities = self::append_user_fullnames( $activities );
    578 
    579         // Get activity meta
    580         $activity_ids = array();
    581         foreach ( (array) $activities as $activity ) {
    582             $activity_ids[] = $activity->id;
    583         }
    584 
    585         if ( ! empty( $activity_ids ) && $r['update_meta_cache'] ) {
    586             bp_activity_update_meta_cache( $activity_ids );
    587         }
    588 
    589         if ( $activities && $r['display_comments'] ) {
    590             $activities = BP_Activity_Activity::append_comments( $activities, $r['spam'] );
    591         }
    592 
    593         // Pre-fetch data associated with activity users and other objects
    594         BP_Activity_Activity::prefetch_object_data( $activities );
    595 
    596         // Generate action strings
    597         $activities = BP_Activity_Activity::generate_action_strings( $activities );
    598 
    599         $retval['activities'] = $activities;
    600 
    601         // If $max is set, only return up to the max results
    602         if ( ! empty( $r['count_total'] ) ) {
    603 
    604             /**
    605              * Filters the total activities MySQL statement.
    606              *
    607              * @since BuddyPress (1.5.0)
    608              *
    609              * @param string MySQL statement used to query for total activities.
    610              * @param string $where_sql MySQL WHERE statement portion.
    611              * @param string $sort sort direction for query.
    612              */
    613             $total_activities_sql = apply_filters( 'bp_activity_total_activities_sql', "SELECT count(DISTINCT a.id) FROM {$bp->activity->table_name} a {$join_sql} {$where_sql}", $where_sql, $sort );
    614             $total_activities     = $wpdb->get_var( $total_activities_sql );
    615 
    616             if ( !empty( $r['max'] ) ) {
    617                 if ( (int) $total_activities > (int) $r['max'] ) {
    618                     $total_activities = $r['max'];
    619                 }
    620             }
    621 
    622             $retval['total'] = $total_activities;
    623         }
    624 
    625         return $retval;
    626     }
    627 
    628     /**
    629      * Convert activity IDs to activity objects, as expected in template loop.
    630      *
    631      * @since 2.0
    632      *
    633      * @param array $activity_ids Array of activity IDs.
    634      * @return array
    635      */
    636     protected static function get_activity_data( $activity_ids = array() ) {
    637         global $wpdb;
    638 
    639         // Bail if no activity ID's passed
    640         if ( empty( $activity_ids ) ) {
    641             return array();
    642         }
    643 
    644         // Get BuddyPress
    645         $bp = buddypress();
    646 
    647         $activities   = array();
    648         $uncached_ids = bp_get_non_cached_ids( $activity_ids, 'bp_activity' );
    649 
    650         // Prime caches as necessary
    651         if ( ! empty( $uncached_ids ) ) {
    652             // Format the activity ID's for use in the query below
    653             $uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) );
    654 
    655             // Fetch data from activity table, preserving order
    656             $queried_adata = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} WHERE id IN ({$uncached_ids_sql})");
    657 
    658             // Put that data into the placeholders created earlier,
    659             // and add it to the cache
    660             foreach ( (array) $queried_adata as $adata ) {
    661                 wp_cache_set( $adata->id, $adata, 'bp_activity' );
    662             }
    663         }
    664 
    665         // Now fetch data from the cache
    666         foreach ( $activity_ids as $activity_id ) {
    667             $activities[] = wp_cache_get( $activity_id, 'bp_activity' );
    668         }
    669 
    670         // Then fetch user data
    671         $user_query = new BP_User_Query( array(
    672             'user_ids'        => wp_list_pluck( $activities, 'user_id' ),
    673             'populate_extras' => false,
    674         ) );
    675 
    676         // Associated located user data with activity items
    677         foreach ( $activities as $a_index => $a_item ) {
    678             $a_user_id = intval( $a_item->user_id );
    679             $a_user    = isset( $user_query->results[ $a_user_id ] ) ? $user_query->results[ $a_user_id ] : '';
    680 
    681             if ( !empty( $a_user ) ) {
    682                 $activities[ $a_index ]->user_email    = $a_user->user_email;
    683                 $activities[ $a_index ]->user_nicename = $a_user->user_nicename;
    684                 $activities[ $a_index ]->user_login    = $a_user->user_login;
    685                 $activities[ $a_index ]->display_name  = $a_user->display_name;
    686             }
    687         }
    688 
    689         return $activities;
    690     }
    691 
    692     /**
    693      * Append xProfile fullnames to an activity array.
    694      *
    695      * @since BuddyPress (2.0.0)
    696      *
    697      * @param array $activities Activities array.
    698      * @return array
    699      */
    700     protected static function append_user_fullnames( $activities ) {
    701 
    702         if ( bp_is_active( 'xprofile' ) && ! empty( $activities ) ) {
    703             $activity_user_ids = wp_list_pluck( $activities, 'user_id' );
    704 
    705             if ( ! empty( $activity_user_ids ) ) {
    706                 $fullnames = bp_core_get_user_displaynames( $activity_user_ids );
    707                 if ( ! empty( $fullnames ) ) {
    708                     foreach ( (array) $activities as $i => $activity ) {
    709                         if ( ! empty( $fullnames[ $activity->user_id ] ) ) {
    710                             $activities[ $i ]->user_fullname = $fullnames[ $activity->user_id ];
    711                         }
    712                     }
    713                 }
    714             }
    715         }
    716 
    717         return $activities;
    718     }
    719 
    720     /**
    721      * Pre-fetch data for objects associated with activity items.
    722      *
    723      * Activity items are associated with users, and often with other
    724      * BuddyPress data objects. Here, we pre-fetch data about these
    725      * associated objects, so that inline lookups - done primarily when
    726      * building action strings - do not result in excess database queries.
    727      *
    728      * The only object data required for activity component activity types
    729      * (activity_update and activity_comment) is related to users, and that
    730      * info is fetched separately in BP_Activity_Activity::get_activity_data().
    731      * So this method contains nothing but a filter that allows other
    732      * components, such as bp-friends and bp-groups, to hook in and prime
    733      * their own caches at the beginning of an activity loop.
    734      *
    735      * @since BuddyPress (2.0.0)
    736      *
    737      * @param array $activities Array of activities.
    738      */
    739     protected static function prefetch_object_data( $activities ) {
    740 
    741         /**
    742          * Filters inside prefetch_object_data method to aid in pre-fetching object data associated with activity item.
    743          *
    744          * @since BuddyPress (2.0.0)
    745          *
    746          * @param array $activities Array of activities.
    747          */
    748         return apply_filters( 'bp_activity_prefetch_object_data', $activities );
    749     }
    750 
    751     /**
    752      * Generate action strings for the activities located in BP_Activity_Activity::get().
    753      *
    754      * If no string can be dynamically generated for a given item
    755      * (typically because the activity type has not been properly
    756      * registered), the static 'action' value pulled from the database will
    757      * be left in place.
    758      *
    759      * @since BuddyPress (2.0.0)
    760      *
    761      * @param array $activities Array of activities.
    762      * @return array
    763      */
    764     protected static function generate_action_strings( $activities ) {
    765         foreach ( $activities as $key => $activity ) {
    766             $generated_action = bp_activity_generate_action_string( $activity );
    767             if ( false !== $generated_action ) {
    768                 $activity->action = $generated_action;
    769             }
    770 
    771             $activities[ $key ] = $activity;
    772         }
    773 
    774         return $activities;
    775     }
    776 
    777     /**
    778      * Get the SQL for the 'meta_query' param in BP_Activity_Activity::get().
    779      *
    780      * We use WP_Meta_Query to do the heavy lifting of parsing the
    781      * meta_query array and creating the necessary SQL clauses. However,
    782      * since BP_Activity_Activity::get() builds its SQL differently than
    783      * WP_Query, we have to alter the return value (stripping the leading
    784      * AND keyword from the 'where' clause).
    785      *
    786      * @since BuddyPress (1.8)
    787      *
    788      * @param array $meta_query An array of meta_query filters. See the
    789      *   documentation for WP_Meta_Query for details.
    790      * @return array $sql_array 'join' and 'where' clauses.
    791      */
    792     public static function get_meta_query_sql( $meta_query = array() ) {
    793         global $wpdb;
    794 
    795         $sql_array = array(
    796             'join'  => '',
    797             'where' => '',
    798         );
    799 
    800         if ( ! empty( $meta_query ) ) {
    801             $activity_meta_query = new WP_Meta_Query( $meta_query );
    802 
    803             // WP_Meta_Query expects the table name at
    804             // $wpdb->activitymeta
    805             $wpdb->activitymeta = buddypress()->activity->table_name_meta;
    806 
    807             $meta_sql = $activity_meta_query->get_sql( 'activity', 'a', 'id' );
    808 
    809             // Strip the leading AND - BP handles it in get()
    810             $sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] );
    811             $sql_array['join']  = $meta_sql['join'];
    812         }
    813 
    814         return $sql_array;
    815     }
    816 
    817     /**
    818      * Get the SQL for the 'date_query' param in BP_Activity_Activity::get().
    819      *
    820      * We use BP_Date_Query, which extends WP_Date_Query, to do the heavy lifting
    821      * of parsing the date_query array and creating the necessary SQL clauses.
    822      * However, since BP_Activity_Activity::get() builds its SQL differently than
    823      * WP_Query, we have to alter the return value (stripping the leading AND
    824      * keyword from the query).
    825      *
    826      * @since BuddyPress (2.1.0)
    827      *
    828      * @param array $date_query An array of date_query parameters. See the
    829      *        documentation for the first parameter of WP_Date_Query.
    830      * @return string
    831      */
    832     public static function get_date_query_sql( $date_query = array() ) {
    833         $sql = '';
    834 
    835         // Date query
    836         if ( ! empty( $date_query ) && is_array( $date_query ) && class_exists( 'BP_Date_Query' ) ) {
    837             $date_query = new BP_Date_Query( $date_query, 'date_recorded' );
    838             $sql = preg_replace( '/^\sAND/', '', $date_query->get_sql() );
    839         }
    840 
    841         return $sql;
    842     }
    843 
    844     /**
    845      * Get the SQL for the 'scope' param in BP_Activity_Activity::get().
    846      *
    847      * A scope is a predetermined set of activity arguments.  This method is used
    848      * to grab these activity arguments and override any existing args if needed.
    849      *
    850      * Can handle multiple scopes.
    851      *
    852      * @since BuddyPress (2.2.0)
    853      *
    854      * @param  mixed $scope  The activity scope. Accepts string or array of scopes
    855      * @param  array $r      Current activity arguments. Same as those of BP_Activity_Activity::get(),
    856      *                       but merged with defaults.
    857      * @return array 'sql' WHERE SQL string and 'override' activity args
    858      */
    859     public static function get_scope_query_sql( $scope = false, $r = array() ) {
    860 
    861         // Define arrays for future use
    862         $query_args = array();
    863         $override   = array();
    864         $retval     = array();
    865 
    866         // Check for array of scopes
    867         if ( is_array( $scope ) ) {
    868             $scopes = $scope;
    869 
    870         // Explode a comma separated string of scopes
    871         } elseif ( is_string( $scope ) ) {
    872             $scopes = explode( ',', $scope );
    873         }
    874 
    875         // Bail if no scope passed
    876         if ( empty( $scopes ) ) {
    877             return false;
    878         }
    879 
    880         // Helper to easily grab the 'user_id'
    881         if ( ! empty( $r['filter']['user_id'] ) ) {
    882             $r['user_id'] = $r['filter']['user_id'];
    883         }
    884 
    885         // parse each scope; yes! we handle multiples!
    886         foreach ( $scopes as $scope ) {
    887             $scope_args = array();
    888 
    889             /**
    890              * Plugins can hook here to set their activity arguments for custom scopes.
    891              *
    892              * This is a dynamic filter based on the activity scope. eg:
    893              *   - 'bp_activity_set_groups_scope_args'
    894              *   - 'bp_activity_set_friends_scope_args'
    895              *
    896              * To see how this filter is used, plugin devs should check out:
    897              *   - bp_groups_filter_activity_scope() - used for 'groups' scope
    898              *   - bp_friends_filter_activity_scope() - used for 'friends' scope
    899              *
    900              * @since BuddyPress (2.2.0)
    901              *
    902              *  @param array {
    903              *     Activity query clauses.
    904              *
    905              *     @type array {
    906              *         Activity arguments for your custom scope.
    907              *         See {@link BP_Activity_Query::_construct()} for more details.
    908              *     }
    909              *     @type array $override Optional. Override existing activity arguments passed by $r.
    910              * }
    911              * @param array $r Current activity arguments passed in BP_Activity_Activity::get()
    912              */
    913             $scope_args = apply_filters( "bp_activity_set_{$scope}_scope_args", array(), $r );
    914 
    915             if ( ! empty( $scope_args ) ) {
    916                 // merge override properties from other scopes
    917                 // this might be a problem...
    918                 if ( ! empty( $scope_args['override'] ) ) {
    919                     $override = array_merge( $override, $scope_args['override'] );
    920                     unset( $scope_args['override'] );
    921                 }
    922 
    923                 // save scope args
    924                 if ( ! empty( $scope_args ) ) {
    925                     $query_args[] = $scope_args;
    926                 }
    927             }
    928         }
    929 
    930         if ( ! empty( $query_args ) ) {
    931             // set relation to OR
    932             $query_args['relation'] = 'OR';
    933 
    934             $query = new BP_Activity_Query( $query_args );
    935             $sql   = $query->get_sql();
    936             if ( ! empty( $sql ) ) {
    937                 $retval['sql'] = $sql;
    938             }
    939         }
    940 
    941         if ( ! empty( $override ) ) {
    942             $retval['override'] = $override;
    943         }
    944 
    945         return $retval;
    946     }
    947 
    948     /**
    949      * In BuddyPress 1.2.x, this was used to retrieve specific activity stream items (for example, on an activity's permalink page).
    950      *
    951      * As of 1.5.x, use BP_Activity_Activity::get() with an 'in' parameter instead.
    952      *
    953      * @since BuddyPress (1.2)
    954      *
    955      * @deprecated 1.5
    956      * @deprecated Use BP_Activity_Activity::get() with an 'in' parameter instead.
    957      *
    958      * @param mixed $activity_ids Array or comma-separated string of activity IDs to retrieve
    959      * @param int $max Maximum number of results to return. (Optional; default is no maximum)
    960      * @param int $page The set of results that the user is viewing. Used in pagination. (Optional; default is 1)
    961      * @param int $per_page Specifies how many results per page. Used in pagination. (Optional; default is 25)
    962      * @param string MySQL column sort; ASC or DESC. (Optional; default is DESC)
    963      * @param bool $display_comments Retrieve an activity item's associated comments or not. (Optional; default is false)
    964      * @return array
    965      */
    966     public static function get_specific( $activity_ids, $max = false, $page = 1, $per_page = 25, $sort = 'DESC', $display_comments = false ) {
    967         _deprecated_function( __FUNCTION__, '1.5', 'Use BP_Activity_Activity::get() with the "in" parameter instead.' );
    968         return BP_Activity_Activity::get( $max, $page, $per_page, $sort, false, false, $display_comments, false, false, $activity_ids );
    969     }
    970 
    971     /**
    972      * Get the first activity ID that matches a set of criteria.
    973      *
    974      * @param int    $user_id           User ID to filter by
    975      * @param string $component         Component to filter by
    976      * @param string $type              Activity type to filter by
    977      * @param int    $item_id           Associated item to filter by
    978      * @param int    $secondary_item_id Secondary associated item to filter by
    979      * @param string $action            Action to filter by
    980      * @param string $content           Content to filter by
    981      * @param string $date_recorded     Date to filter by
    982      *
    983      * @todo Should parameters be optional?
    984      *
    985      * @return int|bool Activity ID on success, false if none is found.
    986      */
    987     public static function get_id( $user_id, $component, $type, $item_id, $secondary_item_id, $action, $content, $date_recorded ) {
    988         global $wpdb;
    989 
    990         $bp = buddypress();
    991 
    992         $where_args = false;
    993 
    994         if ( ! empty( $user_id ) ) {
    995             $where_args[] = $wpdb->prepare( "user_id = %d", $user_id );
    996         }
    997 
    998         if ( ! empty( $component ) ) {
    999             $where_args[] = $wpdb->prepare( "component = %s", $component );
    1000         }
    1001 
    1002         if ( ! empty( $type ) ) {
    1003             $where_args[] = $wpdb->prepare( "type = %s", $type );
    1004         }
    1005 
    1006         if ( ! empty( $item_id ) ) {
    1007             $where_args[] = $wpdb->prepare( "item_id = %d", $item_id );
    1008         }
    1009 
    1010         if ( ! empty( $secondary_item_id ) ) {
    1011             $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id );
    1012         }
    1013 
    1014         if ( ! empty( $action ) ) {
    1015             $where_args[] = $wpdb->prepare( "action = %s", $action );
    1016         }
    1017 
    1018         if ( ! empty( $content ) ) {
    1019             $where_args[] = $wpdb->prepare( "content = %s", $content );
    1020         }
    1021 
    1022         if ( ! empty( $date_recorded ) ) {
    1023             $where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded );
    1024         }
    1025 
    1026         if ( ! empty( $where_args ) ) {
    1027             $where_sql = 'WHERE ' . join( ' AND ', $where_args );
    1028             return $wpdb->get_var( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" );
    1029         }
    1030 
    1031         return false;
    1032     }
    1033 
    1034     /**
    1035      * Delete activity items from the database.
    1036      *
    1037      * To delete a specific activity item, pass an 'id' parameter.
    1038      * Otherwise use the filters.
    1039      *
    1040      * @since BuddyPress (1.2)
    1041      *
    1042      * @param array $args {
    1043      *     @int $id Optional. The ID of a specific item to delete.
    1044      *     @string $action Optional. The action to filter by.
    1045      *     @string $content Optional. The content to filter by.
    1046      *     @string $component Optional. The component name to filter by.
    1047      *     @string $type Optional. The activity type to filter by.
    1048      *     @string $primary_link Optional. The primary URL to filter by.
    1049      *     @int $user_id Optional. The user ID to filter by.
    1050      *     @int $item_id Optional. The associated item ID to filter by.
    1051      *     @int $secondary_item_id Optional. The secondary associated item ID to filter by.
    1052      *     @string $date_recorded Optional. The date to filter by.
    1053      *     @int $hide_sitewide Optional. Default: false.
    1054      * }
    1055      * @return array|bool An array of deleted activity IDs on success, false on failure.
    1056      */
    1057     public static function delete( $args = array() ) {
    1058         global $wpdb;
    1059 
    1060         $bp = buddypress();
    1061 
    1062         $defaults = array(
    1063             'id'                => false,
    1064             'action'            => false,
    1065             'content'           => false,
    1066             'component'         => false,
    1067             'type'              => false,
    1068             'primary_link'      => false,
    1069             'user_id'           => false,
    1070             'item_id'           => false,
    1071             'secondary_item_id' => false,
    1072             'date_recorded'     => false,
    1073             'hide_sitewide'     => false
    1074         );
    1075         $params = wp_parse_args( $args, $defaults );
    1076         extract( $params );
    1077 
    1078         $where_args = false;
    1079 
    1080         if ( !empty( $id ) )
    1081             $where_args[] = $wpdb->prepare( "id = %d", $id );
    1082 
    1083         if ( !empty( $user_id ) )
    1084             $where_args[] = $wpdb->prepare( "user_id = %d", $user_id );
    1085 
    1086         if ( !empty( $action ) )
    1087             $where_args[] = $wpdb->prepare( "action = %s", $action );
    1088 
    1089         if ( !empty( $content ) )
    1090             $where_args[] = $wpdb->prepare( "content = %s", $content );
    1091 
    1092         if ( !empty( $component ) )
    1093             $where_args[] = $wpdb->prepare( "component = %s", $component );
    1094 
    1095         if ( !empty( $type ) )
    1096             $where_args[] = $wpdb->prepare( "type = %s", $type );
    1097 
    1098         if ( !empty( $primary_link ) )
    1099             $where_args[] = $wpdb->prepare( "primary_link = %s", $primary_link );
    1100 
    1101         if ( !empty( $item_id ) )
    1102             $where_args[] = $wpdb->prepare( "item_id = %d", $item_id );
    1103 
    1104         if ( !empty( $secondary_item_id ) )
    1105             $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id );
    1106 
    1107         if ( !empty( $date_recorded ) )
    1108             $where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded );
    1109 
    1110         if ( !empty( $hide_sitewide ) )
    1111             $where_args[] = $wpdb->prepare( "hide_sitewide = %d", $hide_sitewide );
    1112 
    1113         if ( !empty( $where_args ) )
    1114             $where_sql = 'WHERE ' . join( ' AND ', $where_args );
    1115         else
    1116             return false;
    1117 
    1118         // Fetch the activity IDs so we can delete any comments for this activity item
    1119         $activity_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" );
    1120 
    1121         if ( ! $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$where_sql}" ) ) {
    1122             return false;
    1123         }
    1124 
    1125         // Handle accompanying activity comments and meta deletion
    1126         if ( $activity_ids ) {
    1127             $activity_ids_comma          = implode( ',', wp_parse_id_list( $activity_ids ) );
    1128             $activity_comments_where_sql = "WHERE type = 'activity_comment' AND item_id IN ({$activity_ids_comma})";
    1129 
    1130             // Fetch the activity comment IDs for our deleted activity items
    1131             $activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} {$activity_comments_where_sql}" );
    1132 
    1133             // We have activity comments!
    1134             if ( ! empty( $activity_comment_ids ) ) {
    1135                 // Delete activity comments
    1136                 $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$activity_comments_where_sql}" );
    1137 
    1138                 // Merge activity IDs with activity comment IDs
    1139                 $activity_ids = array_merge( $activity_ids, $activity_comment_ids );
    1140             }
    1141 
    1142             // Delete all activity meta entries for activity items and activity comments
    1143             BP_Activity_Activity::delete_activity_meta_entries( $activity_ids );
    1144         }
    1145 
    1146         return $activity_ids;
    1147     }
    1148 
    1149     /**
    1150      * Delete the comments associated with a set of activity items.
    1151      *
    1152      * @since BuddyPress (1.2)
    1153      *
    1154      * @todo Mark as deprecated?  Method is no longer used internally.
    1155      *
    1156      * @param array $activity_ids Activity IDs whose comments should be deleted.
    1157      * @param bool $delete_meta Should we delete the activity meta items for these comments?
    1158      * @return bool True on success.
    1159      */
    1160     public static function delete_activity_item_comments( $activity_ids = array(), $delete_meta = true ) {
    1161         global $wpdb;
    1162 
    1163         $bp = buddypress();
    1164 
    1165         $delete_meta  = (bool) $delete_meta;
    1166         $activity_ids = implode( ',', wp_parse_id_list( $activity_ids ) );
    1167 
    1168         if ( $delete_meta ) {
    1169             // Fetch the activity comment IDs for our deleted activity items
    1170             $activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" );
    1171 
    1172             if ( ! empty( $activity_comment_ids ) ) {
    1173                 self::delete_activity_meta_entries( $activity_comment_ids );
    1174             }
    1175         }
    1176 
    1177         return $wpdb->query( "DELETE FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" );
    1178     }
    1179 
    1180     /**
    1181      * Delete the meta entries associated with a set of activity items.
    1182      *
    1183      * @since BuddyPress (1.2)
    1184      *
    1185      * @param array $activity_ids Activity IDs whose meta should be deleted.
    1186      * @return bool True on success.
    1187      */
    1188     public static function delete_activity_meta_entries( $activity_ids = array() ) {
    1189         $activity_ids = wp_parse_id_list( $activity_ids );
    1190 
    1191         foreach ( $activity_ids as $activity_id ) {
    1192             bp_activity_delete_meta( $activity_id );
    1193         }
    1194 
    1195         return true;
    1196     }
    1197 
    1198     /**
    1199      * Append activity comments to their associated activity items.
    1200      *
    1201      * @since BuddyPress (1.2)
    1202      *
    1203      * @global wpdb $wpdb WordPress database object
    1204      *
    1205      * @param array $activities Activities to fetch comments for.
    1206      * @param bool $spam Optional. 'ham_only' (default), 'spam_only' or 'all'.
    1207      * @return array The updated activities with nested comments.
    1208      */
    1209     public static function append_comments( $activities, $spam = 'ham_only' ) {
    1210         $activity_comments = array();
    1211 
    1212         // Now fetch the activity comments and parse them into the correct position in the activities array.
    1213         foreach ( (array) $activities as $activity ) {
    1214             $top_level_parent_id = 'activity_comment' == $activity->type ? $activity->item_id : 0;
    1215             $activity_comments[$activity->id] = BP_Activity_Activity::get_activity_comments( $activity->id, $activity->mptt_left, $activity->mptt_right, $spam, $top_level_parent_id );
    1216         }
    1217 
    1218         // Merge the comments with the activity items
    1219         foreach ( (array) $activities as $key => $activity ) {
    1220             if ( isset( $activity_comments[$activity->id] ) ) {
    1221                 $activities[$key]->children = $activity_comments[$activity->id];
    1222             }
    1223         }
    1224 
    1225         return $activities;
    1226     }
    1227 
    1228     /**
    1229      * Get activity comments that are associated with a specific activity ID.
    1230      *
    1231      * @since BuddyPress (1.2)
    1232      *
    1233      * @global wpdb $wpdb WordPress database object.
    1234      *
    1235      * @param int $activity_id Activity ID to fetch comments for.
    1236      * @param int $left Left-most node boundary.
    1237      * @param into $right Right-most node boundary.
    1238      * @param bool $spam Optional. 'ham_only' (default), 'spam_only' or 'all'.
    1239      * @param int $top_level_parent_id Optional. The id of the root-level parent activity item.
    1240      * @return array The updated activities with nested comments.
    1241      */
    1242     public static function get_activity_comments( $activity_id, $left, $right, $spam = 'ham_only', $top_level_parent_id = 0 ) {
    1243         global $wpdb;
    1244 
    1245         if ( empty( $top_level_parent_id ) ) {
    1246             $top_level_parent_id = $activity_id;
    1247         }
    1248 
    1249         $comments = wp_cache_get( $activity_id, 'bp_activity_comments' );
    1250 
    1251         // We store the string 'none' to cache the fact that the
    1252         // activity item has no comments
    1253         if ( 'none' === $comments ) {
    1254             $comments = false;
    1255 
    1256         // A true cache miss
    1257         } elseif ( empty( $comments ) ) {
    1258 
    1259             $bp = buddypress();
    1260 
    1261             // Select the user's fullname with the query
    1262             if ( bp_is_active( 'xprofile' ) ) {
    1263                 $fullname_select = ", pd.value as user_fullname";
    1264                 $fullname_from = ", {$bp->profile->table_name_data} pd ";
    1265                 $fullname_where = "AND pd.user_id = a.user_id AND pd.field_id = 1";
    1266 
    1267             // Prevent debug errors
    1268             } else {
    1269                 $fullname_select = $fullname_from = $fullname_where = '';
    1270             }
    1271 
    1272             // Don't retrieve activity comments marked as spam
    1273             if ( 'ham_only' == $spam ) {
    1274                 $spam_sql = 'AND a.is_spam = 0';
    1275             } elseif ( 'spam_only' == $spam ) {
    1276                 $spam_sql = 'AND a.is_spam = 1';
    1277             } else {
    1278                 $spam_sql = '';
    1279             }
    1280 
    1281             // Legacy query - not recommended
    1282             $func_args = func_get_args();
    1283 
    1284             /**
    1285              * Filters if BuddyPress should use the legacy activity query.
    1286              *
    1287              * @since BuddyPress (2.0.0)
    1288              *
    1289              * @param bool                 Whether or not to use the legacy query.
    1290              * @param BP_Activity_Activity Magic method referring to currently called method.
    1291              * @param array $func_args     Array of the method's argument list.
    1292              */
    1293             if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $func_args ) ) {
    1294 
    1295                 /**
    1296                  * Filters the MySQL prepared statement for the legacy activity query.
    1297                  *
    1298                  * @since BuddyPress (1.5.0)
    1299                  *
    1300                  * @param string Prepared statement for the activity query.
    1301                  * @param int    $activity_id Activity ID to fetch comments for.
    1302                  * @param int    $left Left-most node boundary.
    1303                  * @param int    $right Right-most node boundary.
    1304                  * @param string $spam_sql SQL Statement portion to differentiate between ham or spam.
    1305                  */
    1306                 $sql = apply_filters( 'bp_activity_comments_user_join_filter', $wpdb->prepare( "SELECT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name{$fullname_select} FROM {$bp->activity->table_name} a, {$wpdb->users} u{$fullname_from} WHERE u.ID = a.user_id {$fullname_where} AND a.type = 'activity_comment' {$spam_sql} AND a.item_id = %d AND a.mptt_left > %d AND a.mptt_left < %d ORDER BY a.date_recorded ASC", $top_level_parent_id, $left, $right ), $activity_id, $left, $right, $spam_sql );
    1307 
    1308                 $descendants = $wpdb->get_results( $sql );
    1309 
    1310             // We use the mptt BETWEEN clause to limit returned
    1311             // descendants to the correct part of the tree.
    1312             } else {
    1313                 $sql = $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} a WHERE a.type = 'activity_comment' {$spam_sql} AND a.item_id = %d and a.mptt_left > %d AND a.mptt_left < %d ORDER BY a.date_recorded ASC", $top_level_parent_id, $left, $right );
    1314 
    1315                 $descendant_ids = $wpdb->get_col( $sql );
    1316                 $descendants    = self::get_activity_data( $descendant_ids );
    1317                 $descendants    = self::append_user_fullnames( $descendants );
    1318             }
    1319 
    1320             $ref = array();
    1321 
    1322             // Loop descendants and build an assoc array
    1323             foreach ( (array) $descendants as $d ) {
    1324                 $d->children = array();
    1325 
    1326                 // If we have a reference on the parent
    1327                 if ( isset( $ref[ $d->secondary_item_id ] ) ) {
    1328                     $ref[ $d->secondary_item_id ]->children[ $d->id ] = $d;
    1329                     $ref[ $d->id ] =& $ref[ $d->secondary_item_id ]->children[ $d->id ];
    1330 
    1331                 // If we don't have a reference on the parent, put in the root level
    1332                 } else {
    1333                     $comments[ $d->id ] = $d;
    1334                     $ref[ $d->id ] =& $comments[ $d->id ];
    1335                 }
    1336             }
    1337 
    1338             // Calculate depth for each item
    1339             foreach ( $ref as &$r ) {
    1340                 $depth = 1;
    1341                 $parent_id = $r->secondary_item_id;
    1342                 while ( $parent_id !== $r->item_id ) {
    1343                     $depth++;
    1344 
    1345                     // When display_comments=stream, the
    1346                     // parent comment may not be part of
    1347                     // the returned results, so we manually
    1348                     // fetch it
    1349                     if ( empty( $ref[ $parent_id ] ) ) {
    1350                         $direct_parent = new BP_Activity_Activity( $parent_id );
    1351                         if ( isset( $direct_parent->secondary_item_id ) ) {
    1352                             $parent_id = $direct_parent->secondary_item_id;
    1353                         } else {
    1354                             // Something went wrong
    1355                             // Short-circuit the
    1356                             // depth calculation
    1357                             $parent_id = $r->item_id;
    1358                         }
    1359                     } else {
    1360                         $parent_id = $ref[ $parent_id ]->secondary_item_id;
    1361                     }
    1362                 }
    1363                 $r->depth = $depth;
    1364             }
    1365 
    1366             // If we cache a value of false, it'll count as a cache
    1367             // miss the next time the activity comments are fetched.
    1368             // Storing the string 'none' is a hack workaround to
    1369             // avoid unnecessary queries.
    1370             if ( false === $comments ) {
    1371                 $cache_value = 'none';
    1372             } else {
    1373                 $cache_value = $comments;
    1374             }
    1375 
    1376             wp_cache_set( $activity_id, $cache_value, 'bp_activity_comments' );
    1377         }
    1378 
    1379         return $comments;
    1380     }
    1381 
    1382     /**
    1383      * Rebuild nested comment tree under an activity or activity comment.
    1384      *
    1385      * @since BuddyPress (1.2)
    1386      *
    1387      * @global wpdb $wpdb WordPress database object
    1388      *
    1389      * @param  int $parent_id ID of an activity or activity comment
    1390      * @param  int $left      Node boundary start for activity or activity comment
    1391      * @return int Right      Node boundary of activity or activity comment
    1392      */
    1393     public static function rebuild_activity_comment_tree( $parent_id, $left = 1 ) {
    1394         global $wpdb;
    1395 
    1396         $bp = buddypress();
    1397 
    1398         // The right value of this node is the left value + 1
    1399         $right = intval( $left + 1 );
    1400 
    1401         // Get all descendants of this node
    1402         $comments    = BP_Activity_Activity::get_child_comments( $parent_id );
    1403         $descendants = wp_list_pluck( $comments, 'id' );
    1404 
    1405         // Loop the descendants and recalculate the left and right values
    1406         foreach ( (array) $descendants as $descendant_id ) {
    1407             $right = BP_Activity_Activity::rebuild_activity_comment_tree( $descendant_id, $right );
    1408         }
    1409 
    1410         // We've got the left value, and now that we've processed the children
    1411         // of this node we also know the right value
    1412         if ( 1 === $left ) {
    1413             $wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE id = %d", $left, $right, $parent_id ) );
    1414         } else {
    1415             $wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE type = 'activity_comment' AND id = %d", $left, $right, $parent_id ) );
    1416         }
    1417 
    1418         // Return the right value of this node + 1
    1419         return intval( $right + 1 );
    1420     }
    1421 
    1422     /**
    1423      * Get child comments of an activity or activity comment.
    1424      *
    1425      * @since BuddyPress (1.2)
    1426      *
    1427      * @global wpdb $wpdb WordPress database object.
    1428      *
    1429      * @param int $parent_id ID of an activity or activity comment.
    1430      * @return object Numerically indexed array of child comments.
    1431      */
    1432     public static function get_child_comments( $parent_id ) {
    1433         global $wpdb;
    1434 
    1435         $bp = buddypress();
    1436 
    1437         return $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND secondary_item_id = %d", $parent_id ) );
    1438     }
    1439 
    1440     /**
    1441      * Get a list of components that have recorded activity associated with them.
    1442      *
    1443      * @param bool $skip_last_activity If true, components will not be
    1444      *        included if the only activity type associated with them is
    1445      *        'last_activity'. (Since 2.0.0, 'last_activity' is stored in
    1446      *        the activity table, but these items are not full-fledged
    1447      *        activity items.) Default: true.
    1448      * @return array List of component names.
    1449      */
    1450     public static function get_recorded_components( $skip_last_activity = true ) {
    1451         global $wpdb;
    1452 
    1453         $bp = buddypress();
    1454 
    1455         if ( true === $skip_last_activity ) {
    1456             $components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} WHERE action != '' AND action != 'last_activity' ORDER BY component ASC" );
    1457         } else {
    1458             $components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} ORDER BY component ASC" );
    1459         }
    1460 
    1461         return $components;
    1462     }
    1463 
    1464     /**
    1465      * Get sitewide activity items for use in an RSS feed.
    1466      *
    1467      * @param int $limit Optional. Number of items to fetch. Default: 35.
    1468      * @return array $activity_feed List of activity items, with RSS data added.
    1469      */
    1470     public static function get_sitewide_items_for_feed( $limit = 35 ) {
    1471         $activities    = bp_activity_get_sitewide( array( 'max' => $limit ) );
    1472         $activity_feed = array();
    1473 
    1474         for ( $i = 0, $count = count( $activities ); $i < $count; ++$i ) {
    1475             $title                            = explode( '<span', $activities[$i]['content'] );
    1476             $activity_feed[$i]['title']       = trim( strip_tags( $title[0] ) );
    1477             $activity_feed[$i]['link']        = $activities[$i]['primary_link'];
    1478             $activity_feed[$i]['description'] = @sprintf( $activities[$i]['content'], '' );
    1479             $activity_feed[$i]['pubdate']     = $activities[$i]['date_recorded'];
    1480         }
    1481 
    1482         return $activity_feed;
    1483     }
    1484 
    1485     /**
    1486      * Create SQL IN clause for filter queries.
    1487      *
    1488      * @since BuddyPress (1.5)
    1489      *
    1490      * @see BP_Activity_Activity::get_filter_sql()
    1491      *
    1492      * @param string $field The database field.
    1493      * @param array|bool $items The values for the IN clause, or false when none are found.
    1494      */
    1495     public static function get_in_operator_sql( $field, $items ) {
    1496         global $wpdb;
    1497 
    1498         // split items at the comma
    1499         if ( ! is_array( $items ) ) {
    1500             $items = explode( ',', $items );
    1501         }
    1502 
    1503         // array of prepared integers or quoted strings
    1504         $items_prepared = array();
    1505 
    1506         // clean up and format each item
    1507         foreach ( $items as $item ) {
    1508             // clean up the string
    1509             $item = trim( $item );
    1510             // pass everything through prepare for security and to safely quote strings
    1511             $items_prepared[] = ( is_numeric( $item ) ) ? $wpdb->prepare( '%d', $item ) : $wpdb->prepare( '%s', $item );
    1512         }
    1513 
    1514         // build IN operator sql syntax
    1515         if ( count( $items_prepared ) )
    1516             return sprintf( '%s IN ( %s )', trim( $field ), implode( ',', $items_prepared ) );
    1517         else
    1518             return false;
    1519     }
    1520 
    1521     /**
    1522      * Create filter SQL clauses.
    1523      *
    1524      * @since BuddyPress (1.5.0)
    1525      *
    1526      * @param array $filter_array {
    1527      *     Fields and values to filter by.
    1528      *     @type array|string|id $user_id User ID(s).
    1529      *     @type array|string $object Corresponds to the 'component'
    1530      *           column in the database.
    1531      *     @type array|string $action Corresponds to the 'type' column
    1532      *           in the database.
    1533      *     @type array|string|int $primary_id Corresponds to the 'item_id'
    1534      *           column in the database.
    1535      *     @type array|string|int $secondary_id Corresponds to the
    1536      *           'secondary_item_id' column in the database.
    1537      *     @type int $offset Return only those items with an ID greater
    1538      *           than the offset value.
    1539      *     @type string $since Return only those items that have a
    1540      *           date_recorded value greater than a given MySQL-formatted
    1541      *           date.
    1542      * }
    1543      * @return string The filter clause, for use in a SQL query.
    1544      */
    1545     public static function get_filter_sql( $filter_array ) {
    1546 
    1547         $filter_sql = array();
    1548 
    1549         if ( !empty( $filter_array['user_id'] ) ) {
    1550             $user_sql = BP_Activity_Activity::get_in_operator_sql( 'a.user_id', $filter_array['user_id'] );
    1551             if ( !empty( $user_sql ) )
    1552                 $filter_sql[] = $user_sql;
    1553         }
    1554 
    1555         if ( !empty( $filter_array['object'] ) ) {
    1556             $object_sql = BP_Activity_Activity::get_in_operator_sql( 'a.component', $filter_array['object'] );
    1557             if ( !empty( $object_sql ) )
    1558                 $filter_sql[] = $object_sql;
    1559         }
    1560 
    1561         if ( !empty( $filter_array['action'] ) ) {
    1562             $action_sql = BP_Activity_Activity::get_in_operator_sql( 'a.type', $filter_array['action'] );
    1563             if ( ! empty( $action_sql ) )
    1564                 $filter_sql[] = $action_sql;
    1565         }
    1566 
    1567         if ( !empty( $filter_array['primary_id'] ) ) {
    1568             $pid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.item_id', $filter_array['primary_id'] );
    1569             if ( !empty( $pid_sql ) )
    1570                 $filter_sql[] = $pid_sql;
    1571         }
    1572 
    1573         if ( !empty( $filter_array['secondary_id'] ) ) {
    1574             $sid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.secondary_item_id', $filter_array['secondary_id'] );
    1575             if ( !empty( $sid_sql ) )
    1576                 $filter_sql[] = $sid_sql;
    1577         }
    1578 
    1579         if ( ! empty( $filter_array['offset'] ) ) {
    1580             $sid_sql = absint( $filter_array['offset'] );
    1581             $filter_sql[] = "a.id >= {$sid_sql}";
    1582         }
    1583 
    1584         if ( ! empty( $filter_array['since'] ) ) {
    1585             // Validate that this is a proper Y-m-d H:i:s date
    1586             // Trick: parse to UNIX date then translate back
    1587             $translated_date = date( 'Y-m-d H:i:s', strtotime( $filter_array['since'] ) );
    1588             if ( $translated_date === $filter_array['since'] ) {
    1589                 $filter_sql[] = "a.date_recorded > '{$translated_date}'";
    1590             }
    1591         }
    1592 
    1593         if ( empty( $filter_sql ) )
    1594             return false;
    1595 
    1596         return join( ' AND ', $filter_sql );
    1597     }
    1598 
    1599     /**
    1600      * Get the date/time of last recorded activity.
    1601      *
    1602      * @since BuddyPress (1.2)
    1603      *
    1604      * @return string ISO timestamp.
    1605      */
    1606     public static function get_last_updated() {
    1607         global $wpdb;
    1608 
    1609         $bp = buddypress();
    1610 
    1611         return $wpdb->get_var( "SELECT date_recorded FROM {$bp->activity->table_name} ORDER BY date_recorded DESC LIMIT 1" );
    1612     }
    1613 
    1614     /**
    1615      * Get favorite count for a given user.
    1616      *
    1617      * @since BuddyPress (1.2)
    1618      *
    1619      * @param int The ID of the user whose favorites you're counting.
    1620      * @return int A count of the user's favorites.
    1621      */
    1622     public static function total_favorite_count( $user_id ) {
    1623 
    1624         // Get activities from user meta
    1625         $favorite_activity_entries = bp_get_user_meta( $user_id, 'bp_favorite_activities', true );
    1626         if ( ! empty( $favorite_activity_entries ) ) {
    1627             return count( maybe_unserialize( $favorite_activity_entries ) );
    1628         }
    1629 
    1630         // No favorites
    1631         return 0;
    1632     }
    1633 
    1634     /**
    1635      * Check whether an activity item exists with a given string content.
    1636      *
    1637      * @param string $content The content to filter by.
    1638      * @return int|bool The ID of the first matching item if found, otherwise false.
    1639      */
    1640     public static function check_exists_by_content( $content ) {
    1641         global $wpdb;
    1642 
    1643         $bp = buddypress();
    1644 
    1645         return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE content = %s", $content ) );
    1646     }
    1647 
    1648     /**
    1649      * Hide all activity for a given user.
    1650      *
    1651      * @param int $user_id The ID of the user whose activity you want to mark hidden.
    1652      * @param int
    1653      */
    1654     public static function hide_all_for_user( $user_id ) {
    1655         global $wpdb;
    1656 
    1657         $bp = buddypress();
    1658 
    1659         return $wpdb->get_var( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET hide_sitewide = 1 WHERE user_id = %d", $user_id ) );
    1660     }
    1661 
    1662     /**
    1663      * PHP-agnostic version of {@link array_replace_recursive()}.
    1664      *
    1665      * array_replace_recursive() is a PHP 5.3 function.  BuddyPress (and
    1666      * WordPress) currently supports down to PHP 5.2, so this method is a workaround
    1667      * for PHP 5.2.
    1668      *
    1669      * Note: array_replace_recursive() supports infinite arguments, but for our use-
    1670      * case, we only need to support two arguments.
    1671      *
    1672      * Subject to removal once WordPress makes PHP 5.3.0 the minimum requirement.
    1673      *
    1674      * @since BuddyPress (2.2.0)
    1675      *
    1676      * @see http://php.net/manual/en/function.array-replace-recursive.php#109390
    1677      *
    1678      * @param  array $base         Array with keys needing to be replaced
    1679      * @param  array $replacements Array with the replaced keys
    1680      * @return array
    1681      */
    1682     protected static function array_replace_recursive( $base = array(), $replacements = array() ) {
    1683         if ( function_exists( 'array_replace_recursive' ) ) {
    1684             return array_replace_recursive( $base, $replacements );
    1685         }
    1686 
    1687         // PHP 5.2-compatible version
    1688         // http://php.net/manual/en/function.array-replace-recursive.php#109390
    1689         foreach ( array_slice( func_get_args(), 1 ) as $replacements ) {
    1690             $bref_stack = array( &$base );
    1691             $head_stack = array( $replacements );
    1692 
    1693             do {
    1694                 end( $bref_stack );
    1695 
    1696                 $bref = &$bref_stack[ key( $bref_stack ) ];
    1697                 $head = array_pop( $head_stack );
    1698 
    1699                 unset( $bref_stack[ key($bref_stack) ] );
    1700 
    1701                 foreach ( array_keys( $head ) as $key ) {
    1702                     if ( isset( $key, $bref ) && is_array( $bref[$key] ) && is_array( $head[$key] ) ) {
    1703                         $bref_stack[] = &$bref[ $key ];
    1704                         $head_stack[] = $head[ $key ];
    1705                     } else {
    1706                         $bref[ $key ] = $head[ $key ];
    1707                     }
    1708                 }
    1709             } while( count( $head_stack ) );
    1710         }
    1711 
    1712         return $base;
    1713     }
    1714 }
    1715 
    1716 /**
    1717  * Class for generating the WHERE SQL clause for advanced activity fetching.
    1718  *
    1719  * This is notably used in {@link BP_Activity_Activity::get()} with the
    1720  * 'filter_query' parameter.
    1721  *
    1722  * @since BuddyPress (2.2.0)
    1723  */
    1724 class BP_Activity_Query extends BP_Recursive_Query {
    1725     /**
    1726      * Array of activity queries.
    1727      *
    1728      * See {@see BP_Activity_Query::__construct()} for information on query arguments.
    1729      *
    1730      * @since BuddyPress (2.2.0)
    1731      * @access public
    1732      * @var array
    1733      */
    1734     public $queries = array();
    1735 
    1736     /**
    1737      * Table alias.
    1738      *
    1739      * @since BuddyPress (2.2.0)
    1740      * @access public
    1741      * @var string
    1742      */
    1743     public $table_alias = '';
    1744 
    1745     /**
    1746      * Supported DB columns.
    1747      *
    1748      * See the 'wp_bp_activity' DB table schema.
    1749      *
    1750      * @since BuddyPress (2.2.0)
    1751      * @access public
    1752      * @var array
    1753      */
    1754     public $db_columns = array(
    1755         'id', 'user_id', 'component', 'type', 'action', 'content',
    1756         'item_id', 'secondary_item_id', 'hide_sitewide', 'is_spam',
    1757     );
    1758 
    1759     /**
    1760      * Constructor.
    1761      *
    1762      * @since BuddyPress (2.2.0)
    1763      *
    1764      * @param array $query {
    1765      *     Array of query clauses.
    1766      *
    1767      *     @type array {
    1768      *         @type string $column   Required. The column to query against. Basically, any DB column in the main
    1769      *                                'wp_bp_activity' table.
    1770      *         @type string $value    Required. Value to filter by.
    1771      *         @type string $compare  Optional. The comparison operator. Default '='.
    1772      *                                Accepts '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'LIKE',
    1773      *                                'NOT LIKE', BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', 'RLIKE'
    1774      *         @type string $relation Optional. The boolean relationship between the activity queries.
    1775      *                                Accepts 'OR', 'AND'. Default 'AND'.
    1776      *         @type array {
    1777      *             Optional. Another fully-formed activity query. See parameters above.
    1778      *         }
    1779      *     }
    1780      * }
    1781      */
    1782     public function __construct( $query = array() ) {
    1783         if ( ! is_array( $query ) ) {
    1784             return;
    1785         }
    1786 
    1787         $this->queries = $this->sanitize_query( $query );
    1788     }
    1789 
    1790     /**
    1791      * Generates WHERE SQL clause to be appended to a main query.
    1792      *
    1793      * @since BuddyPress (2.2.0)
    1794      * @access public
    1795      *
    1796      * @param string $alias An existing table alias that is compatible with the current query clause.
    1797      *               Default: 'a'. BP_Activity_Activity::get() uses 'a', so we default to that.
    1798      * @return string SQL fragment to append to the main WHERE clause.
    1799      */
    1800     public function get_sql( $alias = 'a' ) {
    1801         if ( ! empty( $alias ) ) {
    1802             $this->table_alias = sanitize_title( $alias );
    1803         }
    1804 
    1805         $sql = $this->get_sql_clauses();
    1806 
    1807         // we only need the 'where' clause
    1808         //
    1809         // also trim trailing "AND" clause from parent BP_Recursive_Query class
    1810         // since it's not necessary for our needs
    1811         return preg_replace( '/^\sAND/', '', $sql['where'] );
    1812     }
    1813 
    1814     /**
    1815      * Generate WHERE clauses for a first-order clause.
    1816      *
    1817      * @since BuddyPress (2.2.0)
    1818      * @access protected
    1819      *
    1820      * @param  array $clause       Array of arguments belonging to the clause.
    1821      * @param  array $parent_query Parent query to which the clause belongs.
    1822      * @return array {
    1823      *     @type array $where Array of subclauses for the WHERE statement.
    1824      *     @type array $join  Empty array. Not used.
    1825      * }
    1826      */
    1827     protected function get_sql_for_clause( $clause, $parent_query ) {
    1828         global $wpdb;
    1829 
    1830         $sql_chunks = array(
    1831             'where' => array(),
    1832             'join' => array(),
    1833         );
    1834 
    1835         $column = isset( $clause['column'] ) ? $this->validate_column( $clause['column'] ) : '';
    1836         $value  = isset( $clause['value'] )  ? $clause['value'] : '';
    1837         if ( empty( $column ) || ! isset( $clause['value'] ) ) {
    1838             return $sql_chunks;
    1839         }
    1840 
    1841         if ( isset( $clause['compare'] ) ) {
    1842             $clause['compare'] = strtoupper( $clause['compare'] );
    1843         } else {
    1844             $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
    1845         }
    1846 
    1847         // default 'compare' to '=' if no valid operator is found
    1848         if ( ! in_array( $clause['compare'], array(
    1849             '=', '!=', '>', '>=', '<', '<=',
    1850             'LIKE', 'NOT LIKE',
    1851             'IN', 'NOT IN',
    1852             'BETWEEN', 'NOT BETWEEN',
    1853             'REGEXP', 'NOT REGEXP', 'RLIKE'
    1854         ) ) ) {
    1855             $clause['compare'] = '=';
    1856         }
    1857 
    1858         $compare = $clause['compare'];
    1859 
    1860         $alias = ! empty( $this->table_alias ) ? "{$this->table_alias}." : '';
    1861 
    1862         // Next, Build the WHERE clause.
    1863         $where = '';
    1864 
    1865         // value.
    1866         if ( isset( $clause['value'] ) ) {
    1867             if ( in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
    1868                 if ( ! is_array( $value ) ) {
    1869                     $value = preg_split( '/[,\s]+/', $value );
    1870                 }
    1871             }
    1872 
    1873             // tinyint
    1874             if ( ! empty( $column ) && true === in_array( $column, array( 'hide_sitewide', 'is_spam' ) ) ) {
    1875                 $sql_chunks['where'][] = $wpdb->prepare( "{$alias}{$column} = %d", $value );
    1876 
    1877             } else {
    1878                 switch ( $compare ) {
    1879                     // IN uses different syntax
    1880                     case 'IN' :
    1881                     case 'NOT IN' :
    1882                         $in_sql = BP_Activity_Activity::get_in_operator_sql( "{$alias}{$column}", $value );
    1883 
    1884                         // 'NOT IN' operator is as easy as a string replace!
    1885                         if ( 'NOT IN' === $compare ) {
    1886                             $in_sql = str_replace( 'IN', 'NOT IN', $in_sql );
    1887                         }
    1888 
    1889                         $sql_chunks['where'][] = $in_sql;
    1890                         break;
    1891 
    1892                     case 'BETWEEN' :
    1893                     case 'NOT BETWEEN' :
    1894                         $value = array_slice( $value, 0, 2 );
    1895                         $where = $wpdb->prepare( '%s AND %s', $value );
    1896                         break;
    1897 
    1898                     case 'LIKE' :
    1899                     case 'NOT LIKE' :
    1900                         $value = '%' . bp_esc_like( $value ) . '%';
    1901                         $where = $wpdb->prepare( '%s', $value );
    1902                         break;
    1903 
    1904                     default :
    1905                         $where = $wpdb->prepare( '%s', $value );
    1906                         break;
    1907 
    1908                 }
    1909             }
    1910 
    1911             if ( $where ) {
    1912                 $sql_chunks['where'][] = "{$alias}{$column} {$compare} {$where}";
    1913             }
    1914         }
    1915 
    1916         /*
    1917          * Multiple WHERE clauses should be joined in parentheses.
    1918          */
    1919         if ( 1 < count( $sql_chunks['where'] ) ) {
    1920             $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
    1921         }
    1922 
    1923         return $sql_chunks;
    1924     }
    1925 
    1926     /**
    1927      * Determine whether a clause is first-order.
    1928      *
    1929      * @since BuddyPress (2.2.0)
    1930      * @access protected
    1931      *
    1932      * @param  array $query Clause to check.
    1933      * @return bool
    1934      */
    1935     protected function is_first_order_clause( $query ) {
    1936         return isset( $query['column'] ) || isset( $query['value'] );
    1937     }
    1938 
    1939     /**
    1940      * Validates a column name parameter.
    1941      *
    1942      * Column names are checked against a whitelist of known tables.
    1943      * See {@link BP_Activity_Query::db_tables}.
    1944      *
    1945      * @since BuddyPress (2.2.0)
    1946      * @access public
    1947      *
    1948      * @param string $column The user-supplied column name.
    1949      * @return string A validated column name value.
    1950      */
    1951     public function validate_column( $column = '' ) {
    1952         if ( in_array( $column, $this->db_columns ) ) {
    1953             return $column;
    1954         } else {
    1955             return '';
    1956         }
    1957     }
    1958 }
    1959 
    1960 /**
    1961  * Create a RSS feed using the activity component.
    1962  *
    1963  * You should only construct a new feed when you've validated that you're on
    1964  * the appropriate screen.
    1965  *
    1966  * See {@link bp_activity_action_sitewide_feed()} as an example.
    1967  *
    1968  * Accepted parameters:
    1969  *   id               - internal id for the feed; should be alphanumeric only
    1970  *                      (required)
    1971  *   title            - RSS feed title
    1972  *   link             - Relevant link for the RSS feed
    1973  *   description      - RSS feed description
    1974  *   ttl              - Time-to-live (see inline doc in constructor)
    1975  *   update_period    - Part of the syndication module (see inline doc in
    1976  *                      constructor for more info)
    1977  *   update_frequency - Part of the syndication module (see inline doc in
    1978  *                      constructor for more info)
    1979  *   max              - Number of feed items to display
    1980  *   activity_args    - Arguments passed to {@link bp_has_activities()}
    1981  *
    1982  * @since BuddyPress (1.8)
    1983  */
    1984 class BP_Activity_Feed {
    1985     /**
    1986      * Holds our custom class properties.
    1987      *
    1988      * These variables are stored in a protected array that is magically
    1989      * updated using PHP 5.2+ methods.
    1990      *
    1991      * @see BP_Feed::__construct() This is where $data is added
    1992      * @var array
    1993      */
    1994     protected $data;
    1995 
    1996     /**
    1997      * Magic method for checking the existence of a certain data variable.
    1998      *
    1999      * @param string $key
    2000      */
    2001     public function __isset( $key ) { return isset( $this->data[$key] ); }
    2002 
    2003     /**
    2004      * Magic method for getting a certain data variable.
    2005      *
    2006      * @param string $key
    2007      */
    2008     public function __get( $key ) { return isset( $this->data[$key] ) ? $this->data[$key] : null; }
    2009 
    2010     /**
    2011      * Constructor.
    2012      *
    2013      * @param array $args Optional
    2014      */
    2015     public function __construct( $args = array() ) {
    2016 
    2017         /**
    2018          * Filters if BuddyPress should consider feeds enabled. If disabled, it will return early.
    2019          *
    2020          * @since BuddyPress (1.8.0)
    2021          *
    2022          * @param bool true Default true aka feeds are enabled.
    2023          */
    2024         if ( false === (bool) apply_filters( 'bp_activity_enable_feeds', true ) ) {
    2025             global $wp_query;
    2026 
    2027             // set feed flag to false
    2028             $wp_query->is_feed = false;
    2029 
    2030             return false;
    2031         }
    2032 
    2033         // Setup data
    2034         $this->data = wp_parse_args( $args, array(
    2035             // Internal identifier for the RSS feed - should be alphanumeric only
    2036             'id'               => '',
    2037 
    2038             // RSS title - should be plain-text
    2039             'title'            => '',
    2040 
    2041             // relevant link for the RSS feed
    2042             'link'             => '',
    2043 
    2044             // RSS description - should be plain-text
    2045             'description'      => '',
    2046 
    2047             // Time-to-live - number of minutes to cache the data before an aggregator
    2048             // requests it again.  This is only acknowledged if the RSS client supports it
    2049             //
    2050             // See: http://www.rssboard.org/rss-profile#element-channel-ttl
    2051             //      http://www.kbcafe.com/rss/rssfeedstate.html#ttl
    2052             'ttl'              => '30',
    2053 
    2054             // Syndication module - similar to ttl, but not really supported by RSS
    2055             // clients
    2056             //
    2057             // See: http://web.resource.org/rss/1.0/modules/syndication/#description
    2058             //      http://www.kbcafe.com/rss/rssfeedstate.html#syndicationmodule
    2059             'update_period'    => 'hourly',
    2060             'update_frequency' => 2,
    2061 
    2062             // Number of items to display
    2063             'max'              => 50,
    2064 
    2065             // Activity arguments passed to bp_has_activities()
    2066             'activity_args'    => array()
    2067         ) );
    2068 
    2069         /**
    2070          * Fires before the feed is setup so plugins can modify.
    2071          *
    2072          * @since BuddyPress (1.8.0)
    2073          *
    2074          * @param BP_Activity_Feed Reference to current instance of activity feed.
    2075          */
    2076         do_action_ref_array( 'bp_activity_feed_prefetch', array( &$this ) );
    2077 
    2078         // Setup class properties
    2079         $this->setup_properties();
    2080 
    2081         // Check if id is valid
    2082         if ( empty( $this->id ) ) {
    2083             _doing_it_wrong( 'BP_Activity_Feed', __( "RSS feed 'id' must be defined", 'buddypress' ), 'BP 1.8' );
    2084             return false;
    2085         }
    2086 
    2087         /**
    2088          * Fires after the feed is setup so plugins can modify.
    2089          *
    2090          * @since BuddyPress (1.8.0)
    2091          *
    2092          * @param BP_Activity_Feed Reference to current instance of activity feed.
    2093          */
    2094         do_action_ref_array( 'bp_activity_feed_postfetch', array( &$this ) );
    2095 
    2096         // Setup feed hooks
    2097         $this->setup_hooks();
    2098 
    2099         // Output the feed
    2100         $this->output();
    2101 
    2102         // Kill the rest of the output
    2103         die();
    2104     }
    2105 
    2106     /** SETUP ****************************************************************/
    2107 
    2108     /**
    2109      * Setup and validate the class properties.
    2110      *
    2111      * @access protected
    2112      */
    2113     protected function setup_properties() {
    2114         $this->id               = sanitize_title( $this->id );
    2115         $this->title            = strip_tags( $this->title );
    2116         $this->link             = esc_url_raw( $this->link );
    2117         $this->description      = strip_tags( $this->description );
    2118         $this->ttl              = (int) $this->ttl;
    2119         $this->update_period    = strip_tags( $this->update_period );
    2120         $this->update_frequency = (int) $this->update_frequency;
    2121 
    2122         $this->activity_args    = wp_parse_args( $this->activity_args, array(
    2123             'max'              => $this->max,
    2124             'per_page'         => $this->max,
    2125             'display_comments' => 'stream'
    2126         ) );
    2127 
    2128     }
    2129 
    2130     /**
    2131      * Setup some hooks that are used in the feed.
    2132      *
    2133      * Currently, these hooks are used to maintain backwards compatibility with
    2134      * the RSS feeds previous to BP 1.8.
    2135      *
    2136      * @access protected
    2137      */
    2138     protected function setup_hooks() {
    2139         add_action( 'bp_activity_feed_rss_attributes',   array( $this, 'backpat_rss_attributes' ) );
    2140         add_action( 'bp_activity_feed_channel_elements', array( $this, 'backpat_channel_elements' ) );
    2141         add_action( 'bp_activity_feed_item_elements',    array( $this, 'backpat_item_elements' ) );
    2142     }
    2143 
    2144     /** BACKPAT HOOKS ********************************************************/
    2145 
    2146     /**
    2147      * Fire a hook to ensure backward compatibility for RSS attributes.
    2148      */
    2149     public function backpat_rss_attributes() {
    2150 
    2151         /**
    2152          * Fires inside backpat_rss_attributes method for backwards compatibility related to RSS attributes.
    2153          *
    2154          * This hook was originally separated out for individual components but has since been abstracted into the BP_Activity_Feed class.
    2155          *
    2156          * @since BuddyPress (1.0.0)
    2157          */
    2158         do_action( 'bp_activity_' . $this->id . '_feed' );
    2159     }
    2160 
    2161     /**
    2162      * Fire a hook to ensure backward compatibility for channel elements.
    2163      */
    2164     public function backpat_channel_elements() {
    2165 
    2166         /**
    2167          * Fires inside backpat_channel_elements method for backwards compatibility related to RSS channel elements.
    2168          *
    2169          * This hook was originally separated out for individual components but has since been abstracted into the BP_Activity_Feed class.
    2170          *
    2171          * @since BuddyPress (1.0.0)
    2172          */
    2173         do_action( 'bp_activity_' . $this->id . '_feed_head' );
    2174     }
    2175 
    2176     /**
    2177      * Fire a hook to ensure backward compatibility for item elements.
    2178      */
    2179     public function backpat_item_elements() {
    2180         switch ( $this->id ) {
    2181 
    2182             // sitewide and friends feeds use the 'personal' hook
    2183             case 'sitewide' :
    2184             case 'friends' :
    2185                 $id = 'personal';
    2186 
    2187                 break;
    2188 
    2189             default :
    2190                 $id = $this->id;
    2191 
    2192                 break;
    2193         }
    2194 
    2195         /**
    2196          * Fires inside backpat_item_elements method for backwards compatibility related to RSS item elements.
    2197          *
    2198          * This hook was originally separated out for individual components but has since been abstracted into the BP_Activity_Feed class.
    2199          *
    2200          * @since BuddyPress (1.0.0)
    2201          */
    2202         do_action( 'bp_activity_' . $id . '_feed_item' );
    2203     }
    2204 
    2205     /** HELPERS **************************************************************/
    2206 
    2207     /**
    2208      * Output the feed's item content.
    2209      *
    2210      * @access protected
    2211      */
    2212     protected function feed_content() {
    2213         bp_activity_content_body();
    2214 
    2215         switch ( $this->id ) {
    2216 
    2217             // also output parent activity item if we're on a specific feed
    2218             case 'favorites' :
    2219             case 'friends' :
    2220             case 'mentions' :
    2221             case 'personal' :
    2222 
    2223                 if ( 'activity_comment' == bp_get_activity_action_name() ) :
    2224             ?>
    2225                 <strong><?php _e( 'In reply to', 'buddypress' ) ?></strong> -
    2226                 <?php bp_activity_parent_content() ?>
    2227             <?php
    2228                 endif;
    2229 
    2230                 break;
    2231         }
    2232     }
    2233 
    2234     /**
    2235      * Sets various HTTP headers related to Content-Type and browser caching.
    2236      *
    2237      * Most of this class method is derived from {@link WP::send_headers()}.
    2238      *
    2239      * @since BuddyPress (1.9.0)
    2240      *
    2241      * @access protected
    2242      */
    2243     protected function http_headers() {
    2244         // set up some additional headers if not on a directory page
    2245         // this is done b/c BP uses pseudo-pages
    2246         if ( ! bp_is_directory() ) {
    2247             global $wp_query;
    2248 
    2249             $wp_query->is_404 = false;
    2250             status_header( 200 );
    2251         }
    2252 
    2253         // Set content-type
    2254         @header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true );
    2255         send_nosniff_header();
    2256 
    2257         // Cache-related variables
    2258         $last_modified      = mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false );
    2259         $modified_timestamp = strtotime( $last_modified );
    2260         $etag               = md5( $last_modified );
    2261 
    2262         // Set cache-related headers
    2263         @header( 'Last-Modified: ' . $last_modified );
    2264         @header( 'Pragma: no-cache' );
    2265         @header( 'ETag: ' . '"' . $etag . '"' );
    2266 
    2267         // First commit of BuddyPress! (Easter egg)
    2268         @header( 'Expires: Tue, 25 Mar 2008 17:13:55 GMT');
    2269 
    2270         // Get ETag from supported user agents
    2271         if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) {
    2272             $client_etag = wp_unslash( $_SERVER['HTTP_IF_NONE_MATCH'] );
    2273 
    2274             // Remove quotes from ETag
    2275             $client_etag = trim( $client_etag, '"' );
    2276 
    2277             // Strip suffixes from ETag if they exist (eg. "-gzip")
    2278             $etag_suffix_pos = strpos( $client_etag, '-' );
    2279             if ( ! empty( $etag_suffix_pos ) ) {
    2280                 $client_etag = substr( $client_etag, 0, $etag_suffix_pos );
    2281             }
    2282 
    2283         // No ETag found
    2284         } else {
    2285             $client_etag = false;
    2286         }
    2287 
    2288         // Get client last modified timestamp from supported user agents
    2289         $client_last_modified      = empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ? '' : trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
    2290         $client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;
    2291 
    2292         // Set 304 status if feed hasn't been updated since last fetch
    2293         if ( ( $client_last_modified && $client_etag ) ?
    2294                  ( ( $client_modified_timestamp >= $modified_timestamp ) && ( $client_etag == $etag ) ) :
    2295                  ( ( $client_modified_timestamp >= $modified_timestamp ) || ( $client_etag == $etag ) ) ) {
    2296             $status = 304;
    2297         } else {
    2298             $status = false;
    2299         }
    2300 
    2301         // If feed hasn't changed as reported by the user agent, set 304 status header
    2302         if ( ! empty( $status ) ) {
    2303             status_header( $status );
    2304 
    2305             // cached response, so stop now!
    2306             if ( $status == 304 ) {
    2307                 exit();
    2308             }
    2309         }
    2310     }
    2311 
    2312     /** OUTPUT ***************************************************************/
    2313 
    2314     /**
    2315      * Output the RSS feed.
    2316      *
    2317      * @access protected
    2318      */
    2319     protected function output() {
    2320         $this->http_headers();
    2321         echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?'.'>';
    2322     ?>
    2323 
    2324 <rss version="2.0"
    2325     xmlns:content="http://purl.org/rss/1.0/modules/content/"
    2326     xmlns:atom="http://www.w3.org/2005/Atom"
    2327     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    2328     xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    2329     <?php
    2330 
    2331     /**
    2332      * Fires at the end of the opening RSS tag for feed output so plugins can add extra attributes.
    2333      *
    2334      * @since BuddyPress (1.8.0)
    2335      */
    2336     do_action( 'bp_activity_feed_rss_attributes' ); ?>
    2337 >
    2338 
    2339 <channel>
    2340     <title><?php echo $this->title; ?></title>
    2341     <link><?php echo $this->link; ?></link>
    2342     <atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
    2343     <description><?php echo $this->description ?></description>
    2344     <lastBuildDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false ); ?></lastBuildDate>
    2345     <generator>http://buddypress.org/?v=<?php bp_version(); ?></generator>
    2346     <language><?php bloginfo_rss( 'language' ); ?></language>
    2347     <ttl><?php echo $this->ttl; ?></ttl>
    2348     <sy:updatePeriod><?php echo $this->update_period; ?></sy:updatePeriod>
    2349     <sy:updateFrequency><?php echo $this->update_frequency; ?></sy:updateFrequency>
    2350     <?php
    2351 
    2352     /**
    2353      * Fires at the end of channel elements list in RSS feed so plugins can add extra channel elements.
    2354      *
    2355      * @since BuddyPress (1.8.0)
    2356      */
    2357     do_action( 'bp_activity_feed_channel_elements' ); ?>
    2358 
    2359     <?php if ( bp_has_activities( $this->activity_args ) ) : ?>
    2360         <?php while ( bp_activities() ) : bp_the_activity(); ?>
    2361             <item>
    2362                 <guid isPermaLink="false"><?php bp_activity_feed_item_guid(); ?></guid>
    2363                 <title><?php echo stripslashes( bp_get_activity_feed_item_title() ); ?></title>
    2364                 <link><?php bp_activity_thread_permalink() ?></link>
    2365                 <pubDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_get_activity_feed_item_date(), false ); ?></pubDate>
    2366 
    2367                 <?php if ( bp_get_activity_feed_item_description() ) : ?>
    2368                     <content:encoded><![CDATA[<?php $this->feed_content(); ?>]]></content:encoded>
    2369                 <?php endif; ?>
    2370 
    2371                 <?php if ( bp_activity_can_comment() ) : ?>
    2372                     <slash:comments><?php bp_activity_comment_count(); ?></slash:comments>
    2373                 <?php endif; ?>
    2374 
    2375                 <?php
    2376 
    2377                 /**
    2378                  * Fires at the end of the individual RSS Item list in RSS feed so plugins can add extra item elements.
    2379                  *
    2380                  * @since BuddyPress (1.8.0)
    2381                  */
    2382                 do_action( 'bp_activity_feed_item_elements' ); ?>
    2383             </item>
    2384         <?php endwhile; ?>
    2385 
    2386     <?php endif; ?>
    2387 </channel>
    2388 </rss><?php
    2389     }
    2390 }
     12require __DIR__ . '/classes/class-bp-activity-activity.php';
     13require __DIR__ . '/classes/class-bp-activity-feed.php';
     14require __DIR__ . '/classes/class-bp-activity-query.php';
Note: See TracChangeset for help on using the changeset viewer.