bp-activity-classes.php 35.6 KB
Newer Older
root's avatar
root committed
1
<?php
root's avatar
root committed
2
3
4
5
/**
 * BuddyPress Activity Classes
 *
 * @package BuddyPress
lechuck's avatar
lechuck committed
6
 * @subpackage Activity
root's avatar
root committed
7
8
9
10
11
 */

// Exit if accessed directly
if ( !defined( 'ABSPATH' ) ) exit;

lechuck's avatar
lechuck committed
12
class BP_Activity_Activity {
root's avatar
root committed
13
14
15
16
17
18
19
20
21
22
23
	var $id;
	var $item_id;
	var $secondary_item_id;
	var $user_id;
	var $primary_link;
	var $component;
	var $type;
	var $action;
	var $content;
	var $date_recorded;
	var $hide_sitewide = false;
root's avatar
root committed
24
25
	var $mptt_left;
	var $mptt_right;
lechuck's avatar
lechuck committed
26
	var $is_spam;
root's avatar
root committed
27
28
29

	function __construct( $id = false ) {
		if ( !empty( $id ) ) {
root's avatar
root committed
30
31
32
33
34
35
36
37
			$this->id = $id;
			$this->populate();
		}
	}

	function populate() {
		global $wpdb, $bp;

root's avatar
root committed
38
39
40
		if ( $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->activity->table_name} WHERE id = %d", $this->id ) ) ) {
			$this->id                = $row->id;
			$this->item_id           = $row->item_id;
root's avatar
root committed
41
			$this->secondary_item_id = $row->secondary_item_id;
root's avatar
root committed
42
43
44
45
46
47
48
49
50
51
			$this->user_id           = $row->user_id;
			$this->primary_link      = $row->primary_link;
			$this->component         = $row->component;
			$this->type              = $row->type;
			$this->action            = $row->action;
			$this->content           = $row->content;
			$this->date_recorded     = $row->date_recorded;
			$this->hide_sitewide     = $row->hide_sitewide;
			$this->mptt_left         = $row->mptt_left;
			$this->mptt_right        = $row->mptt_right;
lechuck's avatar
lechuck committed
52
			$this->is_spam           = $row->is_spam;
lechuck's avatar
lechuck committed
53

lechuck's avatar
lechuck committed
54
			bp_activity_update_meta_cache( $this->id );
root's avatar
root committed
55
56
57
58
		}
	}

	function save() {
lechuck's avatar
lechuck committed
59
		global $wpdb, $bp, $current_user;
root's avatar
root committed
60

root's avatar
root committed
61
62
63
64
65
66
67
68
69
70
71
72
73
		$this->id                = apply_filters_ref_array( 'bp_activity_id_before_save',                array( $this->id,                &$this ) );
		$this->item_id           = apply_filters_ref_array( 'bp_activity_item_id_before_save',           array( $this->item_id,           &$this ) );
		$this->secondary_item_id = apply_filters_ref_array( 'bp_activity_secondary_item_id_before_save', array( $this->secondary_item_id, &$this ) );
		$this->user_id           = apply_filters_ref_array( 'bp_activity_user_id_before_save',           array( $this->user_id,           &$this ) );
		$this->primary_link      = apply_filters_ref_array( 'bp_activity_primary_link_before_save',      array( $this->primary_link,      &$this ) );
		$this->component         = apply_filters_ref_array( 'bp_activity_component_before_save',         array( $this->component,         &$this ) );
		$this->type              = apply_filters_ref_array( 'bp_activity_type_before_save',              array( $this->type,              &$this ) );
		$this->action            = apply_filters_ref_array( 'bp_activity_action_before_save',            array( $this->action,            &$this ) );
		$this->content           = apply_filters_ref_array( 'bp_activity_content_before_save',           array( $this->content,           &$this ) );
		$this->date_recorded     = apply_filters_ref_array( 'bp_activity_date_recorded_before_save',     array( $this->date_recorded,     &$this ) );
		$this->hide_sitewide     = apply_filters_ref_array( 'bp_activity_hide_sitewide_before_save',     array( $this->hide_sitewide,     &$this ) );
		$this->mptt_left         = apply_filters_ref_array( 'bp_activity_mptt_left_before_save',         array( $this->mptt_left,         &$this ) );
		$this->mptt_right        = apply_filters_ref_array( 'bp_activity_mptt_right_before_save',        array( $this->mptt_right,        &$this ) );
lechuck's avatar
lechuck committed
74
		$this->is_spam           = apply_filters_ref_array( 'bp_activity_is_spam_before_save',           array( $this->is_spam,           &$this ) );
root's avatar
root committed
75
76
77

		// Use this, not the filters above
		do_action_ref_array( 'bp_activity_before_save', array( &$this ) );
root's avatar
root committed
78
79
80
81
82

		if ( !$this->component || !$this->type )
			return false;

		if ( !$this->primary_link )
lechuck's avatar
lechuck committed
83
			$this->primary_link = bp_loggedin_user_domain();
root's avatar
root committed
84

root's avatar
root committed
85
		// If we have an existing ID, update the activity item, otherwise insert it.
lechuck's avatar
lechuck committed
86
		if ( $this->id )
lechuck's avatar
lechuck committed
87
			$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 );
lechuck's avatar
lechuck committed
88
		else
lechuck's avatar
lechuck committed
89
			$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 );
root's avatar
root committed
90

lechuck's avatar
lechuck committed
91
		if ( false === $wpdb->query( $q ) )
root's avatar
root committed
92
93
			return false;

lechuck's avatar
lechuck committed
94
		// If this is a new activity item, set the $id property
lechuck's avatar
lechuck committed
95
		if ( empty( $this->id ) )
root's avatar
root committed
96
97
			$this->id = $wpdb->insert_id;

lechuck's avatar
lechuck committed
98
		// If an existing activity item, prevent any changes to the content generating new @mention notifications.
lechuck's avatar
lechuck committed
99
		else
lechuck's avatar
lechuck committed
100
101
			add_filter( 'bp_activity_at_name_do_notifications', '__return_false' );

root's avatar
root committed
102
103
		do_action_ref_array( 'bp_activity_after_save', array( &$this ) );

root's avatar
root committed
104
105
106
		return true;
	}

root's avatar
root committed
107
	// Static Functions
root's avatar
root committed
108

lechuck's avatar
lechuck committed
109
110
111
112
113
114
115
	/**
	 * Get activity items, as specified by parameters
	 *
	 * @param array $args See $defaults for explanation of arguments
	 * @return array
	 */
	function get( $args = array() ) {
root's avatar
root committed
116
117
		global $wpdb, $bp;

lechuck's avatar
lechuck committed
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
		// Backward compatibility with old method of passing arguments
		if ( !is_array( $args ) || func_num_args() > 1 ) {
			_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__ ) );

			$old_args_keys = array(
				0 => 'max',
				1 => 'page',
				2 => 'per_page',
				3 => 'sort',
				4 => 'search_terms',
				5 => 'filter',
				6 => 'display_comments',
				7 => 'show_hidden',
				8 => 'exclude',
				9 => 'in',
				10 => 'spam'
			);

			$func_args = func_get_args();
			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
		}

		$defaults = array(
			'page'             => 1,          // The current page
			'per_page'         => 25,         // Activity items per page
			'max'              => false,      // Max number of items to return
			'sort'             => 'DESC',     // ASC or DESC
			'exclude'          => false,      // Array of ids to exclude
			'in'               => false,      // Array of ids to limit query by (IN)
lechuck's avatar
lechuck committed
147
			'meta_query'       => false,      // Filter by activitymeta
lechuck's avatar
lechuck committed
148
149
150
151
152
153
154
155
156
			'filter'           => false,      // See self::get_filter_sql()
			'search_terms'     => false,      // Terms to search by
			'display_comments' => false,      // Whether to include activity comments
			'show_hidden'      => false,      // Show items marked hide_sitewide
			'spam'             => 'ham_only', // Spam status
		);
		$r = wp_parse_args( $args, $defaults );
		extract( $r );

root's avatar
root committed
157
		// Select conditions
lechuck's avatar
lechuck committed
158
		$select_sql = "SELECT DISTINCT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name";
root's avatar
root committed
159
160
161

		$from_sql = " FROM {$bp->activity->table_name} a LEFT JOIN {$wpdb->users} u ON a.user_id = u.ID";

lechuck's avatar
lechuck committed
162
163
		$join_sql = '';

root's avatar
root committed
164
		// Where conditions
root's avatar
root committed
165
166
		$where_conditions = array();

lechuck's avatar
lechuck committed
167
168
169
170
171
172
		// Spam
		if ( 'ham_only' == $spam )
			$where_conditions['spam_sql'] = 'a.is_spam = 0';
		elseif ( 'spam_only' == $spam )
			$where_conditions['spam_sql'] = 'a.is_spam = 1';

root's avatar
root committed
173
		// Searching
root's avatar
root committed
174
		if ( $search_terms ) {
lechuck's avatar
lechuck committed
175
			$search_terms = esc_sql( $search_terms );
lechuck's avatar
lechuck committed
176
			$where_conditions['search_sql'] = "a.content LIKE '%%" . esc_sql( like_escape( $search_terms ) ) . "%%'";
root's avatar
root committed
177
178
		}

root's avatar
root committed
179
		// Filtering
root's avatar
root committed
180
181
182
		if ( $filter && $filter_sql = BP_Activity_Activity::get_filter_sql( $filter ) )
			$where_conditions['filter_sql'] = $filter_sql;

root's avatar
root committed
183
		// Sorting
root's avatar
root committed
184
185
186
		if ( $sort != 'ASC' && $sort != 'DESC' )
			$sort = 'DESC';

root's avatar
root committed
187
		// Hide Hidden Items?
root's avatar
root committed
188
189
190
		if ( !$show_hidden )
			$where_conditions['hidden_sql'] = "a.hide_sitewide = 0";

root's avatar
root committed
191
		// Exclude specified items
lechuck's avatar
lechuck committed
192
193
		if ( !empty( $exclude ) ) {
			$exclude = implode( ',', wp_parse_id_list( $exclude ) );
root's avatar
root committed
194
			$where_conditions['exclude'] = "a.id NOT IN ({$exclude})";
lechuck's avatar
lechuck committed
195
		}
root's avatar
root committed
196
197
198

		// The specific ids to which you want to limit the query
		if ( !empty( $in ) ) {
lechuck's avatar
lechuck committed
199
			$in = implode( ',', wp_parse_id_list( $in ) );
root's avatar
root committed
200
			$where_conditions['in'] = "a.id IN ({$in})";
root's avatar
root committed
201
202
		}

lechuck's avatar
lechuck committed
203
204
205
206
207
208
209
210
211
212
213
		// Process meta_query into SQL
		$meta_query_sql = self::get_meta_query_sql( $meta_query );

		if ( ! empty( $meta_query_sql['join'] ) ) {
			$join_sql .= $meta_query_sql['join'];
		}

		if ( ! empty( $meta_query_sql['where'] ) ) {
			$where_conditions[] = $meta_query_sql['where'];
		}

root's avatar
root committed
214
215
216
217
218
219
		// Alter the query based on whether we want to show activity item
		// comments in the stream like normal comments or threaded below
		// the activity.
		if ( false === $display_comments || 'threaded' === $display_comments )
			$where_conditions[] = "a.type != 'activity_comment'";

root's avatar
root committed
220
221
		$where_sql = 'WHERE ' . join( ' AND ', $where_conditions );

lechuck's avatar
lechuck committed
222
223
		// Define the preferred order for indexes
		$indexes = apply_filters( 'bp_activity_preferred_index_order', array( 'user_id', 'item_id', 'secondary_item_id', 'date_recorded', 'component', 'type', 'hide_sitewide', 'is_spam' ) );
lechuck's avatar
lechuck committed
224

lechuck's avatar
lechuck committed
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
		foreach( $indexes as $key => $index ) {
			if ( false !== strpos( $where_sql, $index ) ) {
				$the_index = $index;
				break; // Take the first one we find
			}
		}

		if ( !empty( $the_index ) ) {
			$index_hint_sql = "USE INDEX ({$the_index})";
		} else {
			$index_hint_sql = '';
		}

		if ( !empty( $per_page ) && !empty( $page ) ) {

			// Make sure page values are absolute integers
			$page     = absint( $page     );
			$per_page = absint( $per_page );

			$pag_sql    = $wpdb->prepare( "LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page );
lechuck's avatar
lechuck committed
245
			$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 ) );
root's avatar
root committed
246
		} else {
lechuck's avatar
lechuck committed
247
			$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 ) );
root's avatar
root committed
248
249
		}

lechuck's avatar
lechuck committed
250
		$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 );
root's avatar
root committed
251

root's avatar
root committed
252
		$total_activities = $wpdb->get_var( $total_activities_sql );
root's avatar
root committed
253

root's avatar
root committed
254
		// Get the fullnames of users so we don't have to query in the loop
lechuck's avatar
lechuck committed
255
256
257
		if ( bp_is_active( 'xprofile' ) && !empty( $activities ) ) {
			$activity_user_ids = wp_list_pluck( $activities, 'user_id' );
			$activity_user_ids = implode( ',', wp_parse_id_list( $activity_user_ids ) );
root's avatar
root committed
258

lechuck's avatar
lechuck committed
259
			if ( !empty( $activity_user_ids ) ) {
lechuck's avatar
lechuck committed
260
261
				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})" ) ) {
					foreach ( (array) $names as $name )
root's avatar
root committed
262
263
						$tmp_names[$name->user_id] = $name->user_fullname;

lechuck's avatar
lechuck committed
264
					foreach ( (array) $activities as $i => $activity ) {
root's avatar
root committed
265
266
267
268
269
270
271
272
273
						if ( !empty( $tmp_names[$activity->user_id] ) )
							$activities[$i]->user_fullname = $tmp_names[$activity->user_id];
					}

					unset( $names );
					unset( $tmp_names );
				}
			}
		}
lechuck's avatar
lechuck committed
274

lechuck's avatar
lechuck committed
275
276
277
278
279
		// Get activity meta
		$activity_ids = array();
		foreach ( (array) $activities as $activity ) {
			$activity_ids[] = $activity->id;
		}
lechuck's avatar
lechuck committed
280

lechuck's avatar
lechuck committed
281
282
283
		if ( !empty( $activity_ids ) ) {
			bp_activity_update_meta_cache( $activity_ids );
		}
root's avatar
root committed
284
285

		if ( $activities && $display_comments )
lechuck's avatar
lechuck committed
286
			$activities = BP_Activity_Activity::append_comments( $activities, $spam );
root's avatar
root committed
287

root's avatar
root committed
288
		// If $max is set, only return up to the max results
root's avatar
root committed
289
		if ( !empty( $max ) ) {
lechuck's avatar
lechuck committed
290
			if ( (int) $total_activities > (int) $max )
root's avatar
root committed
291
292
293
				$total_activities = $max;
		}

lechuck's avatar
lechuck committed
294
		return array( 'activities' => $activities, 'total' => (int) $total_activities );
root's avatar
root committed
295
296
	}

lechuck's avatar
lechuck committed
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
	/**
	 * Get the SQL for the 'meta_query' param in BP_Activity_Activity::get()
	 *
	 * We use WP_Meta_Query to do the heavy lifting of parsing the
	 * meta_query array and creating the necessary SQL clauses. However,
	 * since BP_Activity_Activity::get() builds its SQL differently than
	 * WP_Query, we have to alter the return value (stripping the leading
	 * AND keyword from the 'where' clause).
	 *
	 * @since BuddyPress (1.8)
	 *
	 * @param array $meta_query An array of meta_query filters. See the
	 *   documentation for WP_Meta_Query for details.
	 * @return array $sql_array 'join' and 'where' clauses
	 */
	public static function get_meta_query_sql( $meta_query = array() ) {
		global $wpdb;

		$sql_array = array(
			'join'  => '',
			'where' => '',
		);

		if ( ! empty( $meta_query ) ) {
			$activity_meta_query = new WP_Meta_Query( $meta_query );

			// WP_Meta_Query expects the table name at
			// $wpdb->activitymeta
			$wpdb->activitymeta = buddypress()->activity->table_name_meta;

			$meta_sql = $activity_meta_query->get_sql( 'activity', 'a', 'id' );

			// Strip the leading AND - BP handles it in get()
			$sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] );
			$sql_array['join']  = $meta_sql['join'];
		}

		return $sql_array;
	}

root's avatar
root committed
337
338
	/**
	 * In BuddyPress 1.2.x, this was used to retrieve specific activity stream items (for example, on an activity's permalink page).
lechuck's avatar
lechuck committed
339
	 * As of 1.5.x, use BP_Activity_Activity::get() with an 'in' parameter instead.
root's avatar
root committed
340
341
	 *
	 * @deprecated 1.5
lechuck's avatar
lechuck committed
342
	 * @deprecated Use BP_Activity_Activity::get() with an 'in' parameter instead.
root's avatar
root committed
343
344
345
346
347
348
349
	 * @param mixed $activity_ids Array or comma-separated string of activity IDs to retrieve
	 * @param int $max Maximum number of results to return. (Optional; default is no maximum)
	 * @param int $page The set of results that the user is viewing. Used in pagination. (Optional; default is 1)
	 * @param int $per_page Specifies how many results per page. Used in pagination. (Optional; default is 25)
	 * @param string MySQL column sort; ASC or DESC. (Optional; default is DESC)
	 * @param bool $display_comments Retrieve an activity item's associated comments or not. (Optional; default is false)
	 * @return array
lechuck's avatar
lechuck committed
350
	 * @since BuddyPress (1.2)
root's avatar
root committed
351
	 */
root's avatar
root committed
352
	function get_specific( $activity_ids, $max = false, $page = 1, $per_page = 25, $sort = 'DESC', $display_comments = false ) {
lechuck's avatar
lechuck committed
353
		_deprecated_function( __FUNCTION__, '1.5', 'Use BP_Activity_Activity::get() with the "in" parameter instead.' );
root's avatar
root committed
354
		return BP_Activity_Activity::get( $max, $page, $per_page, $sort, false, false, $display_comments, false, false, $activity_ids );
root's avatar
root committed
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
	}

	function get_id( $user_id, $component, $type, $item_id, $secondary_item_id, $action, $content, $date_recorded ) {
		global $bp, $wpdb;

		$where_args = false;

		if ( !empty( $user_id ) )
			$where_args[] = $wpdb->prepare( "user_id = %d", $user_id );

		if ( !empty( $component ) )
			$where_args[] = $wpdb->prepare( "component = %s", $component );

		if ( !empty( $type ) )
			$where_args[] = $wpdb->prepare( "type = %s", $type );

		if ( !empty( $item_id ) )
lechuck's avatar
lechuck committed
372
			$where_args[] = $wpdb->prepare( "item_id = %d", $item_id );
root's avatar
root committed
373
374

		if ( !empty( $secondary_item_id ) )
lechuck's avatar
lechuck committed
375
			$where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id );
root's avatar
root committed
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397

		if ( !empty( $action ) )
			$where_args[] = $wpdb->prepare( "action = %s", $action );

		if ( !empty( $content ) )
			$where_args[] = $wpdb->prepare( "content = %s", $content );

		if ( !empty( $date_recorded ) )
			$where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded );

		if ( !empty( $where_args ) )
			$where_sql = 'WHERE ' . join( ' AND ', $where_args );
		else
			return false;

		return $wpdb->get_var( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" );
	}

	function delete( $args ) {
		global $wpdb, $bp;

		$defaults = array(
root's avatar
root committed
398
399
400
401
402
403
404
405
			'id'                => false,
			'action'            => false,
			'content'           => false,
			'component'         => false,
			'type'              => false,
			'primary_link'      => false,
			'user_id'           => false,
			'item_id'           => false,
root's avatar
root committed
406
			'secondary_item_id' => false,
root's avatar
root committed
407
408
			'date_recorded'     => false,
			'hide_sitewide'     => false
root's avatar
root committed
409
		);
root's avatar
root committed
410
411
		$params = wp_parse_args( $args, $defaults );
		extract( $params );
root's avatar
root committed
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436

		$where_args = false;

		if ( !empty( $id ) )
			$where_args[] = $wpdb->prepare( "id = %d", $id );

		if ( !empty( $user_id ) )
			$where_args[] = $wpdb->prepare( "user_id = %d", $user_id );

		if ( !empty( $action ) )
			$where_args[] = $wpdb->prepare( "action = %s", $action );

		if ( !empty( $content ) )
			$where_args[] = $wpdb->prepare( "content = %s", $content );

		if ( !empty( $component ) )
			$where_args[] = $wpdb->prepare( "component = %s", $component );

		if ( !empty( $type ) )
			$where_args[] = $wpdb->prepare( "type = %s", $type );

		if ( !empty( $primary_link ) )
			$where_args[] = $wpdb->prepare( "primary_link = %s", $primary_link );

		if ( !empty( $item_id ) )
lechuck's avatar
lechuck committed
437
			$where_args[] = $wpdb->prepare( "item_id = %d", $item_id );
root's avatar
root committed
438
439

		if ( !empty( $secondary_item_id ) )
lechuck's avatar
lechuck committed
440
			$where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id );
root's avatar
root committed
441
442
443
444
445
446
447
448
449
450
451
452

		if ( !empty( $date_recorded ) )
			$where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded );

		if ( !empty( $hide_sitewide ) )
			$where_args[] = $wpdb->prepare( "hide_sitewide = %d", $hide_sitewide );

		if ( !empty( $where_args ) )
			$where_sql = 'WHERE ' . join( ' AND ', $where_args );
		else
			return false;

root's avatar
root committed
453
		// Fetch the activity IDs so we can delete any comments for this activity item
lechuck's avatar
lechuck committed
454
		$activity_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" );
root's avatar
root committed
455

lechuck's avatar
lechuck committed
456
		if ( !$wpdb->query( "DELETE FROM {$bp->activity->table_name} {$where_sql}" ) )
root's avatar
root committed
457
458
459
460
461
462
463
464
465
466
467
468
			return false;

		if ( $activity_ids ) {
			BP_Activity_Activity::delete_activity_item_comments( $activity_ids );
			BP_Activity_Activity::delete_activity_meta_entries( $activity_ids );

			return $activity_ids;
		}

		return $activity_ids;
	}

lechuck's avatar
lechuck committed
469
	function delete_activity_item_comments( $activity_ids = array() ) {
root's avatar
root committed
470
471
		global $bp, $wpdb;

lechuck's avatar
lechuck committed
472
		$activity_ids = implode( ',', wp_parse_id_list( $activity_ids ) );
root's avatar
root committed
473

lechuck's avatar
lechuck committed
474
		return $wpdb->query( "DELETE FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" );
root's avatar
root committed
475
476
	}

lechuck's avatar
lechuck committed
477
	function delete_activity_meta_entries( $activity_ids = array() ) {
root's avatar
root committed
478
479
		global $bp, $wpdb;

lechuck's avatar
lechuck committed
480
		$activity_ids = implode( ',', wp_parse_id_list( $activity_ids ) );
root's avatar
root committed
481

lechuck's avatar
lechuck committed
482
483
484
485
		foreach ( (array) $activity_ids as $activity_id ) {
			bp_activity_clear_meta_cache_for_activity( $activity_id );
		}

lechuck's avatar
lechuck committed
486
		return $wpdb->query( "DELETE FROM {$bp->activity->table_name_meta} WHERE activity_id IN ({$activity_ids})" );
root's avatar
root committed
487
488
	}

lechuck's avatar
lechuck committed
489
490
491
492
493
494
495
	/**
	 * Append activity comments to their associated activity items
	 *
	 * @global wpdb $wpdb WordPress database object
	 * @param array $activities
	 * @param bool $spam Optional; 'ham_only' (default), 'spam_only' or 'all'.
	 * @return array The updated activities with nested comments
lechuck's avatar
lechuck committed
496
	 * @since BuddyPress (1.2)
lechuck's avatar
lechuck committed
497
498
499
	 */
	function append_comments( $activities, $spam = 'ham_only' ) {
		global $wpdb;
root's avatar
root committed
500

root's avatar
root committed
501
502
		$activity_comments = array();

lechuck's avatar
lechuck committed
503
		// Now fetch the activity comments and parse them into the correct position in the activities array.
lechuck's avatar
lechuck committed
504
		foreach( (array) $activities as $activity ) {
lechuck's avatar
lechuck committed
505
506
			$top_level_parent_id = 'activity_comment' == $activity->type ? $activity->item_id : 0;
			$activity_comments[$activity->id] = BP_Activity_Activity::get_activity_comments( $activity->id, $activity->mptt_left, $activity->mptt_right, $spam, $top_level_parent_id );
root's avatar
root committed
507
508
		}

lechuck's avatar
lechuck committed
509
		// Merge the comments with the activity items
lechuck's avatar
lechuck committed
510
		foreach( (array) $activities as $key => $activity )
root's avatar
root committed
511
512
			if ( isset( $activity_comments[$activity->id] ) )
				$activities[$key]->children = $activity_comments[$activity->id];
root's avatar
root committed
513
514
515
516

		return $activities;
	}

lechuck's avatar
lechuck committed
517
518
519
520
521
522
523
524
525
	/**
	 * Get activity comments that are associated with a specific activity ID
	 *
	 * @global BuddyPress $bp The one true BuddyPress instance
	 * @global wpdb $wpdb WordPress database object
	 * @param int $activity_id Activity ID to fetch comments for
	 * @param int $left Left-most node boundary
	 * @param into $right Right-most node boundary
	 * @param bool $spam Optional; 'ham_only' (default), 'spam_only' or 'all'.
lechuck's avatar
lechuck committed
526
	 * @param int $top_level_parent_id The id of the root-level parent activity item
lechuck's avatar
lechuck committed
527
	 * @return array The updated activities with nested comments
lechuck's avatar
lechuck committed
528
	 * @since BuddyPress (1.2)
lechuck's avatar
lechuck committed
529
	 */
lechuck's avatar
lechuck committed
530
	function get_activity_comments( $activity_id, $left, $right, $spam = 'ham_only', $top_level_parent_id = 0 ) {
root's avatar
root committed
531
532
		global $wpdb, $bp;

lechuck's avatar
lechuck committed
533
534
535
536
		if ( empty( $top_level_parent_id ) ) {
			$top_level_parent_id = $activity_id;
		}

root's avatar
root committed
537
		if ( !$comments = wp_cache_get( 'bp_activity_comments_' . $activity_id ) ) {
lechuck's avatar
lechuck committed
538

root's avatar
root committed
539
540
			// Select the user's fullname with the query
			if ( bp_is_active( 'xprofile' ) ) {
root's avatar
root committed
541
542
543
				$fullname_select = ", pd.value as user_fullname";
				$fullname_from = ", {$bp->profile->table_name_data} pd ";
				$fullname_where = "AND pd.user_id = a.user_id AND pd.field_id = 1";
root's avatar
root committed
544
545
546
547

			// Prevent debug errors
			} else {
				$fullname_select = $fullname_from = $fullname_where = '';
root's avatar
root committed
548
549
			}

lechuck's avatar
lechuck committed
550
			// Don't retrieve activity comments marked as spam
lechuck's avatar
lechuck committed
551
			if ( 'ham_only' == $spam ) {
lechuck's avatar
lechuck committed
552
				$spam_sql = 'AND a.is_spam = 0';
lechuck's avatar
lechuck committed
553
			} elseif ( 'spam_only' == $spam ) {
lechuck's avatar
lechuck committed
554
				$spam_sql = 'AND a.is_spam = 1';
lechuck's avatar
lechuck committed
555
			} else {
lechuck's avatar
lechuck committed
556
				$spam_sql = '';
lechuck's avatar
lechuck committed
557
			}
lechuck's avatar
lechuck committed
558

lechuck's avatar
lechuck committed
559
560
			// The mptt BETWEEN clause allows us to limit returned descendants to the right part of the tree
			$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 );
lechuck's avatar
lechuck committed
561

root's avatar
root committed
562
			// Retrieve all descendants of the $root node
lechuck's avatar
lechuck committed
563
			$descendants = $wpdb->get_results( $sql );
lechuck's avatar
lechuck committed
564
			$ref         = array();
root's avatar
root committed
565

root's avatar
root committed
566
			// Loop descendants and build an assoc array
lechuck's avatar
lechuck committed
567
			foreach ( (array) $descendants as $d ) {
root's avatar
root committed
568
569
570
571
572
573
574
575
576
577
578
579
				$d->children = array();

				// If we have a reference on the parent
				if ( isset( $ref[ $d->secondary_item_id ] ) ) {
					$ref[ $d->secondary_item_id ]->children[ $d->id ] = $d;
					$ref[ $d->id ] =& $ref[ $d->secondary_item_id ]->children[ $d->id ];

				// If we don't have a reference on the parent, put in the root level
				} else {
					$comments[ $d->id ] = $d;
					$ref[ $d->id ] =& $comments[ $d->id ];
				}
root's avatar
root committed
580
581
582
583
584
585
586
587
588
589
			}
			wp_cache_set( 'bp_activity_comments_' . $activity_id, $comments, 'bp' );
		}

		return $comments;
	}

	function rebuild_activity_comment_tree( $parent_id, $left = 1 ) {
		global $wpdb, $bp;

root's avatar
root committed
590
		// The right value of this node is the left value + 1
root's avatar
root committed
591
592
		$right = $left + 1;

root's avatar
root committed
593
		// Get all descendants of this node
root's avatar
root committed
594
595
		$descendants = BP_Activity_Activity::get_child_comments( $parent_id );

root's avatar
root committed
596
		// Loop the descendants and recalculate the left and right values
lechuck's avatar
lechuck committed
597
		foreach ( (array) $descendants as $descendant )
root's avatar
root committed
598
599
			$right = BP_Activity_Activity::rebuild_activity_comment_tree( $descendant->id, $right );

root's avatar
root committed
600
601
		// We've got the left value, and now that we've processed the children
		// of this node we also know the right value
root's avatar
root committed
602
603
604
605
606
		if ( 1 == $left )
			$wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE id = %d", $left, $right, $parent_id ) );
		else
			$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 ) );

root's avatar
root committed
607
		// Return the right value of this node + 1
root's avatar
root committed
608
609
610
611
612
613
614
615
616
		return $right + 1;
	}

	function get_child_comments( $parent_id ) {
		global $bp, $wpdb;

		return $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND secondary_item_id = %d", $parent_id ) );
	}

lechuck's avatar
lechuck committed
617
618
619
620
621
	/**
	 * Fetch a list of all components that have activity items recorded
	 *
	 * @return array
	 */
root's avatar
root committed
622
623
	function get_recorded_components() {
		global $wpdb, $bp;
lechuck's avatar
lechuck committed
624
		return $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} ORDER BY component ASC" );
root's avatar
root committed
625
626
627
	}

	function get_sitewide_items_for_feed( $limit = 35 ) {
lechuck's avatar
lechuck committed
628
		global $bp;
root's avatar
root committed
629

lechuck's avatar
lechuck committed
630
631
		$activities    = bp_activity_get_sitewide( array( 'max' => $limit ) );
		$activity_feed = array();
root's avatar
root committed
632

root's avatar
root committed
633
		for ( $i = 0, $count = count( $activities ); $i < $count; ++$i ) {
lechuck's avatar
lechuck committed
634
635
636
				$title                            = explode( '<span', $activities[$i]['content'] );
				$activity_feed[$i]['title']       = trim( strip_tags( $title[0] ) );
				$activity_feed[$i]['link']        = $activities[$i]['primary_link'];
root's avatar
root committed
637
				$activity_feed[$i]['description'] = @sprintf( $activities[$i]['content'], '' );
lechuck's avatar
lechuck committed
638
				$activity_feed[$i]['pubdate']     = $activities[$i]['date_recorded'];
root's avatar
root committed
639
640
641
642
643
		}

		return $activity_feed;
	}

root's avatar
root committed
644
	function get_in_operator_sql( $field, $items ) {
root's avatar
root committed
645
646
		global $wpdb;

root's avatar
root committed
647
648
		// split items at the comma
		$items_dirty = explode( ',', $items );
root's avatar
root committed
649

root's avatar
root committed
650
651
		// array of prepared integers or quoted strings
		$items_prepared = array();
root's avatar
root committed
652

root's avatar
root committed
653
654
655
656
657
658
		// clean up and format each item
		foreach ( $items_dirty as $item ) {
			// clean up the string
			$item = trim( $item );
			// pass everything through prepare for security and to safely quote strings
			$items_prepared[] = ( is_numeric( $item ) ) ? $wpdb->prepare( '%d', $item ) : $wpdb->prepare( '%s', $item );
root's avatar
root committed
659
660
		}

root's avatar
root committed
661
662
663
664
665
666
		// build IN operator sql syntax
		if ( count( $items_prepared ) )
			return sprintf( '%s IN ( %s )', trim( $field ), implode( ',', $items_prepared ) );
		else
			return false;
	}
root's avatar
root committed
667

root's avatar
root committed
668
	function get_filter_sql( $filter_array ) {
lechuck's avatar
lechuck committed
669
670

		$filter_sql = array();
root's avatar
root committed
671

root's avatar
root committed
672
673
674
675
676
		if ( !empty( $filter_array['user_id'] ) ) {
			$user_sql = BP_Activity_Activity::get_in_operator_sql( 'a.user_id', $filter_array['user_id'] );
			if ( !empty( $user_sql ) )
				$filter_sql[] = $user_sql;
		}
root's avatar
root committed
677

root's avatar
root committed
678
679
680
681
682
		if ( !empty( $filter_array['object'] ) ) {
			$object_sql = BP_Activity_Activity::get_in_operator_sql( 'a.component', $filter_array['object'] );
			if ( !empty( $object_sql ) )
				$filter_sql[] = $object_sql;
		}
root's avatar
root committed
683

root's avatar
root committed
684
685
686
687
		if ( !empty( $filter_array['action'] ) ) {
			$action_sql = BP_Activity_Activity::get_in_operator_sql( 'a.type', $filter_array['action'] );
			if ( !empty( $action_sql ) )
				$filter_sql[] = $action_sql;
root's avatar
root committed
688
689
690
		}

		if ( !empty( $filter_array['primary_id'] ) ) {
root's avatar
root committed
691
692
693
			$pid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.item_id', $filter_array['primary_id'] );
			if ( !empty( $pid_sql ) )
				$filter_sql[] = $pid_sql;
root's avatar
root committed
694
695
696
		}

		if ( !empty( $filter_array['secondary_id'] ) ) {
root's avatar
root committed
697
698
699
			$sid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.secondary_item_id', $filter_array['secondary_id'] );
			if ( !empty( $sid_sql ) )
				$filter_sql[] = $sid_sql;
root's avatar
root committed
700
701
		}

lechuck's avatar
lechuck committed
702
		if ( empty( $filter_sql ) )
root's avatar
root committed
703
704
705
706
707
708
709
710
			return false;

		return join( ' AND ', $filter_sql );
	}

	function get_last_updated() {
		global $bp, $wpdb;

lechuck's avatar
lechuck committed
711
		return $wpdb->get_var( "SELECT date_recorded FROM {$bp->activity->table_name} ORDER BY date_recorded DESC LIMIT 1" );
root's avatar
root committed
712
713
714
	}

	function total_favorite_count( $user_id ) {
root's avatar
root committed
715
		if ( !$favorite_activity_entries = bp_get_user_meta( $user_id, 'bp_favorite_activities', true ) )
root's avatar
root committed
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
			return 0;

		return count( maybe_unserialize( $favorite_activity_entries ) );
	}

	function check_exists_by_content( $content ) {
		global $wpdb, $bp;

		return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE content = %s", $content ) );
	}

	function hide_all_for_user( $user_id ) {
		global $wpdb, $bp;

		return $wpdb->get_var( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET hide_sitewide = 1 WHERE user_id = %d", $user_id ) );
	}
}
lechuck's avatar
lechuck committed
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000

/**
 * Create a RSS feed using the activity component.
 *
 * You should only construct a new feed when you've validated that you're on
 * the appropriate screen.
 *
 * See {@link bp_activity_action_sitewide_feed()} as an example.
 *
 * Accepted parameters:
 *   id	              - internal id for the feed; should be alphanumeric only
 *                      (required)
 *   title            - RSS feed title
 *   link             - Relevant link for the RSS feed
 *   description      - RSS feed description
 *   ttl              - Time-to-live (see inline doc in constructor)
 *   update_period    - Part of the syndication module (see inline doc in
 *                      constructor for more info)
 *   update_frequency - Part of the syndication module (see inline doc in
 *                      constructor for more info)
 *   max              - Number of feed items to display
 *   activity_args    - Arguments passed to {@link bp_has_activities()}
 *
 * @since BuddyPress (1.8)
 */
class BP_Activity_Feed {
	/**
	 * Holds our custom class properties.
	 *
	 * These variables are stored in a protected array that is magically
	 * updated using PHP 5.2+ methods.
	 *
	 * @see BP_Feed::__construct() This is where $data is added
	 * @var array
	 */
	protected $data;

	/**
	 * Magic method for checking the existence of a certain data variable.
	 *
	 * @param string $key
	 */
	public function __isset( $key ) { return isset( $this->data[$key] ); }

	/**
	 * Magic method for getting a certain data variable.
	 *
	 * @param string $key
	 */
	public function __get( $key ) { return isset( $this->data[$key] ) ? $this->data[$key] : null; }

	/**
	 * Constructor.
	 *
	 * @param array $args Optional
	 */
	public function __construct( $args = array() ) {
		// If feeds are disabled, stop now!
		if ( false === (bool) apply_filters( 'bp_activity_enable_feeds', true ) ) {
			global $wp_query;

			// set feed flag to false
			$wp_query->is_feed = false;

			return false;
		}

		// Setup data
		$this->data = wp_parse_args( $args, array(
			// Internal identifier for the RSS feed - should be alphanumeric only
			'id'               => '',

			// RSS title - should be plain-text
			'title'            => '',

			// relevant link for the RSS feed
			'link'             => '',

			// RSS description - should be plain-text
			'description'      => '',

			// Time-to-live - number of minutes to cache the data before an aggregator
			// requests it again.  This is only acknowledged if the RSS client supports it
			//
			// See: http://www.rssboard.org/rss-profile#element-channel-ttl
			//      http://www.kbcafe.com/rss/rssfeedstate.html#ttl
			'ttl'              => '30',

			// Syndication module - similar to ttl, but not really supported by RSS
			// clients
			//
			// See: http://web.resource.org/rss/1.0/modules/syndication/#description
			//      http://www.kbcafe.com/rss/rssfeedstate.html#syndicationmodule
			'update_period'    => 'hourly',
			'update_frequency' => 2,

			// Number of items to display
			'max'              => 50,

			// Activity arguments passed to bp_has_activities()
			'activity_args'    => array()
		) );

		// Plugins can use this filter to modify the feed before it is setup
		do_action_ref_array( 'bp_activity_feed_prefetch', array( &$this ) );

		// Setup class properties
		$this->setup_properties();

		// Check if id is valid
		if ( empty( $this->id ) ) {
			_doing_it_wrong( 'BP_Activity_Feed', __( "RSS feed 'id' must be defined", 'buddypress' ), 'BP 1.8' );
			return false;
		}

		// Plugins can use this filter to modify the feed after it's setup
		do_action_ref_array( 'bp_activity_feed_postfetch', array( &$this ) );

		// Setup feed hooks
		$this->setup_hooks();

		// Output the feed
		$this->output();

		// Kill the rest of the output
		die();
	}

	/** SETUP ****************************************************************/

	/**
	 * Setup and validate the class properties.
	 */
	protected function setup_properties() {
		$this->id               = sanitize_title( $this->id );
		$this->title            = strip_tags( $this->title );
		$this->link             = esc_url_raw( $this->link );
		$this->description      = strip_tags( $this->description );
		$this->ttl              = (int) $this->ttl;
		$this->update_period    = strip_tags( $this->update_period );
		$this->update_frequency = (int) $this->update_frequency;

		$this->activity_args    = wp_parse_args( $this->activity_args, array(
			'max'              => $this->max,
			'display_comments' => 'stream'
		) );

	}

	/**
	 * Setup some hooks that are used in the feed.
	 *
	 * Currently, these hooks are used to maintain backwards compatibility with
	 * the RSS feeds previous to BP 1.8.
	 */
	protected function setup_hooks() {
		add_action( 'bp_activity_feed_rss_attributes',   array( $this, 'backpat_rss_attributes' ) );
		add_action( 'bp_activity_feed_channel_elements', array( $this, 'backpat_channel_elements' ) );
		add_action( 'bp_activity_feed_item_elements',    array( $this, 'backpat_item_elements' ) );
	}

	/** BACKPAT HOOKS ********************************************************/

	public function backpat_rss_attributes() {
		do_action( 'bp_activity_' . $this->id . '_feed' );
	}

	public function backpat_channel_elements() {
		do_action( 'bp_activity_' . $this->id . '_feed_head' );
	}

	public function backpat_item_elements() {
		switch ( $this->id ) {

			// sitewide and friends feeds use the 'personal' hook
			case 'sitewide' :
			case 'friends' :
				$id = 'personal';

				break;

			default :
				$id = $this->id;

				break;
		}

		do_action( 'bp_activity_' . $id . '_feed_item' );
	}

	/** HELPERS **************************************************************/

	/**
	 * Output the feed's item content.
	 */
	protected function feed_content() {
		bp_activity_content_body();

		switch ( $this->id ) {

			// also output parent activity item if we're on a specific feed
			case 'favorites' :
			case 'friends' :
			case 'mentions' :
			case 'personal' :

				if ( 'activity_comment' == bp_get_activity_action_name() ) :
			?>
				<strong><?php _e( 'In reply to', 'buddypress' ) ?></strong> -
				<?php bp_activity_parent_content() ?>
			<?php
				endif;

				break;
		}
	}

	/** OUTPUT ***************************************************************/

	/**
	 * Output the RSS feed.
	 */
	protected function output() {
		// set up some additional headers if not on a directory page
		// this is done b/c BP uses pseudo-pages
		if ( ! bp_is_directory() ) {
			global $wp_query;

			$wp_query->is_404 = false;
			status_header( 200 );
		}

		header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true );
		echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?'.'>';
	?>

<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	<?php do_action( 'bp_activity_feed_rss_attributes' ); ?>
>

<channel>
	<title><?php echo $this->title; ?></title>
	<link><?php echo $this->link; ?></link>
	<atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
	<description><?php echo $this->description ?></description>
	<lastBuildDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false ); ?></lastBuildDate>
	<generator>http://buddypress.org/?v=<?php bp_version(); ?></generator>
	<language><?php bloginfo_rss( 'language' ); ?></language>
	<ttl><?php echo $this->ttl; ?></ttl>
	<sy:updatePeriod><?php echo $this->update_period; ?></sy:updatePeriod>
 	<sy:updateFrequency><?php echo $this->update_frequency; ?></sy:updateFrequency>
	<?php do_action( 'bp_activity_feed_channel_elements' ); ?>

	<?php if ( bp_has_activities( $this->activity_args ) ) : ?>
		<?php while ( bp_activities() ) : bp_the_activity(); ?>
			<item>
				<guid isPermaLink="false"><?php bp_activity_feed_item_guid(); ?></guid>
				<title><?php echo stripslashes( bp_get_activity_feed_item_title() ); ?></title>
				<link><?php bp_activity_thread_permalink() ?></link>
				<pubDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_get_activity_feed_item_date(), false ); ?></pubDate>

				<?php if ( bp_get_activity_feed_item_description() ) : ?>
					<content:encoded><![CDATA[<?php $this->feed_content(); ?>]]></content:encoded>
				<?php endif; ?>
For faster browsing, not all history is shown. View entire blame