Skip to:
Content

BuddyPress.org

Ticket #3497: bp-activity-classes-adjusted.php

File bp-activity-classes-adjusted.php, 50.4 KB (added by finzend, 10 years ago)

New classes file, based on 1.9.1

Line 
1<?php
2/**
3 * BuddyPress Activity Classes
4 *
5 * @package BuddyPress
6 * @subpackage Activity
7 */
8
9// Exit if accessed directly
10if ( !defined( 'ABSPATH' ) ) exit;
11
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 */
20class 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, $bp;
139
140                if ( $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->activity->table_name} WHERE id = %d", $this->id ) ) ) {
141                        $this->id                = $row->id;
142                        $this->item_id           = $row->item_id;
143                        $this->secondary_item_id = $row->secondary_item_id;
144                        $this->user_id           = $row->user_id;
145                        $this->primary_link      = $row->primary_link;
146                        $this->component         = $row->component;
147                        $this->type              = $row->type;
148                        $this->action            = $row->action;
149                        $this->content           = $row->content;
150                        $this->date_recorded     = $row->date_recorded;
151                        $this->hide_sitewide     = $row->hide_sitewide;
152                        $this->mptt_left         = $row->mptt_left;
153                        $this->mptt_right        = $row->mptt_right;
154                        $this->is_spam           = $row->is_spam;
155
156                        bp_activity_update_meta_cache( $this->id );
157                }
158        }
159
160        /**
161         * Save the activity item to the database.
162         *
163         * @return bool True on success.
164         */
165        public function save() {
166                global $wpdb, $bp;
167
168                $this->id                = apply_filters_ref_array( 'bp_activity_id_before_save',                array( $this->id,                &$this ) );
169                $this->item_id           = apply_filters_ref_array( 'bp_activity_item_id_before_save',           array( $this->item_id,           &$this ) );
170                $this->secondary_item_id = apply_filters_ref_array( 'bp_activity_secondary_item_id_before_save', array( $this->secondary_item_id, &$this ) );
171                $this->user_id           = apply_filters_ref_array( 'bp_activity_user_id_before_save',           array( $this->user_id,           &$this ) );
172                $this->primary_link      = apply_filters_ref_array( 'bp_activity_primary_link_before_save',      array( $this->primary_link,      &$this ) );
173                $this->component         = apply_filters_ref_array( 'bp_activity_component_before_save',         array( $this->component,         &$this ) );
174                $this->type              = apply_filters_ref_array( 'bp_activity_type_before_save',              array( $this->type,              &$this ) );
175                $this->action            = apply_filters_ref_array( 'bp_activity_action_before_save',            array( $this->action,            &$this ) );
176                $this->content           = apply_filters_ref_array( 'bp_activity_content_before_save',           array( $this->content,           &$this ) );
177                $this->date_recorded     = apply_filters_ref_array( 'bp_activity_date_recorded_before_save',     array( $this->date_recorded,     &$this ) );
178                $this->hide_sitewide     = apply_filters_ref_array( 'bp_activity_hide_sitewide_before_save',     array( $this->hide_sitewide,     &$this ) );
179                $this->mptt_left         = apply_filters_ref_array( 'bp_activity_mptt_left_before_save',         array( $this->mptt_left,         &$this ) );
180                $this->mptt_right        = apply_filters_ref_array( 'bp_activity_mptt_right_before_save',        array( $this->mptt_right,        &$this ) );
181                $this->is_spam           = apply_filters_ref_array( 'bp_activity_is_spam_before_save',           array( $this->is_spam,           &$this ) );
182
183                // Use this, not the filters above
184                do_action_ref_array( 'bp_activity_before_save', array( &$this ) );
185
186                if ( !$this->component || !$this->type )
187                        return false;
188
189                if ( !$this->primary_link )
190                        $this->primary_link = bp_loggedin_user_domain();
191
192                // If we have an existing ID, update the activity item, otherwise insert it.
193                if ( $this->id )
194                        $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 );
195                else
196                        $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 );
197
198                if ( false === $wpdb->query( $q ) )
199                        return false;
200
201                // If this is a new activity item, set the $id property
202                if ( empty( $this->id ) )
203                        $this->id = $wpdb->insert_id;
204
205                // If an existing activity item, prevent any changes to the content generating new @mention notifications.
206                else
207                        add_filter( 'bp_activity_at_name_do_notifications', '__return_false' );
208
209                do_action_ref_array( 'bp_activity_after_save', array( &$this ) );
210
211                return true;
212        }
213
214        /** Static Methods ***************************************************/
215
216        /**
217         * Get activity items, as specified by parameters
218         *
219         * @see BP_Activity_Activity::get_filter_sql() for a description of the
220         *      'filter' parameter.
221         * @see WP_Meta_Query::queries for a description of the 'meta_query'
222         *      parameter format.
223         *
224         * @param array $args {
225         *     An array of arguments. All items are optional.
226         *     @type int $page Which page of results to fetch. Using page=1
227         *                     without per_page will result in no pagination.
228         *                     Default: 1.
229         *     @type int|bool $per_page Number of results per page. Default: 25.
230         *     @type int|bool $max Maximum number of results to return.
231         *                         Default: false (unlimited).
232         *     @type string $sort ASC or DESC. Default: 'DESC'.
233         *     @type array $exclude Array of activity IDs to exclude.
234         *                          Default: false.
235         *     @type array $in Array of ids to limit query by (IN).
236         *                     Default: false.
237         *     @type array $meta_query An array of meta_query conditions.
238         *                             See WP_Meta_Query::queries for description.
239         *     @type array $filter See BP_Activity_Activity::get_filter_sql().
240         *     @type string $search_terms Limit results by a search term.
241         *                                Default: false.
242         *     @type bool $display_comments Whether to include activity comments.
243         *                                  Default: false.
244         *     @type bool $show_hidden Whether to show items marked hide_sitewide.
245         *                             Default: false.
246         *     @type string $spam Spam status. Default: 'ham_only'.
247         * }
248         * @return array The array returned has two keys:
249         *     - 'total' is the count of located activities
250         *     - 'activities' is an array of the located activities
251         */
252        public static function get( $args = array() ) {
253                global $wpdb, $bp;
254
255                // Backward compatibility with old method of passing arguments
256                if ( !is_array( $args ) || func_num_args() > 1 ) {
257                        _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__ ) );
258
259                        $old_args_keys = array(
260                                0 => 'max',
261                                1 => 'page',
262                                2 => 'per_page',
263                                3 => 'sort',
264                                4 => 'search_terms',
265                                5 => 'filter',
266                                6 => 'display_comments',
267                                7 => 'show_hidden',
268                                8 => 'exclude',
269                                9 => 'in',
270                                10 => 'spam'
271                        );
272
273                        $func_args = func_get_args();
274                        $args      = bp_core_parse_args_array( $old_args_keys, $func_args );
275                }
276
277                $defaults = array(
278                        'page'             => 1,          // The current page
279                        'per_page'         => 25,         // Activity items per page
280                        'max'              => false,      // Max number of items to return
281                        'sort'             => 'DESC',     // ASC or DESC
282                        'exclude'          => false,      // Array of ids to exclude
283                        'in'               => false,      // Array of ids to limit query by (IN)
284                        'meta_query'       => false,      // Filter by activitymeta
285                        'filter'           => false,      // See self::get_filter_sql()
286                        'search_terms'     => false,      // Terms to search by
287                        'display_comments' => false,      // Whether to include activity comments
288                        'show_hidden'      => false,      // Show items marked hide_sitewide
289                        'spam'             => 'ham_only', // Spam status
290                );
291                $r = wp_parse_args( $args, $defaults );
292                extract( $r );
293
294                // Select conditions old:
295                //$select_sql = "SELECT DISTINCT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name";
296                //$from_sql = " FROM {$bp->activity->table_name} a LEFT JOIN {$wpdb->users} u ON a.user_id = u.ID";
297
298                // Select conditions new: finzend
299                $select_sql = "SELECT id";
300                $from_sql = " FROM {$bp->activity->table_name} a";
301
302                $join_sql = '';
303
304                // Where conditions
305                $where_conditions = array();
306
307                // Spam
308                if ( 'ham_only' == $spam )
309                        $where_conditions['spam_sql'] = 'a.is_spam = 0';
310                elseif ( 'spam_only' == $spam )
311                        $where_conditions['spam_sql'] = 'a.is_spam = 1';
312
313                // Searching
314                if ( $search_terms ) {
315                        $search_terms = esc_sql( $search_terms );
316                        $where_conditions['search_sql'] = "a.content LIKE '%%" . esc_sql( like_escape( $search_terms ) ) . "%%'";
317                }
318
319                // Filtering
320                if ( $filter && $filter_sql = BP_Activity_Activity::get_filter_sql( $filter ) )
321                        $where_conditions['filter_sql'] = $filter_sql;
322
323                // Sorting
324                if ( $sort != 'ASC' && $sort != 'DESC' )
325                        $sort = 'DESC';
326
327                // Hide Hidden Items?
328                if ( !$show_hidden )
329                        $where_conditions['hidden_sql'] = "a.hide_sitewide != 1"; //finzend
330
331                // Exclude specified items
332                if ( !empty( $exclude ) ) {
333                        $exclude = implode( ',', wp_parse_id_list( $exclude ) );
334                        $where_conditions['exclude'] = "a.id NOT IN ({$exclude})";
335                }
336
337                // The specific ids to which you want to limit the query
338                if ( !empty( $in ) ) {
339                        $in = implode( ',', wp_parse_id_list( $in ) );
340                        $where_conditions['in'] = "a.id IN ({$in})";
341                }
342
343                // Process meta_query into SQL
344                $meta_query_sql = self::get_meta_query_sql( $meta_query );
345
346                if ( ! empty( $meta_query_sql['join'] ) ) {
347                        $join_sql .= $meta_query_sql['join'];
348                }
349
350                if ( ! empty( $meta_query_sql['where'] ) ) {
351                        $where_conditions[] = $meta_query_sql['where'];
352                }
353
354                // Alter the query based on whether we want to show activity item
355                // comments in the stream like normal comments or threaded below
356                // the activity.
357                if ( false === $display_comments || 'threaded' === $display_comments ) {
358                        $where_conditions[] = "a.type != 'activity_comment'";
359                }
360
361                // Filter the where conditions
362                $where_conditions = apply_filters( 'bp_activity_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql );
363
364                // Join the where conditions together
365                $where_sql = 'WHERE ' . join( ' AND ', $where_conditions );
366
367                // Define the preferred order for indexes
368                $indexes = apply_filters( 'bp_activity_preferred_index_order', array( 'user_id', 'item_id', 'secondary_item_id', 'date_recorded', 'component', 'type', 'hide_sitewide', 'is_spam' ) );
369
370                foreach( $indexes as $key => $index ) {
371                        if ( false !== strpos( $where_sql, $index ) ) {
372                                $the_index = $index;
373                                break; // Take the first one we find
374                        }
375                }
376
377                if ( !empty( $the_index ) ) {
378                        $index_hint_sql = "USE INDEX ({$the_index})";
379                } else {
380                        $index_hint_sql = '';
381                }
382
383                if ( !empty( $per_page ) && !empty( $page ) ) {
384
385                        // Make sure page values are absolute integers
386                        $page     = absint( $page     );
387                        $per_page = absint( $per_page );
388
389                        $pag_sql    = $wpdb->prepare( "LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page );
390
391                        // Added by finzend
392                        $full_sql_statement = "SELECT a1.*, u.user_email, u.user_nicename, u.user_login, u.display_name ".
393                          "FROM wp_bp_activity a1, wp_users u, (".
394                          {$select_sql} {$from_sql} {$where_sql} ORDER BY a.date_recorded {$sort} {$pag_sql} ".
395                          ") a2 ".
396                          "WHERE a1.id = a2.id AND a1.user_id = u.ID ";
397
398                        //$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 ) );
399                        //Adjusted by finzend:
400                        $activities = $wpdb->get_results( $wpdb->prepare( $full_sql_statement ) );
401                } else {
402                        $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 ) );
403                }
404
405                $total_activities_sql = apply_filters( 'bp_activity_total_activities_sql', "SELECT count(DISTINCT a.id) FROM {$bp->activity->table_name} a {$index_hint_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}", $where_sql, $sort );
406
407                //$total_activities = $wpdb->get_var( $total_activities_sql );
408                //Adjusted by finzend:
409                $total_activities = $wpdb->get_var( $wpdb->prepare( "SELECT count(a.id) FROM {$bp->activity->table_name} a {$where_sql}" ) );
410
411                // Get the fullnames of users so we don't have to query in the loop
412                if ( bp_is_active( 'xprofile' ) && !empty( $activities ) ) {
413                        $activity_user_ids = wp_list_pluck( $activities, 'user_id' );
414                        $activity_user_ids = implode( ',', wp_parse_id_list( $activity_user_ids ) );
415
416                        if ( !empty( $activity_user_ids ) ) {
417                                if ( $names = $wpdb->get_results( "SELECT user_id, value AS user_fullname FROM {$bp->profile->table_name_data} WHERE field_id = 1 AND user_id IN ({$activity_user_ids})" ) ) {
418                                        foreach ( (array) $names as $name )
419                                                $tmp_names[$name->user_id] = $name->user_fullname;
420
421                                        foreach ( (array) $activities as $i => $activity ) {
422                                                if ( !empty( $tmp_names[$activity->user_id] ) )
423                                                        $activities[$i]->user_fullname = $tmp_names[$activity->user_id];
424                                        }
425
426                                        unset( $names );
427                                        unset( $tmp_names );
428                                }
429                        }
430                }
431
432                // Get activity meta
433                $activity_ids = array();
434                foreach ( (array) $activities as $activity ) {
435                        $activity_ids[] = $activity->id;
436                }
437
438                if ( !empty( $activity_ids ) ) {
439                        bp_activity_update_meta_cache( $activity_ids );
440                }
441
442                if ( $activities && $display_comments )
443                        $activities = BP_Activity_Activity::append_comments( $activities, $spam );
444
445                // If $max is set, only return up to the max results
446                if ( !empty( $max ) ) {
447                        if ( (int) $total_activities > (int) $max )
448                                $total_activities = $max;
449                }
450
451                return array( 'activities' => $activities, 'total' => (int) $total_activities );
452        }
453
454        /**
455         * Get the SQL for the 'meta_query' param in BP_Activity_Activity::get().
456         *
457         * We use WP_Meta_Query to do the heavy lifting of parsing the
458         * meta_query array and creating the necessary SQL clauses. However,
459         * since BP_Activity_Activity::get() builds its SQL differently than
460         * WP_Query, we have to alter the return value (stripping the leading
461         * AND keyword from the 'where' clause).
462         *
463         * @since BuddyPress (1.8)
464         *
465         * @param array $meta_query An array of meta_query filters. See the
466         *   documentation for WP_Meta_Query for details.
467         * @return array $sql_array 'join' and 'where' clauses.
468         */
469        public static function get_meta_query_sql( $meta_query = array() ) {
470                global $wpdb;
471
472                $sql_array = array(
473                        'join'  => '',
474                        'where' => '',
475                );
476
477                if ( ! empty( $meta_query ) ) {
478                        $activity_meta_query = new WP_Meta_Query( $meta_query );
479
480                        // WP_Meta_Query expects the table name at
481                        // $wpdb->activitymeta
482                        $wpdb->activitymeta = buddypress()->activity->table_name_meta;
483
484                        $meta_sql = $activity_meta_query->get_sql( 'activity', 'a', 'id' );
485
486                        // Strip the leading AND - BP handles it in get()
487                        $sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] );
488                        $sql_array['join']  = $meta_sql['join'];
489                }
490
491                return $sql_array;
492        }
493
494        /**
495         * In BuddyPress 1.2.x, this was used to retrieve specific activity stream items (for example, on an activity's permalink page).
496         *
497         * As of 1.5.x, use BP_Activity_Activity::get() with an 'in' parameter instead.
498         *
499         * @since BuddyPress (1.2)
500         *
501         * @deprecated 1.5
502         * @deprecated Use BP_Activity_Activity::get() with an 'in' parameter instead.
503         *
504         * @param mixed $activity_ids Array or comma-separated string of activity IDs to retrieve
505         * @param int $max Maximum number of results to return. (Optional; default is no maximum)
506         * @param int $page The set of results that the user is viewing. Used in pagination. (Optional; default is 1)
507         * @param int $per_page Specifies how many results per page. Used in pagination. (Optional; default is 25)
508         * @param string MySQL column sort; ASC or DESC. (Optional; default is DESC)
509         * @param bool $display_comments Retrieve an activity item's associated comments or not. (Optional; default is false)
510         * @return array
511         */
512        public static function get_specific( $activity_ids, $max = false, $page = 1, $per_page = 25, $sort = 'DESC', $display_comments = false ) {
513                _deprecated_function( __FUNCTION__, '1.5', 'Use BP_Activity_Activity::get() with the "in" parameter instead.' );
514                return BP_Activity_Activity::get( $max, $page, $per_page, $sort, false, false, $display_comments, false, false, $activity_ids );
515        }
516
517        /**
518         * Get the first activity ID that matches a set of criteria.
519         *
520         * @param int $user_id The user ID to filter by.
521         * @param string $component The component to filter by.
522         * @param string $type The activity type to filter by.
523         * @param int $item_id The associated item to filter by.
524         * @param int $secondary_item_id The secondary associated item to filter by.
525         * @param string $action The action to filter by.
526         * @param string $content The content to filter by.
527         * @param string $date_recorded The date to filter by.
528         * @return int|bool Activity ID on success, false if none is found.
529         */
530        public static function get_id( $user_id, $component, $type, $item_id, $secondary_item_id, $action, $content, $date_recorded ) {
531                global $bp, $wpdb;
532
533                $where_args = false;
534
535                if ( !empty( $user_id ) )
536                        $where_args[] = $wpdb->prepare( "user_id = %d", $user_id );
537
538                if ( !empty( $component ) )
539                        $where_args[] = $wpdb->prepare( "component = %s", $component );
540
541                if ( !empty( $type ) )
542                        $where_args[] = $wpdb->prepare( "type = %s", $type );
543
544                if ( !empty( $item_id ) )
545                        $where_args[] = $wpdb->prepare( "item_id = %d", $item_id );
546
547                if ( !empty( $secondary_item_id ) )
548                        $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id );
549
550                if ( !empty( $action ) )
551                        $where_args[] = $wpdb->prepare( "action = %s", $action );
552
553                if ( !empty( $content ) )
554                        $where_args[] = $wpdb->prepare( "content = %s", $content );
555
556                if ( !empty( $date_recorded ) )
557                        $where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded );
558
559                if ( !empty( $where_args ) )
560                        $where_sql = 'WHERE ' . join( ' AND ', $where_args );
561                else
562                        return false;
563
564                return $wpdb->get_var( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" );
565        }
566
567        /**
568         * Delete activity items from the database.
569         *
570         * To delete a specific activity item, pass an 'id' parameter.
571         * Otherwise use the filters.
572         *
573         * @since BuddyPress (1.2)
574         *
575         * @param array $args {
576         *     @int $id Optional. The ID of a specific item to delete.
577         *     @string $action Optional. The action to filter by.
578         *     @string $content Optional. The content to filter by.
579         *     @string $component Optional. The component name to filter by.
580         *     @string $type Optional. The activity type to filter by.
581         *     @string $primary_link Optional. The primary URL to filter by.
582         *     @int $user_id Optional. The user ID to filter by.
583         *     @int $item_id Optional. The associated item ID to filter by.
584         *     @int $secondary_item_id Optional. The secondary associated item ID to filter by.
585         *     @string $date_recorded Optional. The date to filter by.
586         *     @int $hide_sitewide Optional. Default: false.
587         * }
588         * @return array|bool An array of deleted activity IDs on success, false on failure.
589         */
590        public static function delete( $args = array() ) {
591                global $wpdb, $bp;
592
593                $defaults = array(
594                        'id'                => false,
595                        'action'            => false,
596                        'content'           => false,
597                        'component'         => false,
598                        'type'              => false,
599                        'primary_link'      => false,
600                        'user_id'           => false,
601                        'item_id'           => false,
602                        'secondary_item_id' => false,
603                        'date_recorded'     => false,
604                        'hide_sitewide'     => false
605                );
606                $params = wp_parse_args( $args, $defaults );
607                extract( $params );
608
609                $where_args = false;
610
611                if ( !empty( $id ) )
612                        $where_args[] = $wpdb->prepare( "id = %d", $id );
613
614                if ( !empty( $user_id ) )
615                        $where_args[] = $wpdb->prepare( "user_id = %d", $user_id );
616
617                if ( !empty( $action ) )
618                        $where_args[] = $wpdb->prepare( "action = %s", $action );
619
620                if ( !empty( $content ) )
621                        $where_args[] = $wpdb->prepare( "content = %s", $content );
622
623                if ( !empty( $component ) )
624                        $where_args[] = $wpdb->prepare( "component = %s", $component );
625
626                if ( !empty( $type ) )
627                        $where_args[] = $wpdb->prepare( "type = %s", $type );
628
629                if ( !empty( $primary_link ) )
630                        $where_args[] = $wpdb->prepare( "primary_link = %s", $primary_link );
631
632                if ( !empty( $item_id ) )
633                        $where_args[] = $wpdb->prepare( "item_id = %d", $item_id );
634
635                if ( !empty( $secondary_item_id ) )
636                        $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id );
637
638                if ( !empty( $date_recorded ) )
639                        $where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded );
640
641                //if ( !empty( $hide_sitewide ) )
642                //      $where_args[] = $wpdb->prepare( "hide_sitewide = %d", $hide_sitewide );
643        // adjusted by finzend
644         if ($hide_sitewide==0) {
645            $where_args[] = $wpdb->prepare( "hide_sitewide != 1" );
646        } elseif ( !empty( $hide_sitewide ) ) {
647            $where_args[] = $wpdb->prepare( "hide_sitewide = %d", $hide_sitewide );
648        }
649
650                if ( !empty( $where_args ) )
651                        $where_sql = 'WHERE ' . join( ' AND ', $where_args );
652                else
653                        return false;
654
655                // Fetch the activity IDs so we can delete any comments for this activity item
656                $activity_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" );
657
658                if ( ! $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$where_sql}" ) ) {
659                        return false;
660                }
661
662                // Handle accompanying activity comments and meta deletion
663                if ( $activity_ids ) {
664                        $activity_ids_comma          = implode( ',', wp_parse_id_list( $activity_ids ) );
665                        $activity_comments_where_sql = "WHERE type = 'activity_comment' AND item_id IN ({$activity_ids_comma})";
666
667                        // Fetch the activity comment IDs for our deleted activity items
668                        $activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} {$activity_comments_where_sql}" );
669
670                        // We have activity comments!
671                        if ( ! empty( $activity_comment_ids ) ) {
672                                // Delete activity comments
673                                $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$activity_comments_where_sql}" );
674
675                                // Merge activity IDs with activity comment IDs
676                                $activity_ids = array_merge( $activity_ids, $activity_comment_ids );
677                        }
678
679                        // Delete all activity meta entries for activity items and activity comments
680                        BP_Activity_Activity::delete_activity_meta_entries( $activity_ids );
681                }
682
683                return $activity_ids;
684        }
685
686        /**
687         * Delete the comments associated with a set of activity items.
688         *
689         * @since BuddyPress (1.2)
690         *
691         * @todo Mark as deprecated?  Method is no longer used internally.
692         *
693         * @param array $activity_ids Activity IDs whose comments should be deleted.
694         * @param bool $delete_meta Should we delete the activity meta items for these comments?
695         * @return bool True on success.
696         */
697        public static function delete_activity_item_comments( $activity_ids = array(), $delete_meta = true ) {
698                global $bp, $wpdb;
699
700                $delete_meta = (bool) $delete_meta;
701
702                $activity_ids = implode( ',', wp_parse_id_list( $activity_ids ) );
703
704                if ( $delete_meta ) {
705                        // Fetch the activity comment IDs for our deleted activity items
706                        $activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" );
707
708                        if ( ! empty( $activity_comment_ids ) ) {
709                                self::delete_activity_meta_entries( $activity_comment_ids );
710                        }
711                }
712
713                return $wpdb->query( "DELETE FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" );
714        }
715
716        /**
717         * Delete the meta entries associated with a set of activity items.
718         *
719         * @since BuddyPress (1.2)
720         *
721         * @param array $activity_ids Activity IDs whose meta should be deleted.
722         * @return bool True on success.
723         */
724        public static function delete_activity_meta_entries( $activity_ids = array() ) {
725                global $bp, $wpdb;
726
727                $activity_ids = implode( ',', wp_parse_id_list( $activity_ids ) );
728
729                foreach ( (array) $activity_ids as $activity_id ) {
730                        bp_activity_clear_meta_cache_for_activity( $activity_id );
731                }
732
733                return $wpdb->query( "DELETE FROM {$bp->activity->table_name_meta} WHERE activity_id IN ({$activity_ids})" );
734        }
735
736        /**
737         * Append activity comments to their associated activity items.
738         *
739         * @since BuddyPress (1.2)
740         *
741         * @global wpdb $wpdb WordPress database object
742         *
743         * @param array $activities Activities to fetch comments for.
744         * @param bool $spam Optional. 'ham_only' (default), 'spam_only' or 'all'.
745         * @return array The updated activities with nested comments.
746         */
747        public static function append_comments( $activities, $spam = 'ham_only' ) {
748                $activity_comments = array();
749
750                // Now fetch the activity comments and parse them into the correct position in the activities array.
751                foreach( (array) $activities as $activity ) {
752                        $top_level_parent_id = 'activity_comment' == $activity->type ? $activity->item_id : 0;
753                        $activity_comments[$activity->id] = BP_Activity_Activity::get_activity_comments( $activity->id, $activity->mptt_left, $activity->mptt_right, $spam, $top_level_parent_id );
754                }
755
756                // Merge the comments with the activity items
757                foreach( (array) $activities as $key => $activity )
758                        if ( isset( $activity_comments[$activity->id] ) )
759                                $activities[$key]->children = $activity_comments[$activity->id];
760
761                return $activities;
762        }
763
764        /**
765         * Get activity comments that are associated with a specific activity ID.
766         *
767         * @since BuddyPress (1.2)
768         *
769         * @global BuddyPress $bp The one true BuddyPress instance.
770         * @global wpdb $wpdb WordPress database object.
771         *
772         * @param int $activity_id Activity ID to fetch comments for.
773         * @param int $left Left-most node boundary.
774         * @param into $right Right-most node boundary.
775         * @param bool $spam Optional. 'ham_only' (default), 'spam_only' or 'all'.
776         * @param int $top_level_parent_id Optional. The id of the root-level parent activity item.
777         * @return array The updated activities with nested comments.
778         */
779        public static function get_activity_comments( $activity_id, $left, $right, $spam = 'ham_only', $top_level_parent_id = 0 ) {
780                global $wpdb, $bp;
781
782                if ( empty( $top_level_parent_id ) ) {
783                        $top_level_parent_id = $activity_id;
784                }
785
786                if ( !$comments = wp_cache_get( 'bp_activity_comments_' . $activity_id ) ) {
787
788                        // Select the user's fullname with the query
789                        if ( bp_is_active( 'xprofile' ) ) {
790                                $fullname_select = ", pd.value as user_fullname";
791                                $fullname_from = ", {$bp->profile->table_name_data} pd ";
792                                $fullname_where = "AND pd.user_id = a.user_id AND pd.field_id = 1";
793
794                        // Prevent debug errors
795                        } else {
796                                $fullname_select = $fullname_from = $fullname_where = '';
797                        }
798
799                        // Don't retrieve activity comments marked as spam
800                        if ( 'ham_only' == $spam ) {
801                                $spam_sql = 'AND a.is_spam = 0';
802                        } elseif ( 'spam_only' == $spam ) {
803                                $spam_sql = 'AND a.is_spam = 1';
804                        } else {
805                                $spam_sql = '';
806                        }
807
808                        // The mptt BETWEEN clause allows us to limit returned descendants to the right part of the tree
809                        $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 );
810
811                        // Retrieve all descendants of the $root node
812                        $descendants = $wpdb->get_results( $sql );
813                        $ref         = array();
814
815                        // Loop descendants and build an assoc array
816                        foreach ( (array) $descendants as $d ) {
817                                $d->children = array();
818
819                                // If we have a reference on the parent
820                                if ( isset( $ref[ $d->secondary_item_id ] ) ) {
821                                        $ref[ $d->secondary_item_id ]->children[ $d->id ] = $d;
822                                        $ref[ $d->id ] =& $ref[ $d->secondary_item_id ]->children[ $d->id ];
823
824                                // If we don't have a reference on the parent, put in the root level
825                                } else {
826                                        $comments[ $d->id ] = $d;
827                                        $ref[ $d->id ] =& $comments[ $d->id ];
828                                }
829                        }
830                        wp_cache_set( 'bp_activity_comments_' . $activity_id, $comments, 'bp' );
831                }
832
833                return $comments;
834        }
835
836        /**
837         * Rebuild nested comment tree under an activity or activity comment.
838         *
839         * @since BuddyPress (1.2)
840         *
841         * @global BuddyPress $bp The one true BuddyPress instance.
842         * @global wpdb $wpdb WordPress database object.
843         *
844         * @param int $parent_id ID of an activty or activity comment.
845         * @param int $left Node boundary start for activity or activity comment.
846         * @return int Right node boundary of activity or activity comment.
847         */
848        public static function rebuild_activity_comment_tree( $parent_id, $left = 1 ) {
849                global $wpdb, $bp;
850
851                // The right value of this node is the left value + 1
852                $right = $left + 1;
853
854                // Get all descendants of this node
855                $descendants = BP_Activity_Activity::get_child_comments( $parent_id );
856
857                // Loop the descendants and recalculate the left and right values
858                foreach ( (array) $descendants as $descendant )
859                        $right = BP_Activity_Activity::rebuild_activity_comment_tree( $descendant->id, $right );
860
861                // We've got the left value, and now that we've processed the children
862                // of this node we also know the right value
863                if ( 1 == $left )
864                        $wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE id = %d", $left, $right, $parent_id ) );
865                else
866                        $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 ) );
867
868                // Return the right value of this node + 1
869                return $right + 1;
870        }
871
872        /**
873         * Get child comments of an activity or activity comment.
874         *
875         * @since BuddyPress (1.2)
876         *
877         * @global BuddyPress $bp The one true BuddyPress instance.
878         * @global wpdb $wpdb WordPress database object.
879         *
880         * @param int $parent_id ID of an activty or activity comment.
881         * @return object Numerically indexed array of child comments.
882         */
883        public static function get_child_comments( $parent_id ) {
884                global $bp, $wpdb;
885
886                return $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND secondary_item_id = %d", $parent_id ) );
887        }
888
889        /**
890         * Get a list of components that have recorded activity associated with them
891         *
892         * @return array List of component names.
893         */
894        public static function get_recorded_components() {
895                global $wpdb, $bp;
896                return $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} ORDER BY component ASC" );
897        }
898
899        /**
900         * Get sitewide activity items for use in an RSS feed.
901         *
902         * @param int $limit Optional. Number of items to fetch. Default: 35.
903         * @return array $activity_feed List of activity items, with RSS data added.
904         */
905        public static function get_sitewide_items_for_feed( $limit = 35 ) {
906                $activities    = bp_activity_get_sitewide( array( 'max' => $limit ) );
907                $activity_feed = array();
908
909                for ( $i = 0, $count = count( $activities ); $i < $count; ++$i ) {
910                        $title                            = explode( '<span', $activities[$i]['content'] );
911                        $activity_feed[$i]['title']       = trim( strip_tags( $title[0] ) );
912                        $activity_feed[$i]['link']        = $activities[$i]['primary_link'];
913                        $activity_feed[$i]['description'] = @sprintf( $activities[$i]['content'], '' );
914                        $activity_feed[$i]['pubdate']     = $activities[$i]['date_recorded'];
915                }
916
917                return $activity_feed;
918        }
919
920        /**
921         * Create SQL IN clause for filter queries.
922         *
923         * @since BuddyPress (1.5)
924         *
925         * @see BP_Activity_Activity::get_filter_sql()
926         *
927         * @param string $field The database field.
928         * @param array|bool $items The values for the IN clause, or false when none are found.
929         */
930        public static function get_in_operator_sql( $field, $items ) {
931                global $wpdb;
932
933                // split items at the comma
934                $items_dirty = explode( ',', $items );
935
936                // array of prepared integers or quoted strings
937                $items_prepared = array();
938
939                // clean up and format each item
940                foreach ( $items_dirty as $item ) {
941                        // clean up the string
942                        $item = trim( $item );
943                        // pass everything through prepare for security and to safely quote strings
944                        $items_prepared[] = ( is_numeric( $item ) ) ? $wpdb->prepare( '%d', $item ) : $wpdb->prepare( '%s', $item );
945                }
946
947                // build IN operator sql syntax
948                if ( count( $items_prepared ) )
949                        return sprintf( '%s IN ( %s )', trim( $field ), implode( ',', $items_prepared ) );
950                else
951                        return false;
952        }
953
954        /**
955         * Create filter SQL clauses.
956         *
957         * @since BuddyPress (1.5)
958         *
959         * @param array $filter_array Fields and values to filter by. Should be
960         *     in the format:
961         *         $filter_array = array(
962         *             'filter1' => $value,
963         *             'filter2' => $value,
964         *         )
965         *     Possible filters are as follows. Each can be either a single
966         *     string, a comma-separated list, or an array of values.
967         *       - 'user_id' User ID(s)
968         *       - 'object' Corresponds to the 'component' column in the database.
969         *       - 'action' Corresponds to the 'type' column in the database.
970         *       - 'primary_id' Corresponds to the 'item_id' column in the database.
971         *       - 'secondary_id' Corresponds to the 'secondary_item_id' column in the database.
972         * @return string The filter clause, for use in a SQL query.
973         */
974        public static function get_filter_sql( $filter_array ) {
975
976                $filter_sql = array();
977
978                if ( !empty( $filter_array['user_id'] ) ) {
979                        $user_sql = BP_Activity_Activity::get_in_operator_sql( 'a.user_id', $filter_array['user_id'] );
980                        if ( !empty( $user_sql ) )
981                                $filter_sql[] = $user_sql;
982                }
983
984                if ( !empty( $filter_array['object'] ) ) {
985                        $object_sql = BP_Activity_Activity::get_in_operator_sql( 'a.component', $filter_array['object'] );
986                        if ( !empty( $object_sql ) )
987                                $filter_sql[] = $object_sql;
988                }
989
990                if ( !empty( $filter_array['action'] ) ) {
991                        $action_sql = BP_Activity_Activity::get_in_operator_sql( 'a.type', $filter_array['action'] );
992                        if ( !empty( $action_sql ) )
993                                $filter_sql[] = $action_sql;
994                }
995
996                if ( !empty( $filter_array['primary_id'] ) ) {
997                        $pid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.item_id', $filter_array['primary_id'] );
998                        if ( !empty( $pid_sql ) )
999                                $filter_sql[] = $pid_sql;
1000                }
1001
1002                if ( !empty( $filter_array['secondary_id'] ) ) {
1003                        $sid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.secondary_item_id', $filter_array['secondary_id'] );
1004                        if ( !empty( $sid_sql ) )
1005                                $filter_sql[] = $sid_sql;
1006                }
1007
1008                if ( empty( $filter_sql ) )
1009                        return false;
1010
1011                return join( ' AND ', $filter_sql );
1012        }
1013
1014        /**
1015         * Get the date/time of last recorded activity.
1016         *
1017         * @since BuddyPress (1.2)
1018         *
1019         * @return string ISO timestamp.
1020         */
1021        public static function get_last_updated() {
1022                global $bp, $wpdb;
1023
1024                return $wpdb->get_var( "SELECT date_recorded FROM {$bp->activity->table_name} ORDER BY date_recorded DESC LIMIT 1" );
1025        }
1026
1027        /**
1028         * Get favorite count for a given user.
1029         *
1030         * @since BuddyPress (1.2)
1031         *
1032         * @param int The ID of the user whose favorites you're counting.
1033         * @return int A count of the user's favorites.
1034         */
1035        public static function total_favorite_count( $user_id ) {
1036                if ( !$favorite_activity_entries = bp_get_user_meta( $user_id, 'bp_favorite_activities', true ) )
1037                        return 0;
1038
1039                return count( maybe_unserialize( $favorite_activity_entries ) );
1040        }
1041
1042        /**
1043         * Check whether an activity item exists with a given string content.
1044         *
1045         * @param string $content The content to filter by.
1046         * @return int|bool The ID of the first matching item if found, otherwise false.
1047         */
1048        public static function check_exists_by_content( $content ) {
1049                global $wpdb, $bp;
1050
1051                return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE content = %s", $content ) );
1052        }
1053
1054        /**
1055         * Hide all activity for a given user.
1056         *
1057         * @param int $user_id The ID of the user whose activity you want to mark hidden.
1058         * @param int
1059         */
1060        public static function hide_all_for_user( $user_id ) {
1061                global $wpdb, $bp;
1062
1063                return $wpdb->get_var( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET hide_sitewide = 1 WHERE user_id = %d", $user_id ) );
1064        }
1065}
1066
1067/**
1068 * Create a RSS feed using the activity component.
1069 *
1070 * You should only construct a new feed when you've validated that you're on
1071 * the appropriate screen.
1072 *
1073 * See {@link bp_activity_action_sitewide_feed()} as an example.
1074 *
1075 * Accepted parameters:
1076 *   id               - internal id for the feed; should be alphanumeric only
1077 *                      (required)
1078 *   title            - RSS feed title
1079 *   link             - Relevant link for the RSS feed
1080 *   description      - RSS feed description
1081 *   ttl              - Time-to-live (see inline doc in constructor)
1082 *   update_period    - Part of the syndication module (see inline doc in
1083 *                      constructor for more info)
1084 *   update_frequency - Part of the syndication module (see inline doc in
1085 *                      constructor for more info)
1086 *   max              - Number of feed items to display
1087 *   activity_args    - Arguments passed to {@link bp_has_activities()}
1088 *
1089 * @since BuddyPress (1.8)
1090 */
1091class BP_Activity_Feed {
1092        /**
1093         * Holds our custom class properties.
1094         *
1095         * These variables are stored in a protected array that is magically
1096         * updated using PHP 5.2+ methods.
1097         *
1098         * @see BP_Feed::__construct() This is where $data is added
1099         * @var array
1100         */
1101        protected $data;
1102
1103        /**
1104         * Magic method for checking the existence of a certain data variable.
1105         *
1106         * @param string $key
1107         */
1108        public function __isset( $key ) { return isset( $this->data[$key] ); }
1109
1110        /**
1111         * Magic method for getting a certain data variable.
1112         *
1113         * @param string $key
1114         */
1115        public function __get( $key ) { return isset( $this->data[$key] ) ? $this->data[$key] : null; }
1116
1117        /**
1118         * Constructor.
1119         *
1120         * @param array $args Optional
1121         */
1122        public function __construct( $args = array() ) {
1123                // If feeds are disabled, stop now!
1124                if ( false === (bool) apply_filters( 'bp_activity_enable_feeds', true ) ) {
1125                        global $wp_query;
1126
1127                        // set feed flag to false
1128                        $wp_query->is_feed = false;
1129
1130                        return false;
1131                }
1132
1133                // Setup data
1134                $this->data = wp_parse_args( $args, array(
1135                        // Internal identifier for the RSS feed - should be alphanumeric only
1136                        'id'               => '',
1137
1138                        // RSS title - should be plain-text
1139                        'title'            => '',
1140
1141                        // relevant link for the RSS feed
1142                        'link'             => '',
1143
1144                        // RSS description - should be plain-text
1145                        'description'      => '',
1146
1147                        // Time-to-live - number of minutes to cache the data before an aggregator
1148                        // requests it again.  This is only acknowledged if the RSS client supports it
1149                        //
1150                        // See: http://www.rssboard.org/rss-profile#element-channel-ttl
1151                        //      http://www.kbcafe.com/rss/rssfeedstate.html#ttl
1152                        'ttl'              => '30',
1153
1154                        // Syndication module - similar to ttl, but not really supported by RSS
1155                        // clients
1156                        //
1157                        // See: http://web.resource.org/rss/1.0/modules/syndication/#description
1158                        //      http://www.kbcafe.com/rss/rssfeedstate.html#syndicationmodule
1159                        'update_period'    => 'hourly',
1160                        'update_frequency' => 2,
1161
1162                        // Number of items to display
1163                        'max'              => 50,
1164
1165                        // Activity arguments passed to bp_has_activities()
1166                        'activity_args'    => array()
1167                ) );
1168
1169                // Plugins can use this filter to modify the feed before it is setup
1170                do_action_ref_array( 'bp_activity_feed_prefetch', array( &$this ) );
1171
1172                // Setup class properties
1173                $this->setup_properties();
1174
1175                // Check if id is valid
1176                if ( empty( $this->id ) ) {
1177                        _doing_it_wrong( 'BP_Activity_Feed', __( "RSS feed 'id' must be defined", 'buddypress' ), 'BP 1.8' );
1178                        return false;
1179                }
1180
1181                // Plugins can use this filter to modify the feed after it's setup
1182                do_action_ref_array( 'bp_activity_feed_postfetch', array( &$this ) );
1183
1184                // Setup feed hooks
1185                $this->setup_hooks();
1186
1187                // Output the feed
1188                $this->output();
1189
1190                // Kill the rest of the output
1191                die();
1192        }
1193
1194        /** SETUP ****************************************************************/
1195
1196        /**
1197         * Setup and validate the class properties.
1198         *
1199         * @access protected
1200         */
1201        protected function setup_properties() {
1202                $this->id               = sanitize_title( $this->id );
1203                $this->title            = strip_tags( $this->title );
1204                $this->link             = esc_url_raw( $this->link );
1205                $this->description      = strip_tags( $this->description );
1206                $this->ttl              = (int) $this->ttl;
1207                $this->update_period    = strip_tags( $this->update_period );
1208                $this->update_frequency = (int) $this->update_frequency;
1209
1210                $this->activity_args    = wp_parse_args( $this->activity_args, array(
1211                        'max'              => $this->max,
1212                        'per_page'         => $this->max,
1213                        'display_comments' => 'stream'
1214                ) );
1215
1216        }
1217
1218        /**
1219         * Setup some hooks that are used in the feed.
1220         *
1221         * Currently, these hooks are used to maintain backwards compatibility with
1222         * the RSS feeds previous to BP 1.8.
1223         *
1224         * @access protected
1225         */
1226        protected function setup_hooks() {
1227                add_action( 'bp_activity_feed_rss_attributes',   array( $this, 'backpat_rss_attributes' ) );
1228                add_action( 'bp_activity_feed_channel_elements', array( $this, 'backpat_channel_elements' ) );
1229                add_action( 'bp_activity_feed_item_elements',    array( $this, 'backpat_item_elements' ) );
1230        }
1231
1232        /** BACKPAT HOOKS ********************************************************/
1233
1234        /**
1235         * Fire a hook to ensure backward compatibility for RSS attributes.
1236         */
1237        public function backpat_rss_attributes() {
1238                do_action( 'bp_activity_' . $this->id . '_feed' );
1239        }
1240
1241        /**
1242         * Fire a hook to ensure backward compatibility for channel elements.
1243         */
1244        public function backpat_channel_elements() {
1245                do_action( 'bp_activity_' . $this->id . '_feed_head' );
1246        }
1247
1248        /**
1249         * Fire a hook to ensure backward compatibility for item elements.
1250         */
1251        public function backpat_item_elements() {
1252                switch ( $this->id ) {
1253
1254                        // sitewide and friends feeds use the 'personal' hook
1255                        case 'sitewide' :
1256                        case 'friends' :
1257                                $id = 'personal';
1258
1259                                break;
1260
1261                        default :
1262                                $id = $this->id;
1263
1264                                break;
1265                }
1266
1267                do_action( 'bp_activity_' . $id . '_feed_item' );
1268        }
1269
1270        /** HELPERS **************************************************************/
1271
1272        /**
1273         * Output the feed's item content.
1274         *
1275         * @access protected
1276         */
1277        protected function feed_content() {
1278                bp_activity_content_body();
1279
1280                switch ( $this->id ) {
1281
1282                        // also output parent activity item if we're on a specific feed
1283                        case 'favorites' :
1284                        case 'friends' :
1285                        case 'mentions' :
1286                        case 'personal' :
1287
1288                                if ( 'activity_comment' == bp_get_activity_action_name() ) :
1289                        ?>
1290                                <strong><?php _e( 'In reply to', 'buddypress' ) ?></strong> -
1291                                <?php bp_activity_parent_content() ?>
1292                        <?php
1293                                endif;
1294
1295                                break;
1296                }
1297        }
1298
1299        /**
1300         * Sets various HTTP headers related to Content-Type and browser caching.
1301         *
1302         * Most of this class method is derived from {@link WP::send_headers()}.
1303         *
1304         * @since BuddyPress (1.9.0)
1305         *
1306         * @access protected
1307         */
1308        protected function http_headers() {
1309                // set up some additional headers if not on a directory page
1310                // this is done b/c BP uses pseudo-pages
1311                if ( ! bp_is_directory() ) {
1312                        global $wp_query;
1313
1314                        $wp_query->is_404 = false;
1315                        status_header( 200 );
1316                }
1317
1318                // Set content-type
1319                @header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true );
1320
1321                // Cache-related variables
1322                $last_modified      = mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false );
1323                $modified_timestamp = strtotime( $last_modified );
1324                $etag               = md5( $last_modified );
1325
1326                // Set cache-related headers
1327                @header( 'Last-Modified: ' . $last_modified );
1328                @header( 'Pragma: no-cache' );
1329                @header( 'ETag: ' . '"' . $etag . '"' );
1330
1331                // First commit of BuddyPress! (Easter egg)
1332                @header( 'Expires: Tue, 25 Mar 2008 17:13:55 GMT');
1333
1334                // Get ETag from supported user agents
1335                if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) {
1336                        $client_etag = wp_unslash( $_SERVER['HTTP_IF_NONE_MATCH'] );
1337
1338                        // Remove quotes from ETag
1339                        $client_etag = trim( $client_etag, '"' );
1340
1341                        // Strip suffixes from ETag if they exist (eg. "-gzip")
1342                        if ( $etag_suffix_pos = strpos( $client_etag, '-' ) ) {
1343                                $client_etag = substr( $client_etag, 0, $etag_suffix_pos );
1344                        }
1345
1346                // No ETag found
1347                } else {
1348                        $client_etag = false;
1349                }
1350
1351                // Get client last modified timestamp from supported user agents
1352                $client_last_modified      = empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ? '' : trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
1353                $client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;
1354
1355                // Set 304 status if feed hasn't been updated since last fetch
1356                if ( ( $client_last_modified && $client_etag ) ?
1357                                 ( ( $client_modified_timestamp >= $modified_timestamp ) && ( $client_etag == $etag ) ) :
1358                                 ( ( $client_modified_timestamp >= $modified_timestamp ) || ( $client_etag == $etag ) ) ) {
1359                        $status = 304;
1360                } else {
1361                        $status = false;
1362                }
1363
1364                // If feed hasn't changed as reported by the user agent, set 304 status header
1365                if ( ! empty( $status ) ) {
1366                        status_header( $status );
1367
1368                        // cached response, so stop now!
1369                        if ( $status == 304 ) {
1370                                exit();
1371                        }
1372                }
1373        }
1374
1375        /** OUTPUT ***************************************************************/
1376
1377        /**
1378         * Output the RSS feed.
1379         *
1380         * @access protected
1381         */
1382        protected function output() {
1383                $this->http_headers();
1384                echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?'.'>';
1385        ?>
1386
1387<rss version="2.0"
1388        xmlns:content="http://purl.org/rss/1.0/modules/content/"
1389        xmlns:atom="http://www.w3.org/2005/Atom"
1390        xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
1391        xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
1392        <?php do_action( 'bp_activity_feed_rss_attributes' ); ?>
1393>
1394
1395<channel>
1396        <title><?php echo $this->title; ?></title>
1397        <link><?php echo $this->link; ?></link>
1398        <atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
1399        <description><?php echo $this->description ?></description>
1400        <lastBuildDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false ); ?></lastBuildDate>
1401        <generator>http://buddypress.org/?v=<?php bp_version(); ?></generator>
1402        <language><?php bloginfo_rss( 'language' ); ?></language>
1403        <ttl><?php echo $this->ttl; ?></ttl>
1404        <sy:updatePeriod><?php echo $this->update_period; ?></sy:updatePeriod>
1405        <sy:updateFrequency><?php echo $this->update_frequency; ?></sy:updateFrequency>
1406        <?php do_action( 'bp_activity_feed_channel_elements' ); ?>
1407
1408        <?php if ( bp_has_activities( $this->activity_args ) ) : ?>
1409                <?php while ( bp_activities() ) : bp_the_activity(); ?>
1410                        <item>
1411                                <guid isPermaLink="false"><?php bp_activity_feed_item_guid(); ?></guid>
1412                                <title><?php echo stripslashes( bp_get_activity_feed_item_title() ); ?></title>
1413                                <link><?php bp_activity_thread_permalink() ?></link>
1414                                <pubDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_get_activity_feed_item_date(), false ); ?></pubDate>
1415
1416                                <?php if ( bp_get_activity_feed_item_description() ) : ?>
1417                                        <content:encoded><![CDATA[<?php $this->feed_content(); ?>]]></content:encoded>
1418                                <?php endif; ?>
1419
1420                                <?php if ( bp_activity_can_comment() ) : ?>
1421                                        <slash:comments><?php bp_activity_comment_count(); ?></slash:comments>
1422                                <?php endif; ?>
1423
1424                                <?php do_action( 'bp_activity_feed_item_elements' ); ?>
1425                        </item>
1426                <?php endwhile; ?>
1427
1428        <?php endif; ?>
1429</channel>
1430</rss><?php
1431        }
1432}