/bp-activity/classes/class-bp-activity-activity.php

  1. <?php 
  2. /** 
  3. * BuddyPress Activity Classes 
  4. * 
  5. * @package BuddyPress 
  6. * @subpackage Activity 
  7. * @since 1.0.0 
  8. */ 
  9.  
  10. // Exit if accessed directly. 
  11. defined( 'ABSPATH' ) || exit; 
  12.  
  13. /** 
  14. * Database interaction class for the BuddyPress activity component. 
  15. * Instance methods are available for creating/editing an activity,  
  16. * static methods for querying activities. 
  17. * 
  18. * @since 1.0.0 
  19. */ 
  20. class BP_Activity_Activity { 
  21.  
  22. /** Properties ************************************************************/ 
  23.  
  24. /** 
  25. * ID of the activity item. 
  26. * 
  27. * @since 1.0.0 
  28. * @var int 
  29. */ 
  30. var $id; 
  31.  
  32. /** 
  33. * ID of the associated item. 
  34. * 
  35. * @since 1.0.0 
  36. * @var int 
  37. */ 
  38. var $item_id; 
  39.  
  40. /** 
  41. * ID of the associated secondary item. 
  42. * 
  43. * @since 1.0.0 
  44. * @var int 
  45. */ 
  46. var $secondary_item_id; 
  47.  
  48. /** 
  49. * ID of user associated with the activity item. 
  50. * 
  51. * @since 1.0.0 
  52. * @var int 
  53. */ 
  54. var $user_id; 
  55.  
  56. /** 
  57. * The primary URL for the activity in RSS feeds. 
  58. * 
  59. * @since 1.0.0 
  60. * @var string 
  61. */ 
  62. var $primary_link; 
  63.  
  64. /** 
  65. * BuddyPress component the activity item relates to. 
  66. * 
  67. * @since 1.2.0 
  68. * @var string 
  69. */ 
  70. var $component; 
  71.  
  72. /** 
  73. * Activity type, eg 'new_blog_post'. 
  74. * 
  75. * @since 1.2.0 
  76. * @var string 
  77. */ 
  78. var $type; 
  79.  
  80. /** 
  81. * Description of the activity, eg 'Alex updated his profile.'. 
  82. * 
  83. * @since 1.2.0 
  84. * @var string 
  85. */ 
  86. var $action; 
  87.  
  88. /** 
  89. * The content of the activity item. 
  90. * 
  91. * @since 1.2.0 
  92. * @var string 
  93. */ 
  94. var $content; 
  95.  
  96. /** 
  97. * The date the activity item was recorded, in 'Y-m-d h:i:s' format. 
  98. * 
  99. * @since 1.0.0 
  100. * @var string 
  101. */ 
  102. var $date_recorded; 
  103.  
  104. /** 
  105. * Whether the item should be hidden in sitewide streams. 
  106. * 
  107. * @since 1.1.0 
  108. * @var int 
  109. */ 
  110. var $hide_sitewide = 0; 
  111.  
  112. /** 
  113. * Node boundary start for activity or activity comment. 
  114. * 
  115. * @since 1.5.0 
  116. * @var int 
  117. */ 
  118. var $mptt_left; 
  119.  
  120. /** 
  121. * Node boundary end for activity or activity comment. 
  122. * 
  123. * @since 1.5.0 
  124. * @var int 
  125. */ 
  126. var $mptt_right; 
  127.  
  128. /** 
  129. * Whether this item is marked as spam. 
  130. * 
  131. * @since 1.6.0 
  132. * @var int 
  133. */ 
  134. var $is_spam; 
  135.  
  136. /** 
  137. * Error holder. 
  138. * 
  139. * @since 2.6.0 
  140. * 
  141. * @var WP_Error 
  142. */ 
  143. public $errors; 
  144.  
  145. /** 
  146. * Error type to return. Either 'bool' or 'wp_error'. 
  147. * 
  148. * @since 2.6.0 
  149. * 
  150. * @var string 
  151. */ 
  152. public $error_type = 'bool'; 
  153.  
  154. /** 
  155. * Constructor method. 
  156. * 
  157. * @since 1.5.0 
  158. * 
  159. * @param int|bool $id Optional. The ID of a specific activity item. 
  160. */ 
  161. public function __construct( $id = false ) { 
  162. // Instantiate errors object. 
  163. $this->errors = new WP_Error; 
  164.  
  165. if ( !empty( $id ) ) { 
  166. $this->id = (int) $id; 
  167. $this->populate(); 
  168.  
  169. /** 
  170. * Populate the object with data about the specific activity item. 
  171. * 
  172. * @since 1.0.0 
  173. */ 
  174. public function populate() { 
  175. global $wpdb; 
  176.  
  177. $row = wp_cache_get( $this->id, 'bp_activity' ); 
  178.  
  179. if ( false === $row ) { 
  180. $bp = buddypress(); 
  181. $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->activity->table_name} WHERE id = %d", $this->id ) ); 
  182.  
  183. wp_cache_set( $this->id, $row, 'bp_activity' ); 
  184.  
  185. if ( empty( $row ) ) { 
  186. $this->id = 0; 
  187. return; 
  188.  
  189. $this->id = (int) $row->id; 
  190. $this->item_id = (int) $row->item_id; 
  191. $this->secondary_item_id = (int) $row->secondary_item_id; 
  192. $this->user_id = (int) $row->user_id; 
  193. $this->primary_link = $row->primary_link; 
  194. $this->component = $row->component; 
  195. $this->type = $row->type; 
  196. $this->action = $row->action; 
  197. $this->content = $row->content; 
  198. $this->date_recorded = $row->date_recorded; 
  199. $this->hide_sitewide = (int) $row->hide_sitewide; 
  200. $this->mptt_left = (int) $row->mptt_left; 
  201. $this->mptt_right = (int) $row->mptt_right; 
  202. $this->is_spam = (int) $row->is_spam; 
  203.  
  204. // Generate dynamic 'action' when possible. 
  205. $action = bp_activity_generate_action_string( $this ); 
  206. if ( false !== $action ) { 
  207. $this->action = $action; 
  208.  
  209. // If no callback is available, use the literal string from 
  210. // the database row. 
  211. } elseif ( ! empty( $row->action ) ) { 
  212. $this->action = $row->action; 
  213.  
  214. // Provide a fallback to avoid PHP notices. 
  215. } else { 
  216. $this->action = ''; 
  217.  
  218. /** 
  219. * Save the activity item to the database. 
  220. * 
  221. * @since 1.0.0 
  222. * 
  223. * @return bool True on success. 
  224. */ 
  225. public function save() { 
  226. global $wpdb; 
  227.  
  228. $bp = buddypress(); 
  229.  
  230. $this->id = apply_filters_ref_array( 'bp_activity_id_before_save', array( $this->id, &$this ) ); 
  231. $this->item_id = apply_filters_ref_array( 'bp_activity_item_id_before_save', array( $this->item_id, &$this ) ); 
  232. $this->secondary_item_id = apply_filters_ref_array( 'bp_activity_secondary_item_id_before_save', array( $this->secondary_item_id, &$this ) ); 
  233. $this->user_id = apply_filters_ref_array( 'bp_activity_user_id_before_save', array( $this->user_id, &$this ) ); 
  234. $this->primary_link = apply_filters_ref_array( 'bp_activity_primary_link_before_save', array( $this->primary_link, &$this ) ); 
  235. $this->component = apply_filters_ref_array( 'bp_activity_component_before_save', array( $this->component, &$this ) ); 
  236. $this->type = apply_filters_ref_array( 'bp_activity_type_before_save', array( $this->type, &$this ) ); 
  237. $this->action = apply_filters_ref_array( 'bp_activity_action_before_save', array( $this->action, &$this ) ); 
  238. $this->content = apply_filters_ref_array( 'bp_activity_content_before_save', array( $this->content, &$this ) ); 
  239. $this->date_recorded = apply_filters_ref_array( 'bp_activity_date_recorded_before_save', array( $this->date_recorded, &$this ) ); 
  240. $this->hide_sitewide = apply_filters_ref_array( 'bp_activity_hide_sitewide_before_save', array( $this->hide_sitewide, &$this ) ); 
  241. $this->mptt_left = apply_filters_ref_array( 'bp_activity_mptt_left_before_save', array( $this->mptt_left, &$this ) ); 
  242. $this->mptt_right = apply_filters_ref_array( 'bp_activity_mptt_right_before_save', array( $this->mptt_right, &$this ) ); 
  243. $this->is_spam = apply_filters_ref_array( 'bp_activity_is_spam_before_save', array( $this->is_spam, &$this ) ); 
  244.  
  245. /** 
  246. * Fires before the current activity item gets saved. 
  247. * 
  248. * Please use this hook to filter the properties above. Each part will be passed in. 
  249. * 
  250. * @since 1.0.0 
  251. * 
  252. * @param BP_Activity_Activity $this Current instance of the activity item being saved. Passed by reference. 
  253. */ 
  254. do_action_ref_array( 'bp_activity_before_save', array( &$this ) ); 
  255.  
  256. if ( 'wp_error' === $this->error_type && $this->errors->get_error_code() ) { 
  257. return $this->errors; 
  258.  
  259. if ( empty( $this->component ) || empty( $this->type ) ) { 
  260. if ( 'bool' === $this->error_type ) { 
  261. return false; 
  262. } else { 
  263. if ( empty( $this->component ) ) { 
  264. $this->errors->add( 'bp_activity_missing_component' ); 
  265. } else { 
  266. $this->errors->add( 'bp_activity_missing_type' ); 
  267.  
  268. return $this->errors; 
  269.  
  270. if ( empty( $this->primary_link ) ) { 
  271. $this->primary_link = bp_loggedin_user_domain(); 
  272.  
  273. // If we have an existing ID, update the activity item, otherwise insert it. 
  274. if ( ! empty( $this->id ) ) { 
  275. $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 ); 
  276. } else { 
  277. $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 ); 
  278.  
  279. if ( false === $wpdb->query( $q ) ) { 
  280. return false; 
  281.  
  282. // If this is a new activity item, set the $id property. 
  283. if ( empty( $this->id ) ) { 
  284. $this->id = $wpdb->insert_id; 
  285.  
  286. // If an existing activity item, prevent any changes to the content generating new @mention notifications. 
  287. } else { 
  288. add_filter( 'bp_activity_at_name_do_notifications', '__return_false' ); 
  289.  
  290. /** 
  291. * Fires after an activity item has been saved to the database. 
  292. * 
  293. * @since 1.0.0 
  294. * 
  295. * @param BP_Activity_Activity $this Current instance of activity item being saved. Passed by reference. 
  296. */ 
  297. do_action_ref_array( 'bp_activity_after_save', array( &$this ) ); 
  298.  
  299. return true; 
  300.  
  301. /** Static Methods ***************************************************/ 
  302.  
  303. /** 
  304. * Get activity items, as specified by parameters. 
  305. * 
  306. * @since 1.2.0 
  307. * @since 2.4.0 Introduced the `$fields` parameter. 
  308. * 
  309. * @see BP_Activity_Activity::get_filter_sql() for a description of the 
  310. * 'filter' parameter. 
  311. * @see WP_Meta_Query::queries for a description of the 'meta_query' 
  312. * parameter format. 
  313. * 
  314. * @param array $args { 
  315. * An array of arguments. All items are optional. 
  316. * @type int $page Which page of results to fetch. Using page=1 without per_page will result 
  317. * in no pagination. Default: 1. 
  318. * @type int|bool $per_page Number of results per page. Default: 25. 
  319. * @type int|bool $max Maximum number of results to return. Default: false (unlimited). 
  320. * @type string $fields Activity fields to return. Pass 'ids' to get only the activity IDs. 
  321. * 'all' returns full activity objects. 
  322. * @type string $sort ASC or DESC. Default: 'DESC'. 
  323. * @type array $exclude Array of activity IDs to exclude. Default: false. 
  324. * @type array $in Array of ids to limit query by (IN). Default: false. 
  325. * @type array $meta_query Array of meta_query conditions. See WP_Meta_Query::queries. 
  326. * @type array $date_query Array of date_query conditions. See first parameter of 
  327. * WP_Date_Query::__construct(). 
  328. * @type array $filter_query Array of advanced query conditions. See BP_Activity_Query::__construct(). 
  329. * @type string|array $scope Pre-determined set of activity arguments. 
  330. * @type array $filter See BP_Activity_Activity::get_filter_sql(). 
  331. * @type string $search_terms Limit results by a search term. Default: false. 
  332. * @type bool $display_comments Whether to include activity comments. Default: false. 
  333. * @type bool $show_hidden Whether to show items marked hide_sitewide. Default: false. 
  334. * @type string $spam Spam status. Default: 'ham_only'. 
  335. * @type bool $update_meta_cache Whether to pre-fetch metadata for queried activity items. Default: true. 
  336. * @type string|bool $count_total If true, an additional DB query is run to count the total activity items 
  337. * for the query. Default: false. 
  338. * } 
  339. * @return array The array returned has two keys: 
  340. * - 'total' is the count of located activities 
  341. * - 'activities' is an array of the located activities 
  342. */ 
  343. public static function get( $args = array() ) { 
  344. global $wpdb; 
  345.  
  346. // Backward compatibility with old method of passing arguments. 
  347. if ( !is_array( $args ) || func_num_args() > 1 ) { 
  348. _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__ ) ); 
  349.  
  350. $old_args_keys = array( 
  351. 0 => 'max',  
  352. 1 => 'page',  
  353. 2 => 'per_page',  
  354. 3 => 'sort',  
  355. 4 => 'search_terms',  
  356. 5 => 'filter',  
  357. 6 => 'display_comments',  
  358. 7 => 'show_hidden',  
  359. 8 => 'exclude',  
  360. 9 => 'in',  
  361. 10 => 'spam' 
  362. ); 
  363.  
  364. $args = bp_core_parse_args_array( $old_args_keys, func_get_args() ); 
  365.  
  366. $bp = buddypress(); 
  367. $r = wp_parse_args( $args, array( 
  368. 'page' => 1, // The current page. 
  369. 'per_page' => 25, // Activity items per page. 
  370. 'max' => false, // Max number of items to return. 
  371. 'fields' => 'all', // Fields to include. 
  372. 'sort' => 'DESC', // ASC or DESC. 
  373. 'exclude' => false, // Array of ids to exclude. 
  374. 'in' => false, // Array of ids to limit query by (IN). 
  375. 'meta_query' => false, // Filter by activitymeta. 
  376. 'date_query' => false, // Filter by date. 
  377. 'filter_query' => false, // Advanced filtering - see BP_Activity_Query. 
  378. 'filter' => false, // See self::get_filter_sql(). 
  379. 'scope' => false, // Preset activity arguments. 
  380. 'search_terms' => false, // Terms to search by. 
  381. 'display_comments' => false, // Whether to include activity comments. 
  382. 'show_hidden' => false, // Show items marked hide_sitewide. 
  383. 'spam' => 'ham_only', // Spam status. 
  384. 'update_meta_cache' => true, // Whether or not to update meta cache. 
  385. 'count_total' => false, // Whether or not to use count_total. 
  386. ) ); 
  387.  
  388. // Select conditions. 
  389. $select_sql = "SELECT DISTINCT a.id"; 
  390.  
  391. $from_sql = " FROM {$bp->activity->table_name} a"; 
  392.  
  393. $join_sql = ''; 
  394.  
  395. // Where conditions. 
  396. $where_conditions = array(); 
  397.  
  398. // Excluded types. 
  399. $excluded_types = array(); 
  400.  
  401. // Scope takes precedence. 
  402. if ( ! empty( $r['scope'] ) ) { 
  403. $scope_query = self::get_scope_query_sql( $r['scope'], $r ); 
  404.  
  405. // Add our SQL conditions if matches were found. 
  406. if ( ! empty( $scope_query['sql'] ) ) { 
  407. $where_conditions['scope_query_sql'] = $scope_query['sql']; 
  408.  
  409. // Override some arguments if needed. 
  410. if ( ! empty( $scope_query['override'] ) ) { 
  411. $r = array_replace_recursive( $r, $scope_query['override'] ); 
  412.  
  413. // Advanced filtering. 
  414. } elseif ( ! empty( $r['filter_query'] ) ) { 
  415. $filter_query = new BP_Activity_Query( $r['filter_query'] ); 
  416. $sql = $filter_query->get_sql(); 
  417. if ( ! empty( $sql ) ) { 
  418. $where_conditions['filter_query_sql'] = $sql; 
  419.  
  420. // Regular filtering. 
  421. if ( $r['filter'] && $filter_sql = BP_Activity_Activity::get_filter_sql( $r['filter'] ) ) { 
  422. $where_conditions['filter_sql'] = $filter_sql; 
  423.  
  424. // Spam. 
  425. if ( 'ham_only' == $r['spam'] ) { 
  426. $where_conditions['spam_sql'] = 'a.is_spam = 0'; 
  427. } elseif ( 'spam_only' == $r['spam'] ) { 
  428. $where_conditions['spam_sql'] = 'a.is_spam = 1'; 
  429.  
  430. // Searching. 
  431. if ( $r['search_terms'] ) { 
  432. $search_terms_like = '%' . bp_esc_like( $r['search_terms'] ) . '%'; 
  433. $where_conditions['search_sql'] = $wpdb->prepare( 'a.content LIKE %s', $search_terms_like ); 
  434.  
  435. // Sorting. 
  436. $sort = $r['sort']; 
  437. if ( $sort != 'ASC' && $sort != 'DESC' ) { 
  438. $sort = 'DESC'; 
  439.  
  440. // Hide Hidden Items? 
  441. if ( ! $r['show_hidden'] ) { 
  442. $where_conditions['hidden_sql'] = "a.hide_sitewide = 0"; 
  443.  
  444. // Exclude specified items. 
  445. if ( ! empty( $r['exclude'] ) ) { 
  446. $exclude = implode( ', ', wp_parse_id_list( $r['exclude'] ) ); 
  447. $where_conditions['exclude'] = "a.id NOT IN ({$exclude})"; 
  448.  
  449. // The specific ids to which you want to limit the query. 
  450. if ( ! empty( $r['in'] ) ) { 
  451. $in = implode( ', ', wp_parse_id_list( $r['in'] ) ); 
  452. $where_conditions['in'] = "a.id IN ({$in})"; 
  453.  
  454. // Process meta_query into SQL. 
  455. $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] ); 
  456.  
  457. if ( ! empty( $meta_query_sql['join'] ) ) { 
  458. $join_sql .= $meta_query_sql['join']; 
  459.  
  460. if ( ! empty( $meta_query_sql['where'] ) ) { 
  461. $where_conditions[] = $meta_query_sql['where']; 
  462.  
  463. // Process date_query into SQL. 
  464. $date_query_sql = self::get_date_query_sql( $r['date_query'] ); 
  465.  
  466. if ( ! empty( $date_query_sql ) ) { 
  467. $where_conditions['date'] = $date_query_sql; 
  468.  
  469. // Alter the query based on whether we want to show activity item 
  470. // comments in the stream like normal comments or threaded below 
  471. // the activity. 
  472. if ( false === $r['display_comments'] || 'threaded' === $r['display_comments'] ) { 
  473. $excluded_types[] = 'activity_comment'; 
  474.  
  475. // Exclude 'last_activity' items unless the 'action' filter has 
  476. // been explicitly set. 
  477. if ( empty( $r['filter']['object'] ) ) { 
  478. $excluded_types[] = 'last_activity'; 
  479.  
  480. // Build the excluded type sql part. 
  481. if ( ! empty( $excluded_types ) ) { 
  482. $not_in = "'" . implode( "', '", esc_sql( $excluded_types ) ) . "'"; 
  483. $where_conditions['excluded_types'] = "a.type NOT IN ({$not_in})"; 
  484.  
  485. /** 
  486. * Filters the MySQL WHERE conditions for the Activity items get method. 
  487. * 
  488. * @since 1.9.0 
  489. * 
  490. * @param array $where_conditions Current conditions for MySQL WHERE statement. 
  491. * @param array $r Parsed arguments passed into method. 
  492. * @param string $select_sql Current SELECT MySQL statement at point of execution. 
  493. * @param string $from_sql Current FROM MySQL statement at point of execution. 
  494. * @param string $join_sql Current INNER JOIN MySQL statement at point of execution. 
  495. */ 
  496. $where_conditions = apply_filters( 'bp_activity_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql ); 
  497.  
  498. // Join the where conditions together. 
  499. $where_sql = 'WHERE ' . join( ' AND ', $where_conditions ); 
  500.  
  501. /** 
  502. * Filter the MySQL JOIN clause for the main activity query. 
  503. * 
  504. * @since 2.5.0 
  505. * 
  506. * @param string $join_sql JOIN clause. 
  507. * @param array $r Method parameters. 
  508. * @param string $select_sql Current SELECT MySQL statement. 
  509. * @param string $from_sql Current FROM MySQL statement. 
  510. * @param string $where_sql Current WHERE MySQL statement. 
  511. */ 
  512. $join_sql = apply_filters( 'bp_activity_get_join_sql', $join_sql, $r, $select_sql, $from_sql, $where_sql ); 
  513.  
  514. /** 
  515. * Filters the preferred order of indexes for activity item. 
  516. * 
  517. * @since 1.6.0 
  518. * 
  519. * @param array $value Array of indexes in preferred order. 
  520. */ 
  521. $indexes = apply_filters( 'bp_activity_preferred_index_order', array( 'user_id', 'item_id', 'secondary_item_id', 'date_recorded', 'component', 'type', 'hide_sitewide', 'is_spam' ) ); 
  522.  
  523. foreach( $indexes as $key => $index ) { 
  524. if ( false !== strpos( $where_sql, $index ) ) { 
  525. $the_index = $index; 
  526. break; // Take the first one we find. 
  527.  
  528. if ( !empty( $the_index ) ) { 
  529. $index_hint_sql = "USE INDEX ({$the_index})"; 
  530. } else { 
  531. $index_hint_sql = ''; 
  532.  
  533. // Sanitize page and per_page parameters. 
  534. $page = absint( $r['page'] ); 
  535. $per_page = absint( $r['per_page'] ); 
  536.  
  537. $retval = array( 
  538. 'activities' => null,  
  539. 'total' => null,  
  540. 'has_more_items' => null,  
  541. ); 
  542.  
  543. /** 
  544. * Filters if BuddyPress should use legacy query structure over current structure for version 2.0+. 
  545. * 
  546. * It is not recommended to use the legacy structure, but allowed to if needed. 
  547. * 
  548. * @since 2.0.0 
  549. * 
  550. * @param bool $value Whether to use legacy structure or not. 
  551. * @param BP_Activity_Activity $value Current method being called. 
  552. * @param array $r Parsed arguments passed into method. 
  553. */ 
  554. if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $r ) ) { 
  555.  
  556. // Legacy queries joined against the user table. 
  557. $select_sql = "SELECT DISTINCT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name"; 
  558. $from_sql = " FROM {$bp->activity->table_name} a LEFT JOIN {$wpdb->users} u ON a.user_id = u.ID"; 
  559.  
  560. if ( ! empty( $page ) && ! empty( $per_page ) ) { 
  561. $pag_sql = $wpdb->prepare( "LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page ); 
  562.  
  563. /** This filter is documented in bp-activity/bp-activity-classes.php */ 
  564. $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}, a.id {$sort} {$pag_sql}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ) ); 
  565. } else { 
  566. $pag_sql = ''; 
  567.  
  568. /** 
  569. * Filters the legacy MySQL query statement so plugins can alter before results are fetched. 
  570. * 
  571. * @since 1.5.0 
  572. * 
  573. * @param string $value Concatenated MySQL statement pieces to be query results with for legacy query. 
  574. * @param string $select_sql Final SELECT MySQL statement portion for legacy query. 
  575. * @param string $from_sql Final FROM MySQL statement portion for legacy query. 
  576. * @param string $where_sql Final WHERE MySQL statement portion for legacy query. 
  577. * @param string $sort Final sort direction for legacy query. 
  578. */ 
  579. $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}, a.id {$sort}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ) ); 
  580.  
  581. // Integer casting for legacy activity query. 
  582. foreach ( (array) $activities as $i => $ac ) { 
  583. $activities[ $i ]->id = (int) $ac->id; 
  584. $activities[ $i ]->item_id = (int) $ac->item_id; 
  585. $activities[ $i ]->secondary_item_id = (int) $ac->secondary_item_id; 
  586. $activities[ $i ]->user_id = (int) $ac->user_id; 
  587. $activities[ $i ]->hide_sitewide = (int) $ac->hide_sitewide; 
  588. $activities[ $i ]->mptt_left = (int) $ac->mptt_left; 
  589. $activities[ $i ]->mptt_right = (int) $ac->mptt_right; 
  590. $activities[ $i ]->is_spam = (int) $ac->is_spam; 
  591.  
  592. } else { 
  593. // Query first for activity IDs. 
  594. $activity_ids_sql = "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}, a.id {$sort}"; 
  595.  
  596. if ( ! empty( $per_page ) && ! empty( $page ) ) { 
  597. // We query for $per_page + 1 items in order to 
  598. // populate the has_more_items flag. 
  599. $activity_ids_sql .= $wpdb->prepare( " LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page + 1 ); 
  600.  
  601. /** 
  602. * Filters the paged activities MySQL statement. 
  603. * 
  604. * @since 2.0.0 
  605. * 
  606. * @param string $activity_ids_sql MySQL statement used to query for Activity IDs. 
  607. * @param array $r Array of arguments passed into method. 
  608. */ 
  609. $activity_ids_sql = apply_filters( 'bp_activity_paged_activities_sql', $activity_ids_sql, $r ); 
  610.  
  611. /** 
  612. * Queries that include 'last_activity' are cached separately,  
  613. * since they are generally much less long-lived. 
  614. */ 
  615. if ( preg_match( '/a\.type NOT IN \([^\)]*\'last_activity\'[^\)]*\)/', $activity_ids_sql ) ) { 
  616. $cache_group = 'bp_activity'; 
  617. } else { 
  618. $cache_group = 'bp_activity_with_last_activity'; 
  619.  
  620. $cached = bp_core_get_incremented_cache( $activity_ids_sql, $cache_group ); 
  621. if ( false === $cached ) { 
  622. $activity_ids = $wpdb->get_col( $activity_ids_sql ); 
  623. bp_core_set_incremented_cache( $activity_ids_sql, $cache_group, $activity_ids ); 
  624. } else { 
  625. $activity_ids = $cached; 
  626.  
  627. $retval['has_more_items'] = ! empty( $per_page ) && count( $activity_ids ) > $per_page; 
  628.  
  629. // If we've fetched more than the $per_page value, we 
  630. // can discard the extra now. 
  631. if ( ! empty( $per_page ) && count( $activity_ids ) === $per_page + 1 ) { 
  632. array_pop( $activity_ids ); 
  633.  
  634. if ( 'ids' === $r['fields'] ) { 
  635. $activities = array_map( 'intval', $activity_ids ); 
  636. } else { 
  637. $activities = self::get_activity_data( $activity_ids ); 
  638.  
  639. if ( 'ids' !== $r['fields'] ) { 
  640. // Get the fullnames of users so we don't have to query in the loop. 
  641. $activities = self::append_user_fullnames( $activities ); 
  642.  
  643. // Get activity meta. 
  644. $activity_ids = array(); 
  645. foreach ( (array) $activities as $activity ) { 
  646. $activity_ids[] = $activity->id; 
  647.  
  648. if ( ! empty( $activity_ids ) && $r['update_meta_cache'] ) { 
  649. bp_activity_update_meta_cache( $activity_ids ); 
  650.  
  651. if ( $activities && $r['display_comments'] ) { 
  652. $activities = BP_Activity_Activity::append_comments( $activities, $r['spam'] ); 
  653.  
  654. // Pre-fetch data associated with activity users and other objects. 
  655. BP_Activity_Activity::prefetch_object_data( $activities ); 
  656.  
  657. // Generate action strings. 
  658. $activities = BP_Activity_Activity::generate_action_strings( $activities ); 
  659.  
  660. $retval['activities'] = $activities; 
  661.  
  662. // If $max is set, only return up to the max results. 
  663. if ( ! empty( $r['count_total'] ) ) { 
  664.  
  665. /** 
  666. * Filters the total activities MySQL statement. 
  667. * 
  668. * @since 1.5.0 
  669. * 
  670. * @param string $value MySQL statement used to query for total activities. 
  671. * @param string $where_sql MySQL WHERE statement portion. 
  672. * @param string $sort Sort direction for query. 
  673. */ 
  674. $total_activities_sql = apply_filters( 'bp_activity_total_activities_sql', "SELECT count(DISTINCT a.id) FROM {$bp->activity->table_name} a {$join_sql} {$where_sql}", $where_sql, $sort ); 
  675. $cached = bp_core_get_incremented_cache( $total_activities_sql, $cache_group ); 
  676. if ( false === $cached ) { 
  677. $total_activities = $wpdb->get_var( $total_activities_sql ); 
  678. bp_core_set_incremented_cache( $total_activities_sql, $cache_group, $total_activities ); 
  679. } else { 
  680. $total_activities = $cached; 
  681.  
  682. if ( !empty( $r['max'] ) ) { 
  683. if ( (int) $total_activities > (int) $r['max'] ) { 
  684. $total_activities = $r['max']; 
  685.  
  686. $retval['total'] = $total_activities; 
  687.  
  688. return $retval; 
  689.  
  690. /** 
  691. * Convert activity IDs to activity objects, as expected in template loop. 
  692. * 
  693. * @since 2.0.0 
  694. * 
  695. * @param array $activity_ids Array of activity IDs. 
  696. * @return array 
  697. */ 
  698. protected static function get_activity_data( $activity_ids = array() ) { 
  699. global $wpdb; 
  700.  
  701. // Bail if no activity ID's passed. 
  702. if ( empty( $activity_ids ) ) { 
  703. return array(); 
  704.  
  705. // Get BuddyPress. 
  706. $bp = buddypress(); 
  707.  
  708. $activities = array(); 
  709. $uncached_ids = bp_get_non_cached_ids( $activity_ids, 'bp_activity' ); 
  710.  
  711. // Prime caches as necessary. 
  712. if ( ! empty( $uncached_ids ) ) { 
  713. // Format the activity ID's for use in the query below. 
  714. $uncached_ids_sql = implode( ', ', wp_parse_id_list( $uncached_ids ) ); 
  715.  
  716. // Fetch data from activity table, preserving order. 
  717. $queried_adata = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} WHERE id IN ({$uncached_ids_sql})"); 
  718.  
  719. // Put that data into the placeholders created earlier,  
  720. // and add it to the cache. 
  721. foreach ( (array) $queried_adata as $adata ) { 
  722. wp_cache_set( $adata->id, $adata, 'bp_activity' ); 
  723.  
  724. // Now fetch data from the cache. 
  725. foreach ( $activity_ids as $activity_id ) { 
  726. // Integer casting. 
  727. $activity = wp_cache_get( $activity_id, 'bp_activity' ); 
  728. if ( ! empty( $activity ) ) { 
  729. $activity->id = (int) $activity->id; 
  730. $activity->user_id = (int) $activity->user_id; 
  731. $activity->item_id = (int) $activity->item_id; 
  732. $activity->secondary_item_id = (int) $activity->secondary_item_id; 
  733. $activity->hide_sitewide = (int) $activity->hide_sitewide; 
  734. $activity->mptt_left = (int) $activity->mptt_left; 
  735. $activity->mptt_right = (int) $activity->mptt_right; 
  736. $activity->is_spam = (int) $activity->is_spam; 
  737.  
  738. $activities[] = $activity; 
  739.  
  740. // Then fetch user data. 
  741. $user_query = new BP_User_Query( array( 
  742. 'user_ids' => wp_list_pluck( $activities, 'user_id' ),  
  743. 'populate_extras' => false,  
  744. ) ); 
  745.  
  746. // Associated located user data with activity items. 
  747. foreach ( $activities as $a_index => $a_item ) { 
  748. $a_user_id = intval( $a_item->user_id ); 
  749. $a_user = isset( $user_query->results[ $a_user_id ] ) ? $user_query->results[ $a_user_id ] : ''; 
  750.  
  751. if ( !empty( $a_user ) ) { 
  752. $activities[ $a_index ]->user_email = $a_user->user_email; 
  753. $activities[ $a_index ]->user_nicename = $a_user->user_nicename; 
  754. $activities[ $a_index ]->user_login = $a_user->user_login; 
  755. $activities[ $a_index ]->display_name = $a_user->display_name; 
  756.  
  757. return $activities; 
  758.  
  759. /** 
  760. * Append xProfile fullnames to an activity array. 
  761. * 
  762. * @since 2.0.0 
  763. * 
  764. * @param array $activities Activities array. 
  765. * @return array 
  766. */ 
  767. protected static function append_user_fullnames( $activities ) { 
  768.  
  769. if ( bp_is_active( 'xprofile' ) && ! empty( $activities ) ) { 
  770. $activity_user_ids = wp_list_pluck( $activities, 'user_id' ); 
  771.  
  772. if ( ! empty( $activity_user_ids ) ) { 
  773. $fullnames = bp_core_get_user_displaynames( $activity_user_ids ); 
  774. if ( ! empty( $fullnames ) ) { 
  775. foreach ( (array) $activities as $i => $activity ) { 
  776. if ( ! empty( $fullnames[ $activity->user_id ] ) ) { 
  777. $activities[ $i ]->user_fullname = $fullnames[ $activity->user_id ]; 
  778.  
  779. return $activities; 
  780.  
  781. /** 
  782. * Pre-fetch data for objects associated with activity items. 
  783. * 
  784. * Activity items are associated with users, and often with other 
  785. * BuddyPress data objects. Here, we pre-fetch data about these 
  786. * associated objects, so that inline lookups - done primarily when 
  787. * building action strings - do not result in excess database queries. 
  788. * 
  789. * The only object data required for activity component activity types 
  790. * (activity_update and activity_comment) is related to users, and that 
  791. * info is fetched separately in BP_Activity_Activity::get_activity_data(). 
  792. * So this method contains nothing but a filter that allows other 
  793. * components, such as bp-friends and bp-groups, to hook in and prime 
  794. * their own caches at the beginning of an activity loop. 
  795. * 
  796. * @since 2.0.0 
  797. * 
  798. * @param array $activities Array of activities. 
  799. * @return array $activities Array of activities. 
  800. */ 
  801. protected static function prefetch_object_data( $activities ) { 
  802.  
  803. /** 
  804. * Filters inside prefetch_object_data method to aid in pre-fetching object data associated with activity item. 
  805. * 
  806. * @since 2.0.0 
  807. * 
  808. * @param array $activities Array of activities. 
  809. */ 
  810. return apply_filters( 'bp_activity_prefetch_object_data', $activities ); 
  811.  
  812. /** 
  813. * Generate action strings for the activities located in BP_Activity_Activity::get(). 
  814. * 
  815. * If no string can be dynamically generated for a given item 
  816. * (typically because the activity type has not been properly 
  817. * registered), the static 'action' value pulled from the database will 
  818. * be left in place. 
  819. * 
  820. * @since 2.0.0 
  821. * 
  822. * @param array $activities Array of activities. 
  823. * @return array 
  824. */ 
  825. protected static function generate_action_strings( $activities ) { 
  826. foreach ( $activities as $key => $activity ) { 
  827. $generated_action = bp_activity_generate_action_string( $activity ); 
  828. if ( false !== $generated_action ) { 
  829. $activity->action = $generated_action; 
  830.  
  831. $activities[ $key ] = $activity; 
  832.  
  833. return $activities; 
  834.  
  835. /** 
  836. * Get the SQL for the 'meta_query' param in BP_Activity_Activity::get(). 
  837. * 
  838. * We use WP_Meta_Query to do the heavy lifting of parsing the 
  839. * meta_query array and creating the necessary SQL clauses. However,  
  840. * since BP_Activity_Activity::get() builds its SQL differently than 
  841. * WP_Query, we have to alter the return value (stripping the leading 
  842. * AND keyword from the 'where' clause). 
  843. * 
  844. * @since 1.8.0 
  845. * 
  846. * @param array $meta_query An array of meta_query filters. See the 
  847. * documentation for WP_Meta_Query for details. 
  848. * @return array $sql_array 'join' and 'where' clauses. 
  849. */ 
  850. public static function get_meta_query_sql( $meta_query = array() ) { 
  851. global $wpdb; 
  852.  
  853. $sql_array = array( 
  854. 'join' => '',  
  855. 'where' => '',  
  856. ); 
  857.  
  858. if ( ! empty( $meta_query ) ) { 
  859. $activity_meta_query = new WP_Meta_Query( $meta_query ); 
  860.  
  861. // WP_Meta_Query expects the table name at 
  862. // $wpdb->activitymeta. 
  863. $wpdb->activitymeta = buddypress()->activity->table_name_meta; 
  864.  
  865. $meta_sql = $activity_meta_query->get_sql( 'activity', 'a', 'id' ); 
  866.  
  867. // Strip the leading AND - BP handles it in get(). 
  868. $sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] ); 
  869. $sql_array['join'] = $meta_sql['join']; 
  870.  
  871. return $sql_array; 
  872.  
  873. /** 
  874. * Get the SQL for the 'date_query' param in BP_Activity_Activity::get(). 
  875. * 
  876. * We use BP_Date_Query, which extends WP_Date_Query, to do the heavy lifting 
  877. * of parsing the date_query array and creating the necessary SQL clauses. 
  878. * However, since BP_Activity_Activity::get() builds its SQL differently than 
  879. * WP_Query, we have to alter the return value (stripping the leading AND 
  880. * keyword from the query). 
  881. * 
  882. * @since 2.1.0 
  883. * 
  884. * @param array $date_query An array of date_query parameters. See the 
  885. * documentation for the first parameter of WP_Date_Query. 
  886. * @return string 
  887. */ 
  888. public static function get_date_query_sql( $date_query = array() ) { 
  889. $sql = ''; 
  890.  
  891. // Date query. 
  892. if ( ! empty( $date_query ) && is_array( $date_query ) ) { 
  893. $date_query = new BP_Date_Query( $date_query, 'date_recorded' ); 
  894. $sql = preg_replace( '/^\sAND/', '', $date_query->get_sql() ); 
  895.  
  896. return $sql; 
  897.  
  898. /** 
  899. * Get the SQL for the 'scope' param in BP_Activity_Activity::get(). 
  900. * 
  901. * A scope is a predetermined set of activity arguments. This method is used 
  902. * to grab these activity arguments and override any existing args if needed. 
  903. * 
  904. * Can handle multiple scopes. 
  905. * 
  906. * @since 2.2.0 
  907. * 
  908. * @param mixed $scope The activity scope. Accepts string or array of scopes. 
  909. * @param array $r Current activity arguments. Same as those of BP_Activity_Activity::get(),  
  910. * but merged with defaults. 
  911. * @return array 'sql' WHERE SQL string and 'override' activity args. 
  912. */ 
  913. public static function get_scope_query_sql( $scope = false, $r = array() ) { 
  914.  
  915. // Define arrays for future use. 
  916. $query_args = array(); 
  917. $override = array(); 
  918. $retval = array(); 
  919.  
  920. // Check for array of scopes. 
  921. if ( is_array( $scope ) ) { 
  922. $scopes = $scope; 
  923.  
  924. // Explode a comma separated string of scopes. 
  925. } elseif ( is_string( $scope ) ) { 
  926. $scopes = explode( ', ', $scope ); 
  927.  
  928. // Bail if no scope passed. 
  929. if ( empty( $scopes ) ) { 
  930. return false; 
  931.  
  932. // Helper to easily grab the 'user_id'. 
  933. if ( ! empty( $r['filter']['user_id'] ) ) { 
  934. $r['user_id'] = $r['filter']['user_id']; 
  935.  
  936. // Parse each scope; yes! we handle multiples! 
  937. foreach ( $scopes as $scope ) { 
  938. $scope_args = array(); 
  939.  
  940. /** 
  941. * Plugins can hook here to set their activity arguments for custom scopes. 
  942. * 
  943. * This is a dynamic filter based on the activity scope. eg: 
  944. * - 'bp_activity_set_groups_scope_args' 
  945. * - 'bp_activity_set_friends_scope_args' 
  946. * 
  947. * To see how this filter is used, plugin devs should check out: 
  948. * - bp_groups_filter_activity_scope() - used for 'groups' scope 
  949. * - bp_friends_filter_activity_scope() - used for 'friends' scope 
  950. * 
  951. * @since 2.2.0 
  952. * 
  953. * @param array { 
  954. * Activity query clauses. 
  955. * @type array { 
  956. * Activity arguments for your custom scope. 
  957. * See {@link BP_Activity_Query::_construct()} for more details. 
  958. * } 
  959. * @type array $override Optional. Override existing activity arguments passed by $r. 
  960. * } 
  961. * } 
  962. * @param array $r Current activity arguments passed in BP_Activity_Activity::get(). 
  963. */ 
  964. $scope_args = apply_filters( "bp_activity_set_{$scope}_scope_args", array(), $r ); 
  965.  
  966. if ( ! empty( $scope_args ) ) { 
  967. // Merge override properties from other scopes 
  968. // this might be a problem... 
  969. if ( ! empty( $scope_args['override'] ) ) { 
  970. $override = array_merge( $override, $scope_args['override'] ); 
  971. unset( $scope_args['override'] ); 
  972.  
  973. // Save scope args. 
  974. if ( ! empty( $scope_args ) ) { 
  975. $query_args[] = $scope_args; 
  976.  
  977. if ( ! empty( $query_args ) ) { 
  978. // Set relation to OR. 
  979. $query_args['relation'] = 'OR'; 
  980.  
  981. $query = new BP_Activity_Query( $query_args ); 
  982. $sql = $query->get_sql(); 
  983. if ( ! empty( $sql ) ) { 
  984. $retval['sql'] = $sql; 
  985.  
  986. if ( ! empty( $override ) ) { 
  987. $retval['override'] = $override; 
  988.  
  989. return $retval; 
  990.  
  991. /** 
  992. * In BuddyPress 1.2.x, this was used to retrieve specific activity stream items (for example, on an activity's permalink page). 
  993. * 
  994. * As of 1.5.x, use BP_Activity_Activity::get() with an 'in' parameter instead. 
  995. * 
  996. * @since 1.2.0 
  997. * 
  998. * @deprecated 1.5 
  999. * @deprecated Use BP_Activity_Activity::get() with an 'in' parameter instead. 
  1000. * 
  1001. * @param mixed $activity_ids Array or comma-separated string of activity IDs to retrieve. 
  1002. * @param int|bool $max Maximum number of results to return. (Optional; default is no maximum). 
  1003. * @param int $page The set of results that the user is viewing. Used in pagination. (Optional; default is 1). 
  1004. * @param int $per_page Specifies how many results per page. Used in pagination. (Optional; default is 25). 
  1005. * @param string $sort MySQL column sort; ASC or DESC. (Optional; default is DESC). 
  1006. * @param bool $display_comments Retrieve an activity item's associated comments or not. (Optional; default is false). 
  1007. * @return array 
  1008. */ 
  1009. public static function get_specific( $activity_ids, $max = false, $page = 1, $per_page = 25, $sort = 'DESC', $display_comments = false ) { 
  1010. _deprecated_function( __FUNCTION__, '1.5', 'Use BP_Activity_Activity::get() with the "in" parameter instead.' ); 
  1011. return BP_Activity_Activity::get( $max, $page, $per_page, $sort, false, false, $display_comments, false, false, $activity_ids ); 
  1012.  
  1013. /** 
  1014. * Get the first activity ID that matches a set of criteria. 
  1015. * 
  1016. * @since 1.2.0 
  1017. * 
  1018. * @todo Should parameters be optional? 
  1019. * 
  1020. * @param int $user_id User ID to filter by. 
  1021. * @param string $component Component to filter by. 
  1022. * @param string $type Activity type to filter by. 
  1023. * @param int $item_id Associated item to filter by. 
  1024. * @param int $secondary_item_id Secondary associated item to filter by. 
  1025. * @param string $action Action to filter by. 
  1026. * @param string $content Content to filter by. 
  1027. * @param string $date_recorded Date to filter by. 
  1028. * @return int|bool Activity ID on success, false if none is found. 
  1029. */ 
  1030. public static function get_id( $user_id, $component, $type, $item_id, $secondary_item_id, $action, $content, $date_recorded ) { 
  1031. global $wpdb; 
  1032.  
  1033. $bp = buddypress(); 
  1034.  
  1035. $where_args = false; 
  1036.  
  1037. if ( ! empty( $user_id ) ) { 
  1038. $where_args[] = $wpdb->prepare( "user_id = %d", $user_id ); 
  1039.  
  1040. if ( ! empty( $component ) ) { 
  1041. $where_args[] = $wpdb->prepare( "component = %s", $component ); 
  1042.  
  1043. if ( ! empty( $type ) ) { 
  1044. $where_args[] = $wpdb->prepare( "type = %s", $type ); 
  1045.  
  1046. if ( ! empty( $item_id ) ) { 
  1047. $where_args[] = $wpdb->prepare( "item_id = %d", $item_id ); 
  1048.  
  1049. if ( ! empty( $secondary_item_id ) ) { 
  1050. $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id ); 
  1051.  
  1052. if ( ! empty( $action ) ) { 
  1053. $where_args[] = $wpdb->prepare( "action = %s", $action ); 
  1054.  
  1055. if ( ! empty( $content ) ) { 
  1056. $where_args[] = $wpdb->prepare( "content = %s", $content ); 
  1057.  
  1058. if ( ! empty( $date_recorded ) ) { 
  1059. $where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded ); 
  1060.  
  1061. if ( ! empty( $where_args ) ) { 
  1062. $where_sql = 'WHERE ' . join( ' AND ', $where_args ); 
  1063. $result = $wpdb->get_var( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" ); 
  1064.  
  1065. return is_numeric( $result ) ? (int) $result : false; 
  1066.  
  1067. return false; 
  1068.  
  1069. /** 
  1070. * Delete activity items from the database. 
  1071. * 
  1072. * To delete a specific activity item, pass an 'id' parameter. 
  1073. * Otherwise use the filters. 
  1074. * 
  1075. * @since 1.2.0 
  1076. * 
  1077. * @param array $args { 
  1078. * @int $id Optional. The ID of a specific item to delete. 
  1079. * @string $action Optional. The action to filter by. 
  1080. * @string $content Optional. The content to filter by. 
  1081. * @string $component Optional. The component name to filter by. 
  1082. * @string $type Optional. The activity type to filter by. 
  1083. * @string $primary_link Optional. The primary URL to filter by. 
  1084. * @int $user_id Optional. The user ID to filter by. 
  1085. * @int $item_id Optional. The associated item ID to filter by. 
  1086. * @int $secondary_item_id Optional. The secondary associated item ID to filter by. 
  1087. * @string $date_recorded Optional. The date to filter by. 
  1088. * @int $hide_sitewide Optional. Default: false. 
  1089. * } 
  1090. * @return array|bool An array of deleted activity IDs on success, false on failure. 
  1091. */ 
  1092. public static function delete( $args = array() ) { 
  1093. global $wpdb; 
  1094.  
  1095. $bp = buddypress(); 
  1096. $r = wp_parse_args( $args, array( 
  1097. 'id' => false,  
  1098. 'action' => false,  
  1099. 'content' => false,  
  1100. 'component' => false,  
  1101. 'type' => false,  
  1102. 'primary_link' => false,  
  1103. 'user_id' => false,  
  1104. 'item_id' => false,  
  1105. 'secondary_item_id' => false,  
  1106. 'date_recorded' => false,  
  1107. 'hide_sitewide' => false 
  1108. ) ); 
  1109.  
  1110. // Setup empty array from where query arguments. 
  1111. $where_args = array(); 
  1112.  
  1113. // ID. 
  1114. if ( ! empty( $r['id'] ) ) { 
  1115. $where_args[] = $wpdb->prepare( "id = %d", $r['id'] ); 
  1116.  
  1117. // User ID. 
  1118. if ( ! empty( $r['user_id'] ) ) { 
  1119. $where_args[] = $wpdb->prepare( "user_id = %d", $r['user_id'] ); 
  1120.  
  1121. // Action. 
  1122. if ( ! empty( $r['action'] ) ) { 
  1123. $where_args[] = $wpdb->prepare( "action = %s", $r['action'] ); 
  1124.  
  1125. // Content. 
  1126. if ( ! empty( $r['content'] ) ) { 
  1127. $where_args[] = $wpdb->prepare( "content = %s", $r['content'] ); 
  1128.  
  1129. // Component. 
  1130. if ( ! empty( $r['component'] ) ) { 
  1131. $where_args[] = $wpdb->prepare( "component = %s", $r['component'] ); 
  1132.  
  1133. // Type. 
  1134. if ( ! empty( $r['type'] ) ) { 
  1135. $where_args[] = $wpdb->prepare( "type = %s", $r['type'] ); 
  1136.  
  1137. // Primary Link. 
  1138. if ( ! empty( $r['primary_link'] ) ) { 
  1139. $where_args[] = $wpdb->prepare( "primary_link = %s", $r['primary_link'] ); 
  1140.  
  1141. // Item ID. 
  1142. if ( ! empty( $r['item_id'] ) ) { 
  1143. $where_args[] = $wpdb->prepare( "item_id = %d", $r['item_id'] ); 
  1144.  
  1145. // Secondary item ID. 
  1146. if ( ! empty( $r['secondary_item_id'] ) ) { 
  1147. $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $r['secondary_item_id'] ); 
  1148.  
  1149. // Date Recorded. 
  1150. if ( ! empty( $r['date_recorded'] ) ) { 
  1151. $where_args[] = $wpdb->prepare( "date_recorded = %s", $r['date_recorded'] ); 
  1152.  
  1153. // Hidden sitewide. 
  1154. if ( ! empty( $r['hide_sitewide'] ) ) { 
  1155. $where_args[] = $wpdb->prepare( "hide_sitewide = %d", $r['hide_sitewide'] ); 
  1156.  
  1157. // Bail if no where arguments. 
  1158. if ( empty( $where_args ) ) { 
  1159. return false; 
  1160.  
  1161. // Join the where arguments for querying. 
  1162. $where_sql = 'WHERE ' . join( ' AND ', $where_args ); 
  1163.  
  1164. // Fetch all activities being deleted so we can perform more actions. 
  1165. $activities = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} {$where_sql}" ); 
  1166.  
  1167. /** 
  1168. * Action to allow intercepting activity items to be deleted. 
  1169. * 
  1170. * @since 2.3.0 
  1171. * 
  1172. * @param array $activities Array of activities. 
  1173. * @param array $r Array of parsed arguments. 
  1174. */ 
  1175. do_action_ref_array( 'bp_activity_before_delete', array( $activities, $r ) ); 
  1176.  
  1177. // Attempt to delete activities from the database. 
  1178. $deleted = $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$where_sql}" ); 
  1179.  
  1180. // Bail if nothing was deleted. 
  1181. if ( empty( $deleted ) ) { 
  1182. return false; 
  1183.  
  1184. /** 
  1185. * Action to allow intercepting activity items just deleted. 
  1186. * 
  1187. * @since 2.3.0 
  1188. * 
  1189. * @param array $activities Array of activities. 
  1190. * @param array $r Array of parsed arguments. 
  1191. */ 
  1192. do_action_ref_array( 'bp_activity_after_delete', array( $activities, $r ) ); 
  1193.  
  1194. // Pluck the activity IDs out of the $activities array. 
  1195. $activity_ids = wp_parse_id_list( wp_list_pluck( $activities, 'id' ) ); 
  1196.  
  1197. // Handle accompanying activity comments and meta deletion. 
  1198. if ( ! empty( $activity_ids ) ) { 
  1199.  
  1200. // Delete all activity meta entries for activity items. 
  1201. BP_Activity_Activity::delete_activity_meta_entries( $activity_ids ); 
  1202.  
  1203. // Setup empty array for comments. 
  1204. $comment_ids = array(); 
  1205.  
  1206. // Loop through activity ids and attempt to delete comments. 
  1207. foreach ( $activity_ids as $activity_id ) { 
  1208.  
  1209. // Attempt to delete comments. 
  1210. $comments = BP_Activity_Activity::delete( array( 
  1211. 'type' => 'activity_comment',  
  1212. 'item_id' => $activity_id 
  1213. ) ); 
  1214.  
  1215. // Merge IDs together. 
  1216. if ( ! empty( $comments ) ) { 
  1217. $comment_ids = array_merge( $comment_ids, $comments ); 
  1218.  
  1219. // Merge activity IDs with any deleted comment IDs. 
  1220. if ( ! empty( $comment_ids ) ) { 
  1221. $activity_ids = array_unique( array_merge( $activity_ids, $comment_ids ) ); 
  1222.  
  1223. return $activity_ids; 
  1224.  
  1225. /** 
  1226. * Delete the comments associated with a set of activity items. 
  1227. * 
  1228. * This method is no longer used by BuddyPress, and it is recommended not to 
  1229. * use it going forward, and use BP_Activity_Activity::delete() instead. 
  1230. * 
  1231. * @since 1.2.0 
  1232. * 
  1233. * @deprecated 2.3.0 
  1234. * 
  1235. * @param array $activity_ids Activity IDs whose comments should be deleted. 
  1236. * @param bool $delete_meta Should we delete the activity meta items for these comments. 
  1237. * @return bool True on success. 
  1238. */ 
  1239. public static function delete_activity_item_comments( $activity_ids = array(), $delete_meta = true ) { 
  1240. global $wpdb; 
  1241.  
  1242. $bp = buddypress(); 
  1243.  
  1244. $delete_meta = (bool) $delete_meta; 
  1245. $activity_ids = implode( ', ', wp_parse_id_list( $activity_ids ) ); 
  1246.  
  1247. if ( $delete_meta ) { 
  1248. // Fetch the activity comment IDs for our deleted activity items. 
  1249. $activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" ); 
  1250.  
  1251. if ( ! empty( $activity_comment_ids ) ) { 
  1252. self::delete_activity_meta_entries( $activity_comment_ids ); 
  1253.  
  1254. return $wpdb->query( "DELETE FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" ); 
  1255.  
  1256. /** 
  1257. * Delete the meta entries associated with a set of activity items. 
  1258. * 
  1259. * @since 1.2.0 
  1260. * 
  1261. * @param array $activity_ids Activity IDs whose meta should be deleted. 
  1262. * @return bool True on success. 
  1263. */ 
  1264. public static function delete_activity_meta_entries( $activity_ids = array() ) { 
  1265. $activity_ids = wp_parse_id_list( $activity_ids ); 
  1266.  
  1267. foreach ( $activity_ids as $activity_id ) { 
  1268. bp_activity_delete_meta( $activity_id ); 
  1269.  
  1270. return true; 
  1271.  
  1272. /** 
  1273. * Append activity comments to their associated activity items. 
  1274. * 
  1275. * @since 1.2.0 
  1276. * 
  1277. * @global wpdb $wpdb WordPress database object. 
  1278. * 
  1279. * @param array $activities Activities to fetch comments for. 
  1280. * @param string $spam Optional. 'ham_only' (default), 'spam_only' or 'all'. 
  1281. * @return array The updated activities with nested comments. 
  1282. */ 
  1283. public static function append_comments( $activities, $spam = 'ham_only' ) { 
  1284. $activity_comments = array(); 
  1285.  
  1286. // Now fetch the activity comments and parse them into the correct position in the activities array. 
  1287. foreach ( (array) $activities as $activity ) { 
  1288. $top_level_parent_id = 'activity_comment' == $activity->type ? $activity->item_id : 0; 
  1289. $activity_comments[$activity->id] = BP_Activity_Activity::get_activity_comments( $activity->id, $activity->mptt_left, $activity->mptt_right, $spam, $top_level_parent_id ); 
  1290.  
  1291. // Merge the comments with the activity items. 
  1292. foreach ( (array) $activities as $key => $activity ) { 
  1293. if ( isset( $activity_comments[$activity->id] ) ) { 
  1294. $activities[$key]->children = $activity_comments[$activity->id]; 
  1295.  
  1296. return $activities; 
  1297.  
  1298. /** 
  1299. * Get activity comments that are associated with a specific activity ID. 
  1300. * 
  1301. * @since 1.2.0 
  1302. * 
  1303. * @global wpdb $wpdb WordPress database object. 
  1304. * 
  1305. * @param int $activity_id Activity ID to fetch comments for. 
  1306. * @param int $left Left-most node boundary. 
  1307. * @param int $right Right-most node boundary. 
  1308. * @param string $spam Optional. 'ham_only' (default), 'spam_only' or 'all'. 
  1309. * @param int $top_level_parent_id Optional. The id of the root-level parent activity item. 
  1310. * @return array The updated activities with nested comments. 
  1311. */ 
  1312. public static function get_activity_comments( $activity_id, $left, $right, $spam = 'ham_only', $top_level_parent_id = 0 ) { 
  1313. global $wpdb; 
  1314.  
  1315. if ( empty( $top_level_parent_id ) ) { 
  1316. $top_level_parent_id = $activity_id; 
  1317.  
  1318. $comments = wp_cache_get( $activity_id, 'bp_activity_comments' ); 
  1319.  
  1320. // We store the string 'none' to cache the fact that the 
  1321. // activity item has no comments. 
  1322. if ( 'none' === $comments ) { 
  1323. $comments = false; 
  1324.  
  1325. // A true cache miss. 
  1326. } elseif ( empty( $comments ) ) { 
  1327.  
  1328. $bp = buddypress(); 
  1329.  
  1330. // Select the user's fullname with the query. 
  1331. if ( bp_is_active( 'xprofile' ) ) { 
  1332. $fullname_select = ", pd.value as user_fullname"; 
  1333. $fullname_from = ", {$bp->profile->table_name_data} pd "; 
  1334. $fullname_where = "AND pd.user_id = a.user_id AND pd.field_id = 1"; 
  1335.  
  1336. // Prevent debug errors. 
  1337. } else { 
  1338. $fullname_select = $fullname_from = $fullname_where = ''; 
  1339.  
  1340. // Don't retrieve activity comments marked as spam. 
  1341. if ( 'ham_only' == $spam ) { 
  1342. $spam_sql = 'AND a.is_spam = 0'; 
  1343. } elseif ( 'spam_only' == $spam ) { 
  1344. $spam_sql = 'AND a.is_spam = 1'; 
  1345. } else { 
  1346. $spam_sql = ''; 
  1347.  
  1348. // Legacy query - not recommended. 
  1349.  
  1350. /** 
  1351. * Filters if BuddyPress should use the legacy activity query. 
  1352. * 
  1353. * @since 2.0.0 
  1354. * 
  1355. * @param bool $value Whether or not to use the legacy query. 
  1356. * @param BP_Activity_Activity $value Magic method referring to currently called method. 
  1357. * @param array $func_args Array of the method's argument list. 
  1358. */ 
  1359. if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, func_get_args() ) ) { 
  1360.  
  1361. /** 
  1362. * Filters the MySQL prepared statement for the legacy activity query. 
  1363. * 
  1364. * @since 1.5.0 
  1365. * 
  1366. * @param string $value Prepared statement for the activity query. 
  1367. * @param int $activity_id Activity ID to fetch comments for. 
  1368. * @param int $left Left-most node boundary. 
  1369. * @param int $right Right-most node boundary. 
  1370. * @param string $spam_sql SQL Statement portion to differentiate between ham or spam. 
  1371. */ 
  1372. $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 ); 
  1373.  
  1374. $descendants = $wpdb->get_results( $sql ); 
  1375.  
  1376. // We use the mptt BETWEEN clause to limit returned 
  1377. // descendants to the correct part of the tree. 
  1378. } else { 
  1379. $sql = $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} a WHERE a.type = 'activity_comment' {$spam_sql} AND a.item_id = %d and a.mptt_left > %d AND a.mptt_left < %d ORDER BY a.date_recorded ASC", $top_level_parent_id, $left, $right ); 
  1380.  
  1381. $descendant_ids = $wpdb->get_col( $sql ); 
  1382. $descendants = self::get_activity_data( $descendant_ids ); 
  1383. $descendants = self::append_user_fullnames( $descendants ); 
  1384.  
  1385. $ref = array(); 
  1386.  
  1387. // Loop descendants and build an assoc array. 
  1388. foreach ( (array) $descendants as $d ) { 
  1389. $d->children = array(); 
  1390.  
  1391. // If we have a reference on the parent. 
  1392. if ( isset( $ref[ $d->secondary_item_id ] ) ) { 
  1393. $ref[ $d->secondary_item_id ]->children[ $d->id ] = $d; 
  1394. $ref[ $d->id ] =& $ref[ $d->secondary_item_id ]->children[ $d->id ]; 
  1395.  
  1396. // If we don't have a reference on the parent, put in the root level. 
  1397. } else { 
  1398. $comments[ $d->id ] = $d; 
  1399. $ref[ $d->id ] =& $comments[ $d->id ]; 
  1400.  
  1401. // Calculate depth for each item. 
  1402. foreach ( $ref as &$r ) { 
  1403. $depth = 1; 
  1404. $parent_id = $r->secondary_item_id; 
  1405.  
  1406. while ( $parent_id !== $r->item_id ) { 
  1407. $depth++; 
  1408.  
  1409. // When display_comments=stream, the parent comment may not be part of the 
  1410. // returned results, so we manually fetch it. 
  1411. if ( empty( $ref[ $parent_id ] ) ) { 
  1412. $direct_parent = new BP_Activity_Activity( $parent_id ); 
  1413. if ( isset( $direct_parent->secondary_item_id ) ) { 
  1414. // If the direct parent is not an activity update, that means we've reached 
  1415. // the parent activity item (eg. new_blog_post). 
  1416. if ( 'activity_update' !== $direct_parent->type ) { 
  1417. $parent_id = $r->item_id; 
  1418.  
  1419. } else { 
  1420. $parent_id = $direct_parent->secondary_item_id; 
  1421.  
  1422. } else { 
  1423. // Something went wrong. Short-circuit the depth calculation. 
  1424. $parent_id = $r->item_id; 
  1425. } else { 
  1426. $parent_id = $ref[ $parent_id ]->secondary_item_id; 
  1427. $r->depth = $depth; 
  1428.  
  1429. // If we cache a value of false, it'll count as a cache 
  1430. // miss the next time the activity comments are fetched. 
  1431. // Storing the string 'none' is a hack workaround to 
  1432. // avoid unnecessary queries. 
  1433. if ( false === $comments ) { 
  1434. $cache_value = 'none'; 
  1435. } else { 
  1436. $cache_value = $comments; 
  1437.  
  1438. wp_cache_set( $activity_id, $cache_value, 'bp_activity_comments' ); 
  1439.  
  1440. return $comments; 
  1441.  
  1442. /** 
  1443. * Rebuild nested comment tree under an activity or activity comment. 
  1444. * 
  1445. * @since 1.2.0 
  1446. * 
  1447. * @global wpdb $wpdb WordPress database object. 
  1448. * 
  1449. * @param int $parent_id ID of an activity or activity comment. 
  1450. * @param int $left Node boundary start for activity or activity comment. 
  1451. * @return int Right Node boundary of activity or activity comment. 
  1452. */ 
  1453. public static function rebuild_activity_comment_tree( $parent_id, $left = 1 ) { 
  1454. global $wpdb; 
  1455.  
  1456. $bp = buddypress(); 
  1457.  
  1458. // The right value of this node is the left value + 1. 
  1459. $right = intval( $left + 1 ); 
  1460.  
  1461. // Get all descendants of this node. 
  1462. $comments = BP_Activity_Activity::get_child_comments( $parent_id ); 
  1463. $descendants = wp_list_pluck( $comments, 'id' ); 
  1464.  
  1465. // Loop the descendants and recalculate the left and right values. 
  1466. foreach ( (array) $descendants as $descendant_id ) { 
  1467. $right = BP_Activity_Activity::rebuild_activity_comment_tree( $descendant_id, $right ); 
  1468.  
  1469. // We've got the left value, and now that we've processed the children 
  1470. // of this node we also know the right value. 
  1471. if ( 1 === $left ) { 
  1472. $wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE id = %d", $left, $right, $parent_id ) ); 
  1473. } else { 
  1474. $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 ) ); 
  1475.  
  1476. // Return the right value of this node + 1. 
  1477. return intval( $right + 1 ); 
  1478.  
  1479. /** 
  1480. * Get child comments of an activity or activity comment. 
  1481. * 
  1482. * @since 1.2.0 
  1483. * 
  1484. * @global wpdb $wpdb WordPress database object. 
  1485. * 
  1486. * @param int $parent_id ID of an activity or activity comment. 
  1487. * @return object Numerically indexed array of child comments. 
  1488. */ 
  1489. public static function get_child_comments( $parent_id ) { 
  1490. global $wpdb; 
  1491.  
  1492. $bp = buddypress(); 
  1493.  
  1494. return $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND secondary_item_id = %d", $parent_id ) ); 
  1495.  
  1496. /** 
  1497. * Get a list of components that have recorded activity associated with them. 
  1498. * 
  1499. * @since 1.2.0 
  1500. * 
  1501. * @param bool $skip_last_activity If true, components will not be 
  1502. * included if the only activity type associated with them is 
  1503. * 'last_activity'. (Since 2.0.0, 'last_activity' is stored in 
  1504. * the activity table, but these items are not full-fledged 
  1505. * activity items.) Default: true. 
  1506. * @return array List of component names. 
  1507. */ 
  1508. public static function get_recorded_components( $skip_last_activity = true ) { 
  1509. global $wpdb; 
  1510.  
  1511. $bp = buddypress(); 
  1512.  
  1513. if ( true === $skip_last_activity ) { 
  1514. $components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} WHERE action != '' AND action != 'last_activity' ORDER BY component ASC" ); 
  1515. } else { 
  1516. $components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} ORDER BY component ASC" ); 
  1517.  
  1518. return $components; 
  1519.  
  1520. /** 
  1521. * Get sitewide activity items for use in an RSS feed. 
  1522. * 
  1523. * @since 1.0.0 
  1524. * 
  1525. * @param int $limit Optional. Number of items to fetch. Default: 35. 
  1526. * @return array $activity_feed List of activity items, with RSS data added. 
  1527. */ 
  1528. public static function get_sitewide_items_for_feed( $limit = 35 ) { 
  1529. $activities = bp_activity_get_sitewide( array( 'max' => $limit ) ); 
  1530. $activity_feed = array(); 
  1531.  
  1532. for ( $i = 0, $count = count( $activities ); $i < $count; ++$i ) { 
  1533. $title = explode( '<span', $activities[$i]['content'] ); 
  1534. $activity_feed[$i]['title'] = trim( strip_tags( $title[0] ) ); 
  1535. $activity_feed[$i]['link'] = $activities[$i]['primary_link']; 
  1536. $activity_feed[$i]['description'] = @sprintf( $activities[$i]['content'], '' ); 
  1537. $activity_feed[$i]['pubdate'] = $activities[$i]['date_recorded']; 
  1538.  
  1539. return $activity_feed; 
  1540.  
  1541. /** 
  1542. * Create SQL IN clause for filter queries. 
  1543. * 
  1544. * @since 1.5.0 
  1545. * 
  1546. * @see BP_Activity_Activity::get_filter_sql() 
  1547. * 
  1548. * @param string $field The database field. 
  1549. * @param array|bool $items The values for the IN clause, or false when none are found. 
  1550. * @return string|bool 
  1551. */ 
  1552. public static function get_in_operator_sql( $field, $items ) { 
  1553. global $wpdb; 
  1554.  
  1555. // Split items at the comma. 
  1556. if ( ! is_array( $items ) ) { 
  1557. $items = explode( ', ', $items ); 
  1558.  
  1559. // Array of prepared integers or quoted strings. 
  1560. $items_prepared = array(); 
  1561.  
  1562. // Clean up and format each item. 
  1563. foreach ( $items as $item ) { 
  1564. // Clean up the string. 
  1565. $item = trim( $item ); 
  1566. // Pass everything through prepare for security and to safely quote strings. 
  1567. $items_prepared[] = ( is_numeric( $item ) ) ? $wpdb->prepare( '%d', $item ) : $wpdb->prepare( '%s', $item ); 
  1568.  
  1569. // Build IN operator sql syntax. 
  1570. if ( count( $items_prepared ) ) 
  1571. return sprintf( '%s IN ( %s )', trim( $field ), implode( ', ', $items_prepared ) ); 
  1572. else 
  1573. return false; 
  1574.  
  1575. /** 
  1576. * Create filter SQL clauses. 
  1577. * 
  1578. * @since 1.5.0 
  1579. * 
  1580. * @param array $filter_array { 
  1581. * Fields and values to filter by. 
  1582. * 
  1583. * @type array|string|int $user_id User ID(s). 
  1584. * @type array|string $object Corresponds to the 'component' 
  1585. * column in the database. 
  1586. * @type array|string $action Corresponds to the 'type' column 
  1587. * in the database. 
  1588. * @type array|string|int $primary_id Corresponds to the 'item_id' 
  1589. * column in the database. 
  1590. * @type array|string|int $secondary_id Corresponds to the 
  1591. * 'secondary_item_id' column in the database. 
  1592. * @type int $offset Return only those items with an ID greater 
  1593. * than the offset value. 
  1594. * @type string $since Return only those items that have a 
  1595. * date_recorded value greater than a 
  1596. * given MySQL-formatted date. 
  1597. * } 
  1598. * @return string The filter clause, for use in a SQL query. 
  1599. */ 
  1600. public static function get_filter_sql( $filter_array ) { 
  1601.  
  1602. $filter_sql = array(); 
  1603.  
  1604. if ( !empty( $filter_array['user_id'] ) ) { 
  1605. $user_sql = BP_Activity_Activity::get_in_operator_sql( 'a.user_id', $filter_array['user_id'] ); 
  1606. if ( !empty( $user_sql ) ) 
  1607. $filter_sql[] = $user_sql; 
  1608.  
  1609. if ( !empty( $filter_array['object'] ) ) { 
  1610. $object_sql = BP_Activity_Activity::get_in_operator_sql( 'a.component', $filter_array['object'] ); 
  1611. if ( !empty( $object_sql ) ) 
  1612. $filter_sql[] = $object_sql; 
  1613.  
  1614. if ( !empty( $filter_array['action'] ) ) { 
  1615. $action_sql = BP_Activity_Activity::get_in_operator_sql( 'a.type', $filter_array['action'] ); 
  1616. if ( ! empty( $action_sql ) ) 
  1617. $filter_sql[] = $action_sql; 
  1618.  
  1619. if ( !empty( $filter_array['primary_id'] ) ) { 
  1620. $pid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.item_id', $filter_array['primary_id'] ); 
  1621. if ( !empty( $pid_sql ) ) 
  1622. $filter_sql[] = $pid_sql; 
  1623.  
  1624. if ( !empty( $filter_array['secondary_id'] ) ) { 
  1625. $sid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.secondary_item_id', $filter_array['secondary_id'] ); 
  1626. if ( !empty( $sid_sql ) ) 
  1627. $filter_sql[] = $sid_sql; 
  1628.  
  1629. if ( ! empty( $filter_array['offset'] ) ) { 
  1630. $sid_sql = absint( $filter_array['offset'] ); 
  1631. $filter_sql[] = "a.id >= {$sid_sql}"; 
  1632.  
  1633. if ( ! empty( $filter_array['since'] ) ) { 
  1634. // Validate that this is a proper Y-m-d H:i:s date. 
  1635. // Trick: parse to UNIX date then translate back. 
  1636. $translated_date = date( 'Y-m-d H:i:s', strtotime( $filter_array['since'] ) ); 
  1637. if ( $translated_date === $filter_array['since'] ) { 
  1638. $filter_sql[] = "a.date_recorded > '{$translated_date}'"; 
  1639.  
  1640. if ( empty( $filter_sql ) ) 
  1641. return false; 
  1642.  
  1643. return join( ' AND ', $filter_sql ); 
  1644.  
  1645. /** 
  1646. * Get the date/time of last recorded activity. 
  1647. * 
  1648. * @since 1.2.0 
  1649. * 
  1650. * @return string ISO timestamp. 
  1651. */ 
  1652. public static function get_last_updated() { 
  1653. global $wpdb; 
  1654.  
  1655. $bp = buddypress(); 
  1656.  
  1657. return $wpdb->get_var( "SELECT date_recorded FROM {$bp->activity->table_name} ORDER BY date_recorded DESC LIMIT 1" ); 
  1658.  
  1659. /** 
  1660. * Get favorite count for a given user. 
  1661. * 
  1662. * @since 1.2.0 
  1663. * 
  1664. * @param int $user_id The ID of the user whose favorites you're counting. 
  1665. * @return int $value A count of the user's favorites. 
  1666. */ 
  1667. public static function total_favorite_count( $user_id ) { 
  1668.  
  1669. // Get activities from user meta. 
  1670. $favorite_activity_entries = bp_get_user_meta( $user_id, 'bp_favorite_activities', true ); 
  1671. if ( ! empty( $favorite_activity_entries ) ) { 
  1672. return count( maybe_unserialize( $favorite_activity_entries ) ); 
  1673.  
  1674. // No favorites. 
  1675. return 0; 
  1676.  
  1677. /** 
  1678. * Check whether an activity item exists with a given string content. 
  1679. * 
  1680. * @since 1.1.0 
  1681. * 
  1682. * @param string $content The content to filter by. 
  1683. * @return int|bool The ID of the first matching item if found, otherwise false. 
  1684. */ 
  1685. public static function check_exists_by_content( $content ) { 
  1686. global $wpdb; 
  1687.  
  1688. $bp = buddypress(); 
  1689.  
  1690. $result = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE content = %s", $content ) ); 
  1691.  
  1692. return is_numeric( $result ) ? (int) $result : false; 
  1693.  
  1694. /** 
  1695. * Hide all activity for a given user. 
  1696. * 
  1697. * @since 1.2.0 
  1698. * 
  1699. * @param int $user_id The ID of the user whose activity you want to mark hidden. 
  1700. * @return mixed 
  1701. */ 
  1702. public static function hide_all_for_user( $user_id ) { 
  1703. global $wpdb; 
  1704.  
  1705. $bp = buddypress(); 
  1706.  
  1707. return $wpdb->get_var( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET hide_sitewide = 1 WHERE user_id = %d", $user_id ) ); 
.