Skip to:
Content

Changeset 9485


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

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

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

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

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

Fixes #6083

Location:
trunk/src
Files:
50 added
9 edited

Legend:

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

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

    r9471 r9485  
    1111defined( 'ABSPATH' ) || exit;
    1212
    13 /**
    14  * The main BuddyPress blog class.
    15  *
    16  * A BP_Blogs_Object represents a link between a specific WordPress blog on a
    17  * network and a specific user on that blog.
    18  *
    19  * @since BuddyPress (1.0.0)
    20  */
    21 class BP_Blogs_Blog {
    22     public $id;
    23     public $user_id;
    24     public $blog_id;
    25 
    26     /**
    27      * Constructor method.
    28      *
    29      * @param int $id Optional. The ID of the blog.
    30      */
    31     public function __construct( $id = null ) {
    32         if ( !empty( $id ) ) {
    33             $this->id = $id;
    34             $this->populate();
    35         }
    36     }
    37 
    38     /**
    39      * Populate the object with data about the specific activity item.
    40      */
    41     public function populate() {
    42         global $wpdb;
    43 
    44         $bp = buddypress();
    45 
    46         $blog = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->blogs->table_name} WHERE id = %d", $this->id ) );
    47 
    48         $this->user_id = $blog->user_id;
    49         $this->blog_id = $blog->blog_id;
    50     }
    51 
    52     /**
    53      * Save the BP blog data to the database.
    54      *
    55      * @return bool True on success, false on failure.
    56      */
    57     public function save() {
    58         global $wpdb;
    59 
    60         $this->user_id = apply_filters( 'bp_blogs_blog_user_id_before_save', $this->user_id, $this->id );
    61         $this->blog_id = apply_filters( 'bp_blogs_blog_id_before_save', $this->blog_id, $this->id );
    62 
    63         /**
    64          * Fires before the current blog item gets saved.
    65          *
    66          * Please use this hook to filter the properties above. Each part will be passed in.
    67          *
    68          * @since BuddyPress (1.0.0)
    69          *
    70          * @param BP_Blogs_Blog Current instance of the blog item being saved. Passed by reference.
    71          */
    72         do_action_ref_array( 'bp_blogs_blog_before_save', array( &$this ) );
    73 
    74         // Don't try and save if there is no user ID or blog ID set.
    75         if ( !$this->user_id || !$this->blog_id )
    76             return false;
    77 
    78         // Don't save if this blog has already been recorded for the user.
    79         if ( !$this->id && $this->exists() )
    80             return false;
    81 
    82         $bp = buddypress();
    83 
    84         if ( $this->id ) {
    85             // Update
    86             $sql = $wpdb->prepare( "UPDATE {$bp->blogs->table_name} SET user_id = %d, blog_id = %d WHERE id = %d", $this->user_id, $this->blog_id, $this->id );
    87         } else {
    88             // Save
    89             $sql = $wpdb->prepare( "INSERT INTO {$bp->blogs->table_name} ( user_id, blog_id ) VALUES ( %d, %d )", $this->user_id, $this->blog_id );
    90         }
    91 
    92         if ( !$wpdb->query($sql) )
    93             return false;
    94 
    95         /**
    96          * Fires after the current blog item gets saved.
    97          *
    98          * Please use this hook to filter the properties above. Each part will be passed in.
    99          *
    100          * @since BuddyPress (1.0.0)
    101          *
    102          * @param BP_Blogs_Blog Current instance of the blog item being saved. Passed by reference.
    103          */
    104         do_action_ref_array( 'bp_blogs_blog_after_save', array( &$this ) );
    105 
    106         if ( $this->id )
    107             return $this->id;
    108         else
    109             return $wpdb->insert_id;
    110     }
    111 
    112     /**
    113      * Check whether an association between this user and this blog exists.
    114      *
    115      * @return int The number of associations between the user and blog
    116      *         saved in the blog component tables.
    117      */
    118     public function exists() {
    119         global $wpdb;
    120 
    121         $bp = buddypress();
    122 
    123         return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->blogs->table_name} WHERE user_id = %d AND blog_id = %d", $this->user_id, $this->blog_id ) );
    124     }
    125 
    126     /** Static Methods ***************************************************/
    127 
    128     /**
    129      * Retrieve a set of blog-user associations.
    130      *
    131      * @param string $type The order in which results should be returned.
    132      *        'active', 'alphabetical', 'newest', or 'random'.
    133      * @param int|bool $limit Optional. The maximum records to return.
    134      *        Default: false.
    135      * @param int|bool $page Optional. The page of records to return.
    136      *        Default: false (unlimited results).
    137      * @param int $user_id Optional. ID of the user whose blogs are being
    138      *        retrieved. Default: 0.
    139      * @param string|bool $search_terms Optional. Search by text stored in
    140      *        blogmeta (such as the blog name). Default: false.
    141      * @param bool $update_meta_cache Whether to pre-fetch metadata for
    142      *        blogs. Default: true.
    143      * @param array $include_blog_ids Array of blog IDs to include.
    144      * @return array Multidimensional results array, structured as follows:
    145      *           'blogs' - Array of located blog objects
    146      *           'total' - A count of the total blogs matching the filter params
    147      */
    148     public static function get( $type, $limit = false, $page = false, $user_id = 0, $search_terms = false, $update_meta_cache = true, $include_blog_ids = false ) {
    149         global $wpdb;
    150 
    151         $bp = buddypress();
    152 
    153         if ( !is_user_logged_in() || ( !bp_current_user_can( 'bp_moderate' ) && ( $user_id != bp_loggedin_user_id() ) ) )
    154             $hidden_sql = "AND wb.public = 1";
    155         else
    156             $hidden_sql = '';
    157 
    158         $pag_sql = ( $limit && $page ) ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ) : '';
    159 
    160         $user_sql = !empty( $user_id ) ? $wpdb->prepare( " AND b.user_id = %d", $user_id ) : '';
    161 
    162         switch ( $type ) {
    163             case 'active': default:
    164                 $order_sql = "ORDER BY bm.meta_value DESC";
    165                 break;
    166             case 'alphabetical':
    167                 $order_sql = "ORDER BY bm_name.meta_value ASC";
    168                 break;
    169             case 'newest':
    170                 $order_sql = "ORDER BY wb.registered DESC";
    171                 break;
    172             case 'random':
    173                 $order_sql = "ORDER BY RAND()";
    174                 break;
    175         }
    176 
    177         $include_sql = '';
    178         $include_blog_ids = array_filter( wp_parse_id_list( $include_blog_ids ) );
    179         if ( ! empty( $include_blog_ids ) ) {
    180             $blog_ids_sql = implode( ',', $include_blog_ids );
    181             $include_sql  = " AND b.blog_id IN ({$blog_ids_sql})";
    182         }
    183 
    184         if ( ! empty( $search_terms ) ) {
    185             $search_terms_like = '%' . bp_esc_like( $search_terms ) . '%';
    186             $search_terms_sql  = $wpdb->prepare( 'AND (bm_name.meta_value LIKE %s OR bm_description.meta_value LIKE %s)', $search_terms_like, $search_terms_like );
    187         } else {
    188             $search_terms_sql = '';
    189         }
    190 
    191         $paged_blogs = $wpdb->get_results( "
    192             SELECT b.blog_id, b.user_id as admin_user_id, u.user_email as admin_user_email, wb.domain, wb.path, bm.meta_value as last_activity, bm_name.meta_value as name
    193             FROM
    194               {$bp->blogs->table_name} b
    195               LEFT JOIN {$bp->blogs->table_name_blogmeta} bm ON (b.blog_id = bm.blog_id)
    196               LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_name ON (b.blog_id = bm_name.blog_id)
    197               LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_description ON (b.blog_id = bm_description.blog_id)
    198               LEFT JOIN {$wpdb->base_prefix}blogs wb ON (b.blog_id = wb.blog_id)
    199               LEFT JOIN {$wpdb->users} u ON (b.user_id = u.ID)
    200             WHERE
    201               wb.archived = '0' AND wb.spam = 0 AND wb.mature = 0 AND wb.deleted = 0 {$hidden_sql}
    202               AND bm.meta_key = 'last_activity' AND bm_name.meta_key = 'name' AND bm_description.meta_key = 'description'
    203               {$search_terms_sql} {$user_sql} {$include_sql}
    204             GROUP BY b.blog_id {$order_sql} {$pag_sql}
    205         " );
    206 
    207         $total_blogs = $wpdb->get_var( "
    208             SELECT COUNT(DISTINCT b.blog_id)
    209             FROM
    210               {$bp->blogs->table_name} b
    211               LEFT JOIN {$wpdb->base_prefix}blogs wb ON (b.blog_id = wb.blog_id)
    212               LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_name ON (b.blog_id = bm_name.blog_id)
    213               LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_description ON (b.blog_id = bm_description.blog_id)
    214             WHERE
    215               wb.archived = '0' AND wb.spam = 0 AND wb.mature = 0 AND wb.deleted = 0 {$hidden_sql}
    216               AND
    217               bm_name.meta_key = 'name' AND bm_description.meta_key = 'description'
    218               {$search_terms_sql} {$user_sql} {$include_sql}
    219         " );
    220 
    221         $blog_ids = array();
    222         foreach ( (array) $paged_blogs as $blog ) {
    223             $blog_ids[] = (int) $blog->blog_id;
    224         }
    225 
    226         $paged_blogs = BP_Blogs_Blog::get_blog_extras( $paged_blogs, $blog_ids, $type );
    227 
    228         if ( $update_meta_cache ) {
    229             bp_blogs_update_meta_cache( $blog_ids );
    230         }
    231 
    232         return array( 'blogs' => $paged_blogs, 'total' => $total_blogs );
    233     }
    234 
    235     /**
    236      * Delete the record of a given blog for all users.
    237      *
    238      * @param int $blog_id The blog being removed from all users.
    239      * @return int|bool Number of rows deleted on success, false on failure.
    240      */
    241     public static function delete_blog_for_all( $blog_id ) {
    242         global $wpdb;
    243 
    244         bp_blogs_delete_blogmeta( $blog_id );
    245        
    246         $bp = buddypress();
    247 
    248         return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->blogs->table_name} WHERE blog_id = %d", $blog_id ) );
    249     }
    250 
    251     /**
    252      * Delete the record of a given blog for a specific user.
    253      *
    254      * @param int $blog_id The blog being removed.
    255      * @param int $user_id Optional. The ID of the user from whom the blog
    256      *        is being removed. If absent, defaults to the logged-in user ID.
    257      * @return int|bool Number of rows deleted on success, false on failure.
    258      */
    259     public static function delete_blog_for_user( $blog_id, $user_id = null ) {
    260         global $wpdb;
    261 
    262         if ( !$user_id )
    263             $user_id = bp_loggedin_user_id();
    264 
    265         $bp = buddypress();
    266 
    267         return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->blogs->table_name} WHERE user_id = %d AND blog_id = %d", $user_id, $blog_id ) );
    268     }
    269 
    270     /**
    271      * Delete all of a user's blog associations in the BP tables.
    272      *
    273      * @param int $user_id Optional. The ID of the user whose blog
    274      *        associations are being deleted. If absent, defaults to
    275      *        logged-in user ID.
    276      * @return int|bool Number of rows deleted on success, false on failure.
    277      */
    278     public static function delete_blogs_for_user( $user_id = null ) {
    279         global $wpdb;
    280 
    281         if ( !$user_id )
    282             $user_id = bp_loggedin_user_id();
    283 
    284         $bp = buddypress();
    285 
    286         return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->blogs->table_name} WHERE user_id = %d", $user_id ) );
    287     }
    288 
    289     /**
    290      * Get all of a user's blogs, as tracked by BuddyPress.
    291      *
    292      * Note that this is different from the WordPress function
    293      * {@link get_blogs_of_user()}; the current method returns only those
    294      * blogs that have been recorded by BuddyPress, while the WP function
    295      * does a true query of a user's blog capabilities.
    296      *
    297      * @param int $user_id Optional. ID of the user whose blogs are being
    298      *        queried. Defaults to logged-in user.
    299      * @param bool $show_hidden Optional. Whether to include blogs that are
    300      *        not marked public. Defaults to true when viewing one's own
    301      *        profile.
    302      * @return array Multidimensional results array, structured as follows:
    303      *           'blogs' - Array of located blog objects
    304      *           'total' - A count of the total blogs for the user.
    305      */
    306     public static function get_blogs_for_user( $user_id = 0, $show_hidden = false ) {
    307         global $wpdb;
    308 
    309         $bp = buddypress();
    310 
    311         if ( !$user_id )
    312             $user_id = bp_displayed_user_id();
    313 
    314         // Show logged in users their hidden blogs.
    315         if ( !bp_is_my_profile() && !$show_hidden )
    316             $blogs = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT b.blog_id, b.id, bm1.meta_value as name, wb.domain, wb.path FROM {$bp->blogs->table_name} b, {$wpdb->base_prefix}blogs wb, {$bp->blogs->table_name_blogmeta} bm1 WHERE b.blog_id = wb.blog_id AND b.blog_id = bm1.blog_id AND bm1.meta_key = 'name' AND wb.public = 1 AND wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND b.user_id = %d ORDER BY b.blog_id", $user_id ) );
    317         else
    318             $blogs = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT b.blog_id, b.id, bm1.meta_value as name, wb.domain, wb.path FROM {$bp->blogs->table_name} b, {$wpdb->base_prefix}blogs wb, {$bp->blogs->table_name_blogmeta} bm1 WHERE b.blog_id = wb.blog_id AND b.blog_id = bm1.blog_id AND bm1.meta_key = 'name' AND wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND b.user_id = %d ORDER BY b.blog_id", $user_id ) );
    319 
    320         $total_blog_count = BP_Blogs_Blog::total_blog_count_for_user( $user_id );
    321 
    322         $user_blogs = array();
    323         foreach ( (array) $blogs as $blog ) {
    324             $user_blogs[$blog->blog_id] = new stdClass;
    325             $user_blogs[$blog->blog_id]->id = $blog->id;
    326             $user_blogs[$blog->blog_id]->blog_id = $blog->blog_id;
    327             $user_blogs[$blog->blog_id]->siteurl = ( is_ssl() ) ? 'https://' . $blog->domain . $blog->path : 'http://' . $blog->domain . $blog->path;
    328             $user_blogs[$blog->blog_id]->name = $blog->name;
    329         }
    330 
    331         return array( 'blogs' => $user_blogs, 'count' => $total_blog_count );
    332     }
    333 
    334     /**
    335      * Get IDs of all of a user's blogs, as tracked by BuddyPress.
    336      *
    337      * This method always includes hidden blogs.
    338      *
    339      * @param int $user_id Optional. ID of the user whose blogs are being
    340      *        queried. Defaults to logged-in user.
    341      * @return int The number of blogs associated with the user.
    342      */
    343     public static function get_blog_ids_for_user( $user_id = 0 ) {
    344         global $wpdb;
    345 
    346         $bp = buddypress();
    347 
    348         if ( !$user_id )
    349             $user_id = bp_displayed_user_id();
    350 
    351         return $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM {$bp->blogs->table_name} WHERE user_id = %d", $user_id ) );
    352     }
    353 
    354     /**
    355      * Check whether a blog has been recorded by BuddyPress.
    356      *
    357      * @param int $blog_id ID of the blog being queried.
    358      * @return int|null The ID of the first located entry in the BP table
    359      *         on success, otherwise null.
    360      */
    361     public static function is_recorded( $blog_id ) {
    362         global $wpdb;
    363 
    364         $bp = buddypress();
    365 
    366         return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->blogs->table_name} WHERE blog_id = %d", $blog_id ) );
    367     }
    368 
    369     /**
    370      * Return a count of associated blogs for a given user.
    371      *
    372      * Includes hidden blogs when the logged-in user is the same as the
    373      * $user_id parameter, or when the logged-in user has the bp_moderate
    374      * cap.
    375      *
    376      * @param int $user_id Optional. ID of the user whose blogs are being
    377      *        queried. Defaults to logged-in user.
    378      * @return int Blog count for the user.
    379      */
    380     public static function total_blog_count_for_user( $user_id = null ) {
    381         global $wpdb;
    382 
    383         $bp = buddypress();
    384 
    385         if ( !$user_id )
    386             $user_id = bp_displayed_user_id();
    387 
    388         // If the user is logged in return the blog count including their hidden blogs.
    389         if ( ( is_user_logged_in() && $user_id == bp_loggedin_user_id() ) || bp_current_user_can( 'bp_moderate' ) ) {
    390             return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT b.blog_id) FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND user_id = %d", $user_id ) );
    391         } else {
    392             return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT b.blog_id) FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.public = 1 AND wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND user_id = %d", $user_id ) );
    393         }
    394     }
    395 
    396     /**
    397      * Return a list of blogs matching a search term.
    398      *
    399      * Matches against blog names and descriptions, as stored in the BP
    400      * blogmeta table.
    401      *
    402      * @param string $filter The search term.
    403      * @param int $limit Optional. The maximum number of items to return.
    404      *        Default: null (no limit).
    405      * @param int $page Optional. The page of results to return. Default:
    406      *        null (no limit).
    407      * @return array Multidimensional results array, structured as follows:
    408      *           'blogs' - Array of located blog objects
    409      *           'total' - A count of the total blogs matching the query.
    410      */
    411     public static function search_blogs( $filter, $limit = null, $page = null ) {
    412         global $wpdb;
    413 
    414         $search_terms_like = '%' . bp_esc_like( $filter ) . '%';
    415         $search_terms_sql  = $wpdb->prepare( 'bm.meta_value LIKE %s', $search_terms_like );
    416 
    417         $hidden_sql = '';
    418         if ( !bp_current_user_can( 'bp_moderate' ) )
    419             $hidden_sql = "AND wb.public = 1";
    420 
    421         $pag_sql = '';
    422         if ( $limit && $page ) {
    423             $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
    424         }
    425 
    426         $bp = buddypress();
    427 
    428         $paged_blogs = $wpdb->get_results( "SELECT DISTINCT bm.blog_id FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE ( ( bm.meta_key = 'name' OR bm.meta_key = 'description' ) AND {$search_terms_sql} ) {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY meta_value ASC{$pag_sql}" );
    429         $total_blogs = $wpdb->get_var( "SELECT COUNT(DISTINCT bm.blog_id) FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE ( ( bm.meta_key = 'name' OR bm.meta_key = 'description' ) AND {$search_terms_sql} ) {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY meta_value ASC" );
    430 
    431         return array( 'blogs' => $paged_blogs, 'total' => $total_blogs );
    432     }
    433 
    434     /**
    435      * Retrieve a list of all blogs.
    436      *
    437      * Query will include hidden blogs if the logged-in user has the
    438      * 'bp_moderate' cap.
    439      *
    440      * @param int $limit Optional. The maximum number of items to return.
    441      *        Default: null (no limit).
    442      * @param int $page Optional. The page of results to return. Default:
    443      *        null (no limit).
    444      * @return array Multidimensional results array, structured as follows:
    445      *           'blogs' - Array of located blog objects
    446      *           'total' - A count of the total blogs.
    447      */
    448     public static function get_all( $limit = null, $page = null ) {
    449         global $wpdb;
    450 
    451         $bp = buddypress();
    452 
    453         $hidden_sql = !bp_current_user_can( 'bp_moderate' ) ? "AND wb.public = 1" : '';
    454         $pag_sql    = ( $limit && $page ) ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ) : '';
    455 
    456         $paged_blogs = $wpdb->get_results( "SELECT DISTINCT b.blog_id FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 {$hidden_sql} {$pag_sql}" );
    457         $total_blogs = $wpdb->get_var( "SELECT COUNT(DISTINCT b.blog_id) FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 {$hidden_sql}" );
    458 
    459         return array( 'blogs' => $paged_blogs, 'total' => $total_blogs );
    460     }
    461 
    462     /**
    463      * Retrieve a list of blogs whose names start with a given letter.
    464      *
    465      * Query will include hidden blogs if the logged-in user has the
    466      * 'bp_moderate' cap.
    467      *
    468      * @param string $letter. The letter you're looking for.
    469      * @param int $limit Optional. The maximum number of items to return.
    470      *        Default: null (no limit).
    471      * @param int $page Optional. The page of results to return. Default:
    472      *        null (no limit).
    473      * @return array Multidimensional results array, structured as follows:
    474      *           'blogs' - Array of located blog objects.
    475      *           'total' - A count of the total blogs matching the query.
    476      */
    477     public static function get_by_letter( $letter, $limit = null, $page = null ) {
    478         global $wpdb;
    479 
    480         $bp = buddypress();
    481 
    482         $letter_like = '%' . bp_esc_like( $letter ) . '%';
    483         $letter_sql  = $wpdb->prepare( 'bm.meta_value LIKE %s', $letter_like );
    484 
    485         $hidden_sql = '';
    486         if ( !bp_current_user_can( 'bp_moderate' ) )
    487             $hidden_sql = "AND wb.public = 1";
    488 
    489         $pag_sql = '';
    490         if ( $limit && $page )
    491             $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
    492 
    493         $paged_blogs = $wpdb->get_results( "SELECT DISTINCT bm.blog_id FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE bm.meta_key = 'name' AND {$letter_sql} {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY bm.meta_value ASC{$pag_sql}" );
    494         $total_blogs = $wpdb->get_var( "SELECT COUNT(DISTINCT bm.blog_id) FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE bm.meta_key = 'name' AND {$letter_sql} {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY bm.meta_value ASC" );
    495 
    496         return array( 'blogs' => $paged_blogs, 'total' => $total_blogs );
    497     }
    498 
    499     /**
    500      * Fetch blog data not caught in the main query and append it to results array.
    501      *
    502      * Gets the following information, which is either unavailable at the
    503      * time of the original query, or is more efficient to look up in one
    504      * fell swoop:
    505      *   - The latest post for each blog, include Featured Image data
    506      *   - The blog description
    507      *
    508      * @param array $paged_blogs Array of results from the original query.
    509      * @param array $blog_ids Array of IDs returned from the original query.
    510      * @param string|bool $type Not currently used. Default: false.
    511      * @return array $paged_blogs The located blogs array, with the extras added.
    512      */
    513     public static function get_blog_extras( &$paged_blogs, &$blog_ids, $type = false ) {
    514         global $wpdb;
    515 
    516         $bp = buddypress();
    517 
    518         if ( empty( $blog_ids ) )
    519             return $paged_blogs;
    520 
    521         $blog_ids = implode( ',', wp_parse_id_list( $blog_ids ) );
    522 
    523         for ( $i = 0, $count = count( $paged_blogs ); $i < $count; ++$i ) {
    524             $blog_prefix = $wpdb->get_blog_prefix( $paged_blogs[$i]->blog_id );
    525             $paged_blogs[$i]->latest_post = $wpdb->get_row( "SELECT ID, post_content, post_title, post_excerpt, guid FROM {$blog_prefix}posts WHERE post_status = 'publish' AND post_type = 'post' AND id != 1 ORDER BY id DESC LIMIT 1" );
    526             $images = array();
    527 
    528             // Add URLs to any Featured Image this post might have
    529             if ( ! empty( $paged_blogs[$i]->latest_post ) && has_post_thumbnail( $paged_blogs[$i]->latest_post->ID ) ) {
    530 
    531                 // Grab 4 sizes of the image. Thumbnail.
    532                 $image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'thumbnail', false );
    533                 if ( ! empty( $image ) )
    534                     $images['thumbnail'] = $image[0];
    535 
    536                 // Medium
    537                 $image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'medium', false );
    538                 if ( ! empty( $image ) )
    539                     $images['medium'] = $image[0];
    540 
    541                 // Large
    542                 $image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'large', false );
    543                 if ( ! empty( $image ) )
    544                     $images['large'] = $image[0];
    545 
    546                 // Post thumbnail
    547                 $image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'post-thumbnail', false );
    548                 if ( ! empty( $image ) )
    549                     $images['post-thumbnail'] = $image[0];
    550 
    551                 // Add the images to the latest_post object
    552                 $paged_blogs[$i]->latest_post->images = $images;
    553             }
    554         }
    555 
    556         /* Fetch the blog description for each blog (as it may be empty we can't fetch it in the main query). */
    557         $blog_descs = $wpdb->get_results( "SELECT blog_id, meta_value as description FROM {$bp->blogs->table_name_blogmeta} WHERE meta_key = 'description' AND blog_id IN ( {$blog_ids} )" );
    558 
    559         for ( $i = 0, $count = count( $paged_blogs ); $i < $count; ++$i ) {
    560             foreach ( (array) $blog_descs as $desc ) {
    561                 if ( $desc->blog_id == $paged_blogs[$i]->blog_id )
    562                     $paged_blogs[$i]->description = $desc->description;
    563             }
    564         }
    565 
    566         return $paged_blogs;
    567     }
    568 
    569     /**
    570      * Check whether a given blog is hidden.
    571      *
    572      * Checks the 'public' column in the wp_blogs table.
    573      *
    574      * @param int $blog_id The ID of the blog being checked.
    575      * @return bool True if hidden (public = 0), false otherwise.
    576      */
    577     public static function is_hidden( $blog_id ) {
    578         global $wpdb;
    579 
    580         if ( !(int) $wpdb->get_var( $wpdb->prepare( "SELECT public FROM {$wpdb->base_prefix}blogs WHERE blog_id = %d", $blog_id ) ) ) {
    581             return true;
    582         }
    583 
    584         return false;
    585     }
    586 
    587     /**
    588      * Get ID of user-blog link.
    589      *
    590      * @param int $user_id ID of user.
    591      * @param int $blog_id ID of blog.
    592      * @return int|bool ID of user-blog link, or false if not found.
    593      */
    594     public static function get_user_blog( $user_id, $blog_id ) {
    595         global $wpdb;
    596 
    597         $bp = buddypress();
    598 
    599         $user_blog = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->blogs->table_name} WHERE user_id = %d AND blog_id = %d", $user_id, $blog_id ) );
    600 
    601         if ( empty( $user_blog ) ) {
    602             $user_blog = false;
    603         } else {
    604             $user_blog = intval( $user_blog );
    605         }
    606 
    607         return $user_blog;
    608     }
    609 }
     13require __DIR__ . '/classes/class-bp-blogs-blog.php';
  • trunk/src/bp-core/bp-core-classes.php

    r9471 r9485  
    1010defined( 'ABSPATH' ) || exit;
    1111
    12 /**
    13  * BuddyPress User Query class.
    14  *
    15  * Used for querying users in a BuddyPress context, in situations where WP_User_Query won't do the trick:
    16  * Member directories, the Friends component, etc.
    17  *
    18  * @since BuddyPress (1.7.0)
    19  *
    20  * @param array $query {
    21  *     Query arguments. All items are optional.
    22  *     @type string            $type            Determines sort order. Select from 'newest', 'active', 'online',
    23  *                                              'random', 'popular', 'alphabetical'. Default: 'newest'.
    24  *     @type int               $per_page Number of results to return. Default: 0 (no limit).
    25  *     @type int               $page            Page offset (together with $per_page). Default: 1.
    26  *     @type int               $user_id         ID of a user. If present, and if the friends component is activated,
    27  *                                              results will be limited to the friends of that user. Default: 0.
    28  *     @type string|bool       $search_terms    Terms to search by. Search happens across xprofile fields. Requires
    29  *                                              XProfile component. Default: false.
    30  *     @type string            $search_wildcard When searching with $search_terms, set where wildcards around the term
    31  *                                              should be positioned. Accepts 'both', 'left', 'right'. Default: 'both'.
    32  *     @type array|string|bool $include         An array or comma-separated list of user IDs to which query should
    33  *                                              be limited. Default: false.
    34  *     @type array|string|bool $exclude         An array or comma-separated list of user IDs that will be excluded from
    35  *                                              query results. Default: false.
    36  *     @type array|string|bool $user_ids        An array or comma-separated list of IDs corresponding to the users
    37  *                                              that should be returned. When this parameter is passed, it will
    38  *                                              override all others; BP User objects will be constructed using these
    39  *                                              IDs only. Default: false.
    40  *     @type array|string      $member_type     Array or comma-separated list of member types to limit results to.
    41  *     @type string|bool       $meta_key        Limit results to users that have usermeta associated with this meta_key.
    42  *                                              Usually used with $meta_value. Default: false.
    43  *     @type string|bool       $meta_value      When used with $meta_key, limits results to users whose usermeta value
    44  *                                              associated with $meta_key matches $meta_value. Default: false.
    45  *     @type array             $xprofile_query  Filter results by xprofile data. Requires the xprofile component. See
    46  *                                              {@see BP_XProfile_Query} for details.
    47  *     @type bool              $populate_extras True if you want to fetch extra metadata
    48  *                                              about returned users, such as total group and friend counts.
    49  *     @type string            $count_total     Determines how BP_User_Query will do a count of total users matching
    50  *                                              the other filter criteria. Default value is 'count_query', which does
    51  *                                              a separate SELECT COUNT query to determine the total.
    52  *                                              'sql_count_found_rows' uses SQL_COUNT_FOUND_ROWS and
    53  *                                              SELECT FOUND_ROWS(). Pass an empty string to skip the total user
    54  *                                              count query.
    55  * }
    56  */
    57 class BP_User_Query {
    58 
    59     /** Variables *************************************************************/
    60 
    61     /**
    62      * Unaltered params as passed to the constructor.
    63      *
    64      * @since BuddyPress (1.8.0)
    65      * @var array
    66      */
    67     public $query_vars_raw = array();
    68 
    69     /**
    70      * Array of variables to query with.
    71      *
    72      * @since BuddyPress (1.7.0)
    73      * @var array
    74      */
    75     public $query_vars = array();
    76 
    77     /**
    78      * List of found users and their respective data.
    79      *
    80      * @access public To allow components to manipulate them.
    81      * @since BuddyPress (1.7.0)
    82      * @var array
    83      */
    84     public $results = array();
    85 
    86     /**
    87      * Total number of found users for the current query.
    88      *
    89      * @access public To allow components to manipulate it.
    90      * @since BuddyPress (1.7.0)
    91      * @var int
    92      */
    93     public $total_users = 0;
    94 
    95     /**
    96      * List of found user IDs.
    97      *
    98      * @access public To allow components to manipulate it.
    99      * @since BuddyPress (1.7.0)
    100      * @var array
    101      */
    102     public $user_ids = array();
    103 
    104     /**
    105      * SQL clauses for the user ID query.
    106      *
    107      * @access public To allow components to manipulate it.
    108      * @since BuddyPress (1.7.0)
    109      * @var array
    110      */
    111     public $uid_clauses = array();
    112 
    113     /**
    114      * SQL table where the user ID is being fetched from.
    115      *
    116      * @since BuddyPress (2.2.0)
    117      * @access public
    118      * @var string
    119      */
    120     public $uid_table = '';
    121 
    122     /**
    123      * SQL database column name to order by.
    124      *
    125      * @since BuddyPress (1.7.0)
    126      * @var string
    127      */
    128     public $uid_name = '';
    129 
    130     /**
    131      * Standard response when the query should not return any rows.
    132      *
    133      * @access protected
    134      * @since BuddyPress (1.7.0)
    135      * @var string
    136      */
    137     protected $no_results = array( 'join' => '', 'where' => '0 = 1' );
    138 
    139 
    140     /** Methods ***************************************************************/
    141 
    142     /**
    143      * Constructor.
    144      *
    145      * @since BuddyPress (1.7.0)
    146      *
    147      * @param string|array $query See {@link BP_User_Query}.
    148      */
    149     public function __construct( $query = null ) {
    150 
    151         // Store the raw query vars for later access
    152         $this->query_vars_raw = $query;
    153 
    154         // Allow extending classes to register action/filter hooks
    155         $this->setup_hooks();
    156 
    157         if ( ! empty( $this->query_vars_raw ) ) {
    158             $this->query_vars = wp_parse_args( $this->query_vars_raw, array(
    159                 'type'            => 'newest',
    160                 'per_page'        => 0,
    161                 'page'            => 1,
    162                 'user_id'         => 0,
    163                 'search_terms'    => false,
    164                 'search_wildcard' => 'both',
    165                 'include'         => false,
    166                 'exclude'         => false,
    167                 'user_ids'        => false,
    168                 'member_type'     => '',
    169                 'meta_key'        => false,
    170                 'meta_value'      => false,
    171                 'xprofile_query'  => false,
    172                 'populate_extras' => true,
    173                 'count_total'     => 'count_query'
    174             ) );
    175 
    176             // Plugins can use this filter to modify query args
    177             // before the query is constructed
    178             do_action_ref_array( 'bp_pre_user_query_construct', array( &$this ) );
    179 
    180             // Get user ids
    181             // If the user_ids param is present, we skip the query
    182             if ( false !== $this->query_vars['user_ids'] ) {
    183                 $this->user_ids = wp_parse_id_list( $this->query_vars['user_ids'] );
    184             } else {
    185                 $this->prepare_user_ids_query();
    186                 $this->do_user_ids_query();
    187             }
    188         }
    189 
    190         // Bail if no user IDs were found
    191         if ( empty( $this->user_ids ) ) {
    192             return;
    193         }
    194 
    195         // Fetch additional data. First, using WP_User_Query
    196         $this->do_wp_user_query();
    197 
    198         // Get BuddyPress specific user data
    199         $this->populate_extras();
    200     }
    201 
    202     /**
    203      * Allow extending classes to set up action/filter hooks.
    204      *
    205      * When extending BP_User_Query, you may need to use some of its
    206      * internal hooks to modify the output. It's not convenient to call
    207      * add_action() or add_filter() in your class constructor, because
    208      * BP_User_Query::__construct() contains a fair amount of logic that
    209      * you may not want to override in your class. Define this method in
    210      * your own class if you need a place where your extending class can
    211      * add its hooks early in the query-building process. See
    212      * {@link BP_Group_Member_Query::setup_hooks()} for an example.
    213      *
    214      * @since BuddyPress (1.8.0)
    215      */
    216     public function setup_hooks() {}
    217 
    218     /**
    219      * Prepare the query for user_ids.
    220      *
    221      * @since BuddyPress (1.7.0)
    222      */
    223     public function prepare_user_ids_query() {
    224         global $wpdb;
    225 
    226         $bp = buddypress();
    227 
    228         // Default query variables used here
    229         $type         = '';
    230         $per_page     = 0;
    231         $page         = 1;
    232         $user_id      = 0;
    233         $include      = false;
    234         $search_terms = false;
    235         $exclude      = false;
    236         $meta_key     = false;
    237         $meta_value   = false;
    238 
    239         extract( $this->query_vars );
    240 
    241         // Setup the main SQL query container
    242         $sql = array(
    243             'select'  => '',
    244             'where'   => array(),
    245             'orderby' => '',
    246             'order'   => '',
    247             'limit'   => ''
    248         );
    249 
    250         /** TYPE **************************************************************/
    251 
    252         // Determines the sort order, which means it also determines where the
    253         // user IDs are drawn from (the SELECT and WHERE statements)
    254         switch ( $type ) {
    255 
    256             // 'online' query happens against the last_activity usermeta key
    257             // Filter 'bp_user_query_online_interval' to modify the
    258             // number of minutes used as an interval
    259             case 'online' :
    260                 $this->uid_name = 'user_id';
    261                 $this->uid_table = $bp->members->table_name_last_activity;
    262                 $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    263                 $sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id );
    264                 $sql['where'][] = $wpdb->prepare( "u.date_recorded >= DATE_SUB( UTC_TIMESTAMP(), INTERVAL %d MINUTE )", apply_filters( 'bp_user_query_online_interval', 15 ) );
    265                 $sql['orderby'] = "ORDER BY u.date_recorded";
    266                 $sql['order']   = "DESC";
    267 
    268                 break;
    269 
    270             // 'active', 'newest', and 'random' queries
    271             // all happen against the last_activity usermeta key
    272             case 'active' :
    273             case 'newest' :
    274             case 'random' :
    275                 $this->uid_name = 'user_id';
    276                 $this->uid_table = $bp->members->table_name_last_activity;
    277                 $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    278                 $sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id );
    279 
    280                 if ( 'newest' == $type ) {
    281                     $sql['orderby'] = "ORDER BY u.user_id";
    282                     $sql['order'] = "DESC";
    283                 } elseif ( 'random' == $type ) {
    284                     $sql['orderby'] = "ORDER BY rand()";
    285                 } else {
    286                     $sql['orderby'] = "ORDER BY u.date_recorded";
    287                     $sql['order'] = "DESC";
    288                 }
    289 
    290                 break;
    291 
    292             // 'popular' sorts by the 'total_friend_count' usermeta
    293             case 'popular' :
    294                 $this->uid_name = 'user_id';
    295                 $this->uid_table = $wpdb->usermeta;
    296                 $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    297                 $sql['where'][] = $wpdb->prepare( "u.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) );
    298                 $sql['orderby'] = "ORDER BY CONVERT(u.meta_value, SIGNED)";
    299                 $sql['order']   = "DESC";
    300 
    301                 break;
    302 
    303             // 'alphabetical' sorts depend on the xprofile setup
    304             case 'alphabetical' :
    305 
    306                 // We prefer to do alphabetical sorts against the display_name field
    307                 // of wp_users, because the table is smaller and better indexed. We
    308                 // can do so if xprofile sync is enabled, or if xprofile is inactive.
    309                 //
    310                 // @todo remove need for bp_is_active() check
    311                 if ( ! bp_disable_profile_sync() || ! bp_is_active( 'xprofile' ) ) {
    312                     $this->uid_name = 'ID';
    313                     $this->uid_table = $wpdb->users;
    314                     $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    315                     $sql['orderby'] = "ORDER BY u.display_name";
    316                     $sql['order']   = "ASC";
    317 
    318                 // When profile sync is disabled, alphabetical sorts must happen against
    319                 // the xprofile table
    320                 } else {
    321                     $this->uid_name = 'user_id';
    322                     $this->uid_table = $bp->profile->table_name_data;
    323                     $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    324                     $sql['where'][] = $wpdb->prepare( "u.field_id = %d", bp_xprofile_fullname_field_id() );
    325                     $sql['orderby'] = "ORDER BY u.value";
    326                     $sql['order']   = "ASC";
    327                 }
    328 
    329                 // Alphabetical queries ignore last_activity, while BP uses last_activity
    330                 // to infer spam/deleted/non-activated users. To ensure that these users
    331                 // are filtered out, we add an appropriate sub-query.
    332                 $sql['where'][] = "u.{$this->uid_name} IN ( SELECT ID FROM {$wpdb->users} WHERE " . bp_core_get_status_sql( '' ) . " )";
    333 
    334                 break;
    335 
    336             // Any other 'type' falls through
    337             default :
    338                 $this->uid_name = 'ID';
    339                 $this->uid_table = $wpdb->users;
    340                 $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    341 
    342                 // In this case, we assume that a plugin is
    343                 // handling order, so we leave those clauses
    344                 // blank
    345 
    346                 break;
    347         }
    348 
    349         /** WHERE *************************************************************/
    350 
    351         // 'include' - User ids to include in the results
    352         $include     = false !== $include ? wp_parse_id_list( $include ) : array();
    353         $include_ids = $this->get_include_ids( $include );
    354         if ( ! empty( $include_ids ) ) {
    355             $include_ids    = implode( ',', wp_parse_id_list( $include_ids ) );
    356             $sql['where'][] = "u.{$this->uid_name} IN ({$include_ids})";
    357         }
    358 
    359         // 'exclude' - User ids to exclude from the results
    360         if ( false !== $exclude ) {
    361             $exclude_ids    = implode( ',', wp_parse_id_list( $exclude ) );
    362             $sql['where'][] = "u.{$this->uid_name} NOT IN ({$exclude_ids})";
    363         }
    364 
    365         // 'user_id' - When a user id is passed, limit to the friends of the user
    366         // @todo remove need for bp_is_active() check
    367         if ( ! empty( $user_id ) && bp_is_active( 'friends' ) ) {
    368             $friend_ids = friends_get_friend_user_ids( $user_id );
    369             $friend_ids = implode( ',', wp_parse_id_list( $friend_ids ) );
    370 
    371             if ( ! empty( $friend_ids ) ) {
    372                 $sql['where'][] = "u.{$this->uid_name} IN ({$friend_ids})";
    373 
    374             // If the user has no friends, the query should always
    375             // return no users
    376             } else {
    377                 $sql['where'][] = $this->no_results['where'];
    378             }
    379         }
    380 
    381         /** Search Terms ******************************************************/
    382 
    383         // 'search_terms' searches user_login and user_nicename
    384         // xprofile field matches happen in bp_xprofile_bp_user_query_search()
    385         if ( false !== $search_terms ) {
    386             $search_terms = bp_esc_like( wp_kses_normalize_entities( $search_terms ) );
    387 
    388             if ( $search_wildcard === 'left' ) {
    389                 $search_terms_nospace = '%' . $search_terms;
    390                 $search_terms_space   = '%' . $search_terms . ' %';
    391             } elseif ( $search_wildcard === 'right' ) {
    392                 $search_terms_nospace =        $search_terms . '%';
    393                 $search_terms_space   = '% ' . $search_terms . '%';
    394             } else {
    395                 $search_terms_nospace = '%' . $search_terms . '%';
    396                 $search_terms_space   = '%' . $search_terms . '%';
    397             }
    398 
    399             $sql['where']['search'] = $wpdb->prepare(
    400                 "u.{$this->uid_name} IN ( SELECT ID FROM {$wpdb->users} WHERE ( user_login LIKE %s OR user_login LIKE %s OR user_nicename LIKE %s OR user_nicename LIKE %s ) )",
    401                 $search_terms_nospace,
    402                 $search_terms_space,
    403                 $search_terms_nospace,
    404                 $search_terms_space
    405             );
    406         }
    407 
    408         // Member type.
    409         if ( ! empty( $member_type ) ) {
    410             $member_types = array();
    411 
    412             if ( ! is_array( $member_type ) ) {
    413                 $member_type = preg_split( '/[,\s+]/', $member_type );
    414             }
    415 
    416             foreach ( $member_type as $mt ) {
    417                 if ( ! bp_get_member_type_object( $mt ) ) {
    418                     continue;
    419                 }
    420 
    421                 $member_types[] = $mt;
    422             }
    423 
    424             if ( ! empty( $member_types ) ) {
    425                 $member_type_tq = new WP_Tax_Query( array(
    426                     array(
    427                         'taxonomy' => 'bp_member_type',
    428                         'field'    => 'name',
    429                         'operator' => 'IN',
    430                         'terms'    => $member_types,
    431                     ),
    432                 ) );
    433 
    434                 // Switch to the root blog, where member type taxonomies live.
    435                 switch_to_blog( bp_get_root_blog_id() );
    436 
    437                 $member_type_sql_clauses = $member_type_tq->get_sql( 'u', $this->uid_name );
    438                 restore_current_blog();
    439 
    440 
    441 
    442                 // Grab the first term_relationships clause and convert to a subquery.
    443                 if ( preg_match( '/' . $wpdb->term_relationships . '\.term_taxonomy_id IN \([0-9, ]+\)/', $member_type_sql_clauses['where'], $matches ) ) {
    444                     $sql['where']['member_type'] = "u.{$this->uid_name} IN ( SELECT object_id FROM $wpdb->term_relationships WHERE {$matches[0]} )";
    445                 }
    446             }
    447         }
    448 
    449         // 'meta_key', 'meta_value' allow usermeta search
    450         // To avoid global joins, do a separate query
    451         if ( false !== $meta_key ) {
    452             $meta_sql = $wpdb->prepare( "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key );
    453 
    454             if ( false !== $meta_value ) {
    455                 $meta_sql .= $wpdb->prepare( " AND meta_value = %s", $meta_value );
    456             }
    457 
    458             $found_user_ids = $wpdb->get_col( $meta_sql );
    459 
    460             if ( ! empty( $found_user_ids ) ) {
    461                 $sql['where'][] = "u.{$this->uid_name} IN (" . implode( ',', wp_parse_id_list( $found_user_ids ) ) . ")";
    462             } else {
    463                 $sql['where'][] = '1 = 0';
    464             }
    465         }
    466 
    467         // 'per_page', 'page' - handles LIMIT
    468         if ( !empty( $per_page ) && !empty( $page ) ) {
    469             $sql['limit'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $per_page ), intval( $per_page ) );
    470         } else {
    471             $sql['limit'] = '';
    472         }
    473 
    474         // Allow custom filters
    475         $sql = apply_filters_ref_array( 'bp_user_query_uid_clauses', array( $sql, &$this ) );
    476 
    477         // Assemble the query chunks
    478         $this->uid_clauses['select']  = $sql['select'];
    479         $this->uid_clauses['where']   = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : '';
    480         $this->uid_clauses['orderby'] = $sql['orderby'];
    481         $this->uid_clauses['order']   = $sql['order'];
    482         $this->uid_clauses['limit']   = $sql['limit'];
    483 
    484         do_action_ref_array( 'bp_pre_user_query', array( &$this ) );
    485     }
    486 
    487     /**
    488      * Query for IDs of users that match the query parameters.
    489      *
    490      * Perform a database query to specifically get only user IDs, using
    491      * existing query variables set previously in the constructor.
    492      *
    493      * Also used to quickly perform user total counts.
    494      *
    495      * @since BuddyPress (1.7.0)
    496      */
    497     public function do_user_ids_query() {
    498         global $wpdb;
    499 
    500         // If counting using SQL_CALC_FOUND_ROWS, set it up here
    501         if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) {
    502             $this->uid_clauses['select'] = str_replace( 'SELECT', 'SELECT SQL_CALC_FOUND_ROWS', $this->uid_clauses['select'] );
    503         }
    504 
    505         // Get the specific user ids
    506         $this->user_ids = $wpdb->get_col( "{$this->uid_clauses['select']} {$this->uid_clauses['where']} {$this->uid_clauses['orderby']} {$this->uid_clauses['order']} {$this->uid_clauses['limit']}" );
    507 
    508         // Get the total user count
    509         if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) {
    510             $this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "SELECT FOUND_ROWS()", $this ) );
    511         } elseif ( 'count_query' == $this->query_vars['count_total'] ) {
    512             $count_select      = preg_replace( '/^SELECT.*?FROM (\S+) u/', "SELECT COUNT(u.{$this->uid_name}) FROM $1 u", $this->uid_clauses['select'] );
    513             $this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "{$count_select} {$this->uid_clauses['where']}", $this ) );
    514         }
    515     }
    516 
    517     /**
    518      * Use WP_User_Query() to pull data for the user IDs retrieved in the main query.
    519      *
    520      * @since BuddyPress (1.7.0)
    521      */
    522     public function do_wp_user_query() {
    523         $fields = array( 'ID', 'user_login', 'user_pass', 'user_nicename', 'user_email', 'user_url', 'user_registered', 'user_activation_key', 'user_status', 'display_name' );
    524 
    525         if ( is_multisite() ) {
    526             $fields[] = 'spam';
    527             $fields[] = 'deleted';
    528         }
    529 
    530         $wp_user_query = new WP_User_Query( apply_filters( 'bp_wp_user_query_args', array(
    531 
    532             // Relevant
    533             'fields'      => $fields,
    534             'include'     => $this->user_ids,
    535 
    536             // Overrides
    537             'blog_id'     => 0,    // BP does not require blog roles
    538             'count_total' => false // We already have a count
    539 
    540         ), $this ) );
    541 
    542         // WP_User_Query doesn't cache the data it pulls from wp_users,
    543         // and it does not give us a way to save queries by fetching
    544         // only uncached users. However, BP does cache this data, so
    545         // we set it here.
    546         foreach ( $wp_user_query->results as $u ) {
    547             wp_cache_set( 'bp_core_userdata_' . $u->ID, $u, 'bp' );
    548         }
    549 
    550         // We calculate total_users using a standalone query, except
    551         // when a whitelist of user_ids is passed to the constructor.
    552         // This clause covers the latter situation, and ensures that
    553         // pagination works when querying by $user_ids.
    554         if ( empty( $this->total_users ) ) {
    555             $this->total_users = count( $wp_user_query->results );
    556         }
    557 
    558         // Reindex for easier matching
    559         $r = array();
    560         foreach ( $wp_user_query->results as $u ) {
    561             $r[ $u->ID ] = $u;
    562         }
    563 
    564         // Match up to the user ids from the main query
    565         foreach ( $this->user_ids as $uid ) {
    566             if ( isset( $r[ $uid ] ) ) {
    567                 $this->results[ $uid ] = $r[ $uid ];
    568 
    569                 // The BP template functions expect an 'id'
    570                 // (as opposed to 'ID') property
    571                 $this->results[ $uid ]->id = $uid;
    572             }
    573         }
    574     }
    575 
    576     /**
    577      * Fetch the IDs of users to put in the IN clause of the main query.
    578      *
    579      * By default, returns the value passed to it
    580      * ($this->query_vars['include']). Having this abstracted into a
    581      * standalone method means that extending classes can override the
    582      * logic, parsing together their own user_id limits with the 'include'
    583      * ids passed to the class constructor. See {@link BP_Group_Member_Query}
    584      * for an example.
    585      *
    586      * @since BuddyPress (1.8.0)
    587      *
    588      * @param array Sanitized array of user IDs, as passed to the 'include'
    589      *        parameter of the class constructor.
    590      * @return array The list of users to which the main query should be
    591      *         limited.
    592      */
    593     public function get_include_ids( $include = array() ) {
    594         return $include;
    595     }
    596 
    597     /**
    598      * Perform a database query to populate any extra metadata we might need.
    599      *
    600      * Different components will hook into the 'bp_user_query_populate_extras'
    601      * action to loop in the things they want.
    602      *
    603      * @since BuddyPress (1.7.0)
    604      *
    605      * @global WPDB $wpdb Global WordPress database access object.
    606      */
    607     public function populate_extras() {
    608         global $wpdb;
    609 
    610         // Bail if no users
    611         if ( empty( $this->user_ids ) || empty( $this->results ) ) {
    612             return;
    613         }
    614 
    615         // Bail if the populate_extras flag is set to false
    616         // In the case of the 'popular' sort type, we force
    617         // populate_extras to true, because we need the friend counts
    618         if ( 'popular' == $this->query_vars['type'] ) {
    619             $this->query_vars['populate_extras'] = 1;
    620         }
    621 
    622         if ( ! (bool) $this->query_vars['populate_extras'] ) {
    623             return;
    624         }
    625 
    626         // Turn user ID's into a query-usable, comma separated value
    627         $user_ids_sql = implode( ',', wp_parse_id_list( $this->user_ids ) );
    628 
    629         /**
    630          * Use this action to independently populate your own custom extras.
    631          *
    632          * Note that anything you add here should query using $user_ids_sql, to
    633          * avoid running multiple queries per user in the loop.
    634          *
    635          * Two BuddyPress components currently do this:
    636          * - XProfile: To override display names
    637          * - Friends:  To set whether or not a user is the current users friend
    638          *
    639          * @see bp_xprofile_filter_user_query_populate_extras()
    640          * @see bp_friends_filter_user_query_populate_extras()
    641          */
    642         do_action_ref_array( 'bp_user_query_populate_extras', array( $this, $user_ids_sql ) );
    643 
    644         // Fetch last_active data from the activity table
    645         $last_activities = BP_Core_User::get_last_activity( $this->user_ids );
    646 
    647         // Set a last_activity value for each user, even if it's empty
    648         foreach ( $this->results as $user_id => $user ) {
    649             $user_last_activity = isset( $last_activities[ $user_id ] ) ? $last_activities[ $user_id ]['date_recorded'] : '';
    650             $this->results[ $user_id ]->last_activity = $user_last_activity;
    651         }
    652 
    653         // Fetch usermeta data
    654         // We want the three following pieces of info from usermeta:
    655         // - friend count
    656         // - latest update
    657         $total_friend_count_key = bp_get_user_meta_key( 'total_friend_count' );
    658         $bp_latest_update_key   = bp_get_user_meta_key( 'bp_latest_update'   );
    659 
    660         // total_friend_count must be set for each user, even if its
    661         // value is 0
    662         foreach ( $this->results as $uindex => $user ) {
    663             $this->results[$uindex]->total_friend_count = 0;
    664         }
    665 
    666         // Create, prepare, and run the separate usermeta query
    667         $user_metas = $wpdb->get_results( $wpdb->prepare( "SELECT user_id, meta_key, meta_value FROM {$wpdb->usermeta} WHERE meta_key IN (%s,%s) AND user_id IN ({$user_ids_sql})", $total_friend_count_key, $bp_latest_update_key ) );
    668 
    669         // The $members_template global expects the index key to be different
    670         // from the meta_key in some cases, so we rejig things here.
    671         foreach ( $user_metas as $user_meta ) {
    672             switch ( $user_meta->meta_key ) {
    673                 case $total_friend_count_key :
    674                     $key = 'total_friend_count';
    675                     break;
    676 
    677                 case $bp_latest_update_key :
    678                     $key = 'latest_update';
    679                     break;
    680             }
    681 
    682             if ( isset( $this->results[ $user_meta->user_id ] ) ) {
    683                 $this->results[ $user_meta->user_id ]->{$key} = $user_meta->meta_value;
    684             }
    685         }
    686 
    687         // When meta_key or meta_value have been passed to the query,
    688         // fetch the resulting values for use in the template functions
    689         if ( ! empty( $this->query_vars['meta_key'] ) ) {
    690             $meta_sql = array(
    691                 'select' => "SELECT user_id, meta_key, meta_value",
    692                 'from'   => "FROM $wpdb->usermeta",
    693                 'where'  => $wpdb->prepare( "WHERE meta_key = %s", $this->query_vars['meta_key'] )
    694             );
    695 
    696             if ( false !== $this->query_vars['meta_value'] ) {
    697                 $meta_sql['where'] .= $wpdb->prepare( " AND meta_value = %s", $this->query_vars['meta_value'] );
    698             }
    699 
    700             $metas = $wpdb->get_results( "{$meta_sql['select']} {$meta_sql['from']} {$meta_sql['where']}" );
    701 
    702             if ( ! empty( $metas ) ) {
    703                 foreach ( $metas as $meta ) {
    704                     if ( isset( $this->results[ $meta->user_id ] ) ) {
    705                         $this->results[ $meta->user_id ]->meta_key = $meta->meta_key;
    706 
    707                         if ( ! empty( $meta->meta_value ) ) {
    708                             $this->results[ $meta->user_id ]->meta_value = $meta->meta_value;
    709                         }
    710                     }
    711                 }
    712             }
    713         }
    714     }
    715 }
    716 
    717 /**
    718  * Fetch data about a BuddyPress user.
    719  *
    720  * BP_Core_User class can be used by any component. It will fetch useful
    721  * details for any user when provided with a user_id.
    722  *
    723  * Example:
    724  *    $user = new BP_Core_User( $user_id );
    725  *    $user_avatar = $user->avatar;
    726  *    $user_email = $user->email;
    727  *    $user_status = $user->status;
    728  *    etc.
    729  */
    730 class BP_Core_User {
    731 
    732     /**
    733      * ID of the user which the object relates to.
    734      *
    735      * @var integer
    736      */
    737     public $id;
    738 
    739     /**
    740      * The URL to the full size of the avatar for the user.
    741      *
    742      * @var string
    743      */
    744     public $avatar;
    745 
    746     /**
    747      * The URL to the thumb size of the avatar for the user.
    748      *
    749      * @var string
    750      */
    751     public $avatar_thumb;
    752 
    753     /**
    754      * The URL to the mini size of the avatar for the user.
    755      *
    756      * @var string
    757      */
    758     public $avatar_mini;
    759 
    760     /**
    761      * The full name of the user
    762      *
    763      * @var string
    764      */
    765     public $fullname;
    766 
    767     /**
    768      * The email for the user.
    769      *
    770      * @var string
    771      */
    772     public $email;
    773 
    774     /**
    775      * The absolute url for the user's profile.
    776      *
    777      * @var string
    778      */
    779     public $user_url;
    780 
    781     /**
    782      * The HTML for the user link, with the link text being the user's full name.
    783      *
    784      * @var string
    785      */
    786     public $user_link;
    787 
    788     /**
    789      * Contains a formatted string when the last time the user was active.
    790      *
    791      * Example: "active 2 hours and 50 minutes ago"
    792      *
    793      * @var string
    794      */
    795     public $last_active;
    796 
    797     /* Extras */
    798 
    799     /**
    800      * The total number of "Friends" the user has on site.
    801      *
    802      * @var integer
    803      */
    804     public $total_friends;
    805 
    806     /**
    807      * The total number of blog posts posted by the user
    808      *
    809      * @var integer
    810      * @deprecated No longer used
    811      */
    812     public $total_blogs;
    813 
    814     /**
    815      * The total number of groups the user is a part of.
    816      *
    817      * Example: "1 group", "2 groups"
    818      *
    819      * @var string
    820      */
    821     public $total_groups;
    822 
    823     /**
    824      * Profile information for the specific user.
    825      *
    826      * @since BuddyPress (1.2.0)
    827      * @var array
    828      */
    829     public $profile_data;
    830 
    831     /** Public Methods *******************************************************/
    832 
    833     /**
    834      * Class constructor.
    835      *
    836      * @param integer $user_id The ID for the user being queried.
    837      * @param bool $populate_extras Whether to fetch extra information
    838      *        such as group/friendship counts or not. Default: false.
    839      */
    840     public function __construct( $user_id, $populate_extras = false ) {
    841         if ( !empty( $user_id ) ) {
    842             $this->id = $user_id;
    843             $this->populate();
    844 
    845             if ( !empty( $populate_extras ) ) {
    846                 $this->populate_extras();
    847             }
    848         }
    849     }
    850 
    851     /**
    852      * Populate the instantiated class with data based on the User ID provided.
    853      *
    854      * @uses bp_core_get_userurl() Returns the URL with no HTML markup for
    855      *       a user based on their user id.
    856      * @uses bp_core_get_userlink() Returns a HTML formatted link for a
    857      *       user with the user's full name as the link text.
    858      * @uses bp_core_get_user_email() Returns the email address for the
    859      *       user based on user ID.
    860      * @uses bp_get_user_meta() BP function returns the value of passed
    861      *       usermeta name from usermeta table.
    862      * @uses bp_core_fetch_avatar() Returns HTML formatted avatar for a user
    863      * @uses bp_profile_last_updated_date() Returns the last updated date
    864      *       for a user.
    865      */
    866     public function populate() {
    867 
    868         if ( bp_is_active( 'xprofile' ) )
    869             $this->profile_data = $this->get_profile_data();
    870 
    871         if ( !empty( $this->profile_data ) ) {
    872             $full_name_field_name = bp_xprofile_fullname_field_name();
    873 
    874             $this->user_url  = bp_core_get_user_domain( $this->id, $this->profile_data['user_nicename'], $this->profile_data['user_login'] );
    875             $this->fullname  = esc_attr( $this->profile_data[$full_name_field_name]['field_data'] );
    876             $this->user_link = "<a href='{$this->user_url}' title='{$this->fullname}'>{$this->fullname}</a>";
    877             $this->email     = esc_attr( $this->profile_data['user_email'] );
    878         } else {
    879             $this->user_url  = bp_core_get_user_domain( $this->id );
    880             $this->user_link = bp_core_get_userlink( $this->id );
    881             $this->fullname  = esc_attr( bp_core_get_user_displayname( $this->id ) );
    882             $this->email     = esc_attr( bp_core_get_user_email( $this->id ) );
    883         }
    884 
    885         // Cache a few things that are fetched often
    886         wp_cache_set( 'bp_user_fullname_' . $this->id, $this->fullname, 'bp' );
    887         wp_cache_set( 'bp_user_email_' . $this->id, $this->email, 'bp' );
    888         wp_cache_set( 'bp_user_url_' . $this->id, $this->user_url, 'bp' );
    889 
    890         $this->avatar       = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'full', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->fullname ) ) );
    891         $this->avatar_thumb = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->fullname ) ) );
    892         $this->avatar_mini  = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->fullname ), 'width' => 30, 'height' => 30 ) );
    893         $this->last_active  = bp_core_get_last_activity( bp_get_user_last_activity( $this->id ), __( 'active %s', 'buddypress' ) );
    894     }
    895 
    896     /**
    897      * Populates extra fields such as group and friendship counts.
    898      */
    899     public function populate_extras() {
    900 
    901         if ( bp_is_active( 'friends' ) ) {
    902             $this->total_friends = BP_Friends_Friendship::total_friend_count( $this->id );
    903         }
    904 
    905         if ( bp_is_active( 'groups' ) ) {
    906             $this->total_groups = BP_Groups_Member::total_group_count( $this->id );
    907             $this->total_groups = sprintf( _n( '%d group', '%d groups', $this->total_groups, 'buddypress' ), $this->total_groups );
    908         }
    909     }
    910 
    911     /**
    912      * Fetch xprofile data for the current user.
    913      *
    914      * @see BP_XProfile_ProfileData::get_all_for_user() for description of
    915      *      return value.
    916      *
    917      * @return array See {@link BP_XProfile_Profile_Data::get_all_for_user()}.
    918      */
    919     public function get_profile_data() {
    920         return BP_XProfile_ProfileData::get_all_for_user( $this->id );
    921     }
    922 
    923     /** Static Methods ********************************************************/
    924 
    925     /**
    926      * Get a list of users that match the query parameters.
    927      *
    928      * Since BuddyPress 1.7, use {@link BP_User_Query} instead.
    929      *
    930      * @deprecated 1.7.0 Use {@link BP_User_Query}.
    931      *
    932      * @see BP_User_Query for a description of parameters, most of which
    933      *      are used there in the same way.
    934      *
    935      * @param string $type See {@link BP_User_Query}.
    936      * @param int $limit See {@link BP_User_Query}. Default: 0.
    937      * @param int $page See {@link BP_User_Query}. Default: 1.
    938      * @param int $user_id See {@link BP_User_Query}. Default: 0.
    939      * @param mixed $include See {@link BP_User_Query}. Default: false.
    940      * @param string|bool $search_terms See {@link BP_User_Query}.
    941      *        Default: false.
    942      * @param bool $populate_extras See {@link BP_User_Query}.
    943      *        Default: true.
    944      * @param mixed $exclude See {@link BP_User_Query}. Default: false.
    945      * @param string|bool $meta_key See {@link BP_User_Query}.
    946      *        Default: false.
    947      * @param string|bool $meta_value See {@link BP_User_Query}.
    948      *        Default: false.
    949      * @return array {
    950      *     @type int $total_users Total number of users matched by query
    951      *           params.
    952      *     @type array $paged_users The current page of users matched by
    953      *           query params.
    954      * }
    955      */
    956     public static function get_users( $type, $limit = 0, $page = 1, $user_id = 0, $include = false, $search_terms = false, $populate_extras = true, $exclude = false, $meta_key = false, $meta_value = false ) {
    957         global $wpdb;
    958 
    959         _deprecated_function( __METHOD__, '1.7', 'BP_User_Query' );
    960 
    961         $bp = buddypress();
    962 
    963         $sql = array();
    964 
    965         $sql['select_main'] = "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.display_name, u.user_email";
    966 
    967         if ( 'active' == $type || 'online' == $type || 'newest' == $type  ) {
    968             $sql['select_active'] = ", um.meta_value as last_activity";
    969         }
    970 
    971         if ( 'popular' == $type ) {
    972             $sql['select_popular'] = ", um.meta_value as total_friend_count";
    973         }
    974 
    975         if ( 'alphabetical' == $type ) {
    976             $sql['select_alpha'] = ", pd.value as fullname";
    977         }
    978 
    979         if ( $meta_key ) {
    980             $sql['select_meta'] = ", umm.meta_key";
    981 
    982             if ( $meta_value ) {
    983                 $sql['select_meta'] .= ", umm.meta_value";
    984             }
    985         }
    986 
    987         $sql['from'] = "FROM {$wpdb->users} u LEFT JOIN {$wpdb->usermeta} um ON um.user_id = u.ID";
    988 
    989         // We search against xprofile fields, so we must join the table
    990         if ( $search_terms && bp_is_active( 'xprofile' ) ) {
    991             $sql['join_profiledata_search'] = "LEFT JOIN {$bp->profile->table_name_data} spd ON u.ID = spd.user_id";
    992         }
    993 
    994         // Alphabetical sorting is done by the xprofile Full Name field
    995         if ( 'alphabetical' == $type ) {
    996             $sql['join_profiledata_alpha'] = "LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id";
    997         }
    998 
    999         if ( $meta_key ) {
    1000             $sql['join_meta'] = "LEFT JOIN {$wpdb->usermeta} umm ON umm.user_id = u.ID";
    1001         }
    1002 
    1003         $sql['where'] = 'WHERE ' . bp_core_get_status_sql( 'u.' );
    1004 
    1005         if ( 'active' == $type || 'online' == $type || 'newest' == $type ) {
    1006             $sql['where_active'] = $wpdb->prepare( "AND um.meta_key = %s", bp_get_user_meta_key( 'last_activity' ) );
    1007         }
    1008 
    1009         if ( 'popular' == $type ) {
    1010             $sql['where_popular'] = $wpdb->prepare( "AND um.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) );
    1011         }
    1012 
    1013         if ( 'online' == $type ) {
    1014             $sql['where_online'] = "AND DATE_ADD( um.meta_value, INTERVAL 5 MINUTE ) >= UTC_TIMESTAMP()";
    1015         }
    1016 
    1017         if ( 'alphabetical' == $type ) {
    1018             $sql['where_alpha'] = "AND pd.field_id = 1";
    1019         }
    1020 
    1021         if ( !empty( $exclude ) ) {
    1022             $exclude              = implode( ',', wp_parse_id_list( $exclude ) );
    1023             $sql['where_exclude'] = "AND u.ID NOT IN ({$exclude})";
    1024         }
    1025 
    1026         // Passing an $include value of 0 or '0' will necessarily result in an empty set
    1027         // returned. The default value of false will hit the 'else' clause.
    1028         if ( 0 === $include || '0' === $include ) {
    1029             $sql['where_users'] = "AND 0 = 1";
    1030         } else {
    1031             if ( !empty( $include ) ) {
    1032                 $include = implode( ',',  wp_parse_id_list( $include ) );
    1033                 $sql['where_users'] = "AND u.ID IN ({$include})";
    1034             } elseif ( !empty( $user_id ) && bp_is_active( 'friends' ) ) {
    1035                 $friend_ids = friends_get_friend_user_ids( $user_id );
    1036 
    1037                 if ( !empty( $friend_ids ) ) {
    1038                     $friend_ids = implode( ',', wp_parse_id_list( $friend_ids ) );
    1039                     $sql['where_friends'] = "AND u.ID IN ({$friend_ids})";
    1040 
    1041                 // User has no friends, return false since there will be no users to fetch.
    1042                 } else {
    1043                     return false;
    1044                 }
    1045             }
    1046         }
    1047 
    1048         if ( !empty( $search_terms ) && bp_is_active( 'xprofile' ) ) {
    1049             $search_terms_like        = '%' . bp_esc_like( $search_terms ) . '%';
    1050             $sql['where_searchterms'] = $wpdb->prepare( "AND spd.value LIKE %s", $search_terms_like );
    1051         }
    1052 
    1053         if ( !empty( $meta_key ) ) {
    1054             $sql['where_meta'] = $wpdb->prepare( " AND umm.meta_key = %s", $meta_key );
    1055 
    1056             // If a meta value is provided, match it
    1057             if ( $meta_value ) {
    1058                 $sql['where_meta'] .= $wpdb->prepare( " AND umm.meta_value = %s", $meta_value );
    1059             }
    1060         }
    1061 
    1062         switch ( $type ) {
    1063             case 'active': case 'online': default:
    1064                 $sql[] = "ORDER BY um.meta_value DESC";
    1065                 break;
    1066             case 'newest':
    1067                 $sql[] = "ORDER BY u.ID DESC";
    1068                 break;
    1069             case 'alphabetical':
    1070                 $sql[] = "ORDER BY pd.value ASC";
    1071                 break;
    1072             case 'random':
    1073                 $sql[] = "ORDER BY rand()";
    1074                 break;
    1075             case 'popular':
    1076                 $sql[] = "ORDER BY CONVERT(um.meta_value, SIGNED) DESC";
    1077                 break;
    1078         }
    1079 
    1080         if ( !empty( $limit ) && !empty( $page ) ) {
    1081             $sql['pagination'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
    1082         }
    1083 
    1084         // Get paginated results
    1085         $paged_users_sql = apply_filters( 'bp_core_get_paged_users_sql', join( ' ', (array) $sql ), $sql );
    1086         $paged_users     = $wpdb->get_results( $paged_users_sql );
    1087 
    1088         // Re-jig the SQL so we can get the total user count
    1089         unset( $sql['select_main'] );
    1090 
    1091         if ( !empty( $sql['select_active'] ) ) {
    1092             unset( $sql['select_active'] );
    1093         }
    1094 
    1095         if ( !empty( $sql['select_popular'] ) ) {
    1096             unset( $sql['select_popular'] );
    1097         }
    1098 
    1099         if ( !empty( $sql['select_alpha'] ) ) {
    1100             unset( $sql['select_alpha'] );
    1101         }
    1102 
    1103         if ( !empty( $sql['pagination'] ) ) {
    1104             unset( $sql['pagination'] );
    1105         }
    1106 
    1107         array_unshift( $sql, "SELECT COUNT(u.ID)" );
    1108 
    1109         // Get total user results
    1110         $total_users_sql = apply_filters( 'bp_core_get_total_users_sql', join( ' ', (array) $sql ), $sql );
    1111         $total_users     = $wpdb->get_var( $total_users_sql );
    1112 
    1113         /***
    1114          * Lets fetch some other useful data in a separate queries, this will be faster than querying the data for every user in a list.
    1115          * We can't add these to the main query above since only users who have this information will be returned (since the much of the data is in usermeta and won't support any type of directional join)
    1116          */
    1117         if ( !empty( $populate_extras ) ) {
    1118             $user_ids = array();
    1119 
    1120             foreach ( (array) $paged_users as $user ) {
    1121                 $user_ids[] = $user->id;
    1122             }
    1123 
    1124             // Add additional data to the returned results
    1125             $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids, $type );
    1126         }
    1127 
    1128         return array( 'users' => $paged_users, 'total' => $total_users );
    1129     }
    1130 
    1131 
    1132     /**
    1133      * Fetch the details for all users whose usernames start with the given letter.
    1134      *
    1135      * @global wpdb $wpdb WordPress database object.
    1136      *
    1137      * @param string $letter The letter the users names are to start with.
    1138      * @param int $limit The number of users we wish to retrive.
    1139      * @param int $page The page number we are currently on, used in
    1140      *        conjunction with $limit to get the start position for the
    1141      *        limit.
    1142      * @param bool $populate_extras Populate extra user fields?
    1143      * @param string $exclude Comma-separated IDs of users whose results
    1144      *        aren't to be fetched.
    1145      * @return mixed False on error, otherwise associative array of results.
    1146      */
    1147     public static function get_users_by_letter( $letter, $limit = null, $page = 1, $populate_extras = true, $exclude = '' ) {
    1148         global $wpdb;
    1149 
    1150         $pag_sql = '';
    1151         if ( $limit && $page ) {
    1152             $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
    1153         }
    1154 
    1155         // Multibyte compliance
    1156         if ( function_exists( 'mb_strlen' ) ) {
    1157             if ( mb_strlen( $letter, 'UTF-8' ) > 1 || is_numeric( $letter ) || !$letter ) {
    1158                 return false;
    1159             }
    1160         } else {
    1161             if ( strlen( $letter ) > 1 || is_numeric( $letter ) || !$letter ) {
    1162                 return false;
    1163             }
    1164         }
    1165 
    1166         $bp = buddypress();
    1167 
    1168         $letter_like = bp_esc_like( $letter ) . '%';
    1169         $status_sql  = bp_core_get_status_sql( 'u.' );
    1170 
    1171         if ( !empty( $exclude ) ) {
    1172             $exclude     = implode( ',', wp_parse_id_list( $exclude ) );
    1173             $exclude_sql = " AND u.id NOT IN ({$exclude})";
    1174         } else {
    1175             $exclude_sql = '';
    1176         }
    1177 
    1178         $total_users_sql = apply_filters( 'bp_core_users_by_letter_count_sql', $wpdb->prepare( "SELECT COUNT(DISTINCT u.ID) FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id LEFT JOIN {$bp->profile->table_name_fields} pf ON pd.field_id = pf.id WHERE {$status_sql} AND pf.name = %s {$exclude_sql} AND pd.value LIKE %s ORDER BY pd.value ASC", bp_xprofile_fullname_field_name(), $letter_like ) );
    1179         $paged_users_sql = apply_filters( 'bp_core_users_by_letter_sql',       $wpdb->prepare( "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.user_email FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id LEFT JOIN {$bp->profile->table_name_fields} pf ON pd.field_id = pf.id WHERE {$status_sql} AND pf.name = %s {$exclude_sql} AND pd.value LIKE %s ORDER BY pd.value ASC{$pag_sql}", bp_xprofile_fullname_field_name(), $letter_like ) );
    1180 
    1181         $total_users = $wpdb->get_var( $total_users_sql );
    1182         $paged_users = $wpdb->get_results( $paged_users_sql );
    1183 
    1184         /***
    1185          * Lets fetch some other useful data in a separate queries, this will be
    1186          * faster than querying the data for every user in a list. We can't add
    1187          * these to the main query above since only users who have this
    1188          * information will be returned (since the much of the data is in
    1189          * usermeta and won't support any type of directional join)
    1190          */
    1191         $user_ids = array();
    1192         foreach ( (array) $paged_users as $user )
    1193             $user_ids[] = (int) $user->id;
    1194 
    1195         // Add additional data to the returned results
    1196         if ( $populate_extras ) {
    1197             $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids );
    1198         }
    1199 
    1200         return array( 'users' => $paged_users, 'total' => $total_users );
    1201     }
    1202 
    1203     /**
    1204      * Get details of specific users from the database.
    1205      *
    1206      * Use {@link BP_User_Query} with the 'user_ids' param instead.
    1207      *
    1208      * @global wpdb $wpdb WordPress database object.
    1209      * @param array $user_ids The user IDs of the users who we wish to
    1210      *        fetch information on.
    1211      * @param int $limit The limit of results we want.
    1212      * @param int $page The page we are on for pagination.
    1213      * @param bool $populate_extras Populate extra user fields?
    1214      * @return array Associative array.
    1215      */
    1216     public static function get_specific_users( $user_ids, $limit = null, $page = 1, $populate_extras = true ) {
    1217         global $wpdb;
    1218 
    1219         $pag_sql = '';
    1220         if ( $limit && $page )
    1221             $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
    1222 
    1223         $user_ids   = implode( ',', wp_parse_id_list( $user_ids ) );
    1224         $status_sql = bp_core_get_status_sql();
    1225 
    1226         $total_users_sql = apply_filters( 'bp_core_get_specific_users_count_sql', "SELECT COUNT(ID) FROM {$wpdb->users} WHERE {$status_sql} AND ID IN ({$user_ids})" );
    1227         $paged_users_sql = apply_filters( 'bp_core_get_specific_users_count_sql', "SELECT ID as id, user_registered, user_nicename, user_login, user_email FROM {$wpdb->users} WHERE {$status_sql} AND ID IN ({$user_ids}) {$pag_sql}" );
    1228 
    1229         $total_users = $wpdb->get_var( $total_users_sql );
    1230         $paged_users = $wpdb->get_results( $paged_users_sql );
    1231 
    1232         /***
    1233          * Lets fetch some other useful data in a separate queries, this will be
    1234          * faster than querying the data for every user in a list. We can't add
    1235          * these to the main query above since only users who have this
    1236          * information will be returned (since the much of the data is in
    1237          * usermeta and won't support any type of directional join)
    1238          */
    1239 
    1240         // Add additional data to the returned results
    1241         if ( !empty( $populate_extras ) ) {
    1242             $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids );
    1243         }
    1244 
    1245         return array( 'users' => $paged_users, 'total' => $total_users );
    1246     }
    1247 
    1248     /**
    1249      * Find users who match on the value of an xprofile data.
    1250      *
    1251      * @global wpdb $wpdb WordPress database object.
    1252      *
    1253      * @param string $search_terms The terms to search the profile table
    1254      *        value column for.
    1255      * @param integer $limit The limit of results we want.
    1256      * @param integer $page The page we are on for pagination.
    1257      * @param boolean $populate_extras Populate extra user fields?
    1258      * @return array Associative array.
    1259      */
    1260     public static function search_users( $search_terms, $limit = null, $page = 1, $populate_extras = true ) {
    1261         global $wpdb;
    1262 
    1263         $bp = buddypress();
    1264 
    1265         $user_ids = array();
    1266         $pag_sql  = $limit && $page ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * intval( $limit ) ), intval( $limit ) ) : '';
    1267 
    1268         $search_terms_like = '%' . bp_esc_like( $search_terms ) . '%';
    1269         $status_sql        = bp_core_get_status_sql( 'u.' );
    1270 
    1271         $total_users_sql = apply_filters( 'bp_core_search_users_count_sql', $wpdb->prepare( "SELECT COUNT(DISTINCT u.ID) as id FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id WHERE {$status_sql} AND pd.value LIKE %s ORDER BY pd.value ASC", $search_terms_like ), $search_terms );
    1272         $paged_users_sql = apply_filters( 'bp_core_search_users_sql',       $wpdb->prepare( "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.user_email FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id WHERE {$status_sql} AND pd.value LIKE %s ORDER BY pd.value ASC{$pag_sql}", $search_terms_like ), $search_terms, $pag_sql );
    1273 
    1274         $total_users = $wpdb->get_var( $total_users_sql );
    1275         $paged_users = $wpdb->get_results( $paged_users_sql );
    1276 
    1277         /***
    1278          * Lets fetch some other useful data in a separate queries, this will be faster than querying the data for every user in a list.
    1279          * We can't add these to the main query above since only users who have this information will be returned (since the much of the data is in usermeta and won't support any type of directional join)
    1280          */
    1281         foreach ( (array) $paged_users as $user )
    1282             $user_ids[] = $user->id;
    1283 
    1284         // Add additional data to the returned results
    1285         if ( $populate_extras )
    1286             $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids );
    1287 
    1288         return array( 'users' => $paged_users, 'total' => $total_users );
    1289     }
    1290 
    1291     /**
    1292      * Fetch extra user information, such as friend count and last profile update message.
    1293      *
    1294      * Accepts multiple user IDs to fetch data for.
    1295      *
    1296      * @global wpdb $wpdb WordPress database object.
    1297      *
    1298      * @param array $paged_users An array of stdClass containing the users.
    1299      * @param string $user_ids The user ids to select information about.
    1300      * @param string $type The type of fields we wish to get.
    1301      * @return mixed False on error, otherwise associative array of results.
    1302      */
    1303     public static function get_user_extras( &$paged_users, &$user_ids, $type = false ) {
    1304         global $wpdb;
    1305 
    1306         $bp = buddypress();
    1307 
    1308         if ( empty( $user_ids ) )
    1309             return $paged_users;
    1310 
    1311         // Sanitize user IDs
    1312         $user_ids = implode( ',', wp_parse_id_list( $user_ids ) );
    1313 
    1314         // Fetch the user's full name
    1315         if ( bp_is_active( 'xprofile' ) && 'alphabetical' != $type ) {
    1316             $names = $wpdb->get_results( $wpdb->prepare( "SELECT pd.user_id as id, pd.value as fullname FROM {$bp->profile->table_name_fields} pf, {$bp->profile->table_name_data} pd WHERE pf.id = pd.field_id AND pf.name = %s AND pd.user_id IN ( {$user_ids} )", bp_xprofile_fullname_field_name() ) );
    1317             for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
    1318                 foreach ( (array) $names as $name ) {
    1319                     if ( $name->id == $paged_users[$i]->id )
    1320                         $paged_users[$i]->fullname = $name->fullname;
    1321                 }
    1322             }
    1323         }
    1324 
    1325         // Fetch the user's total friend count
    1326         if ( 'popular' != $type ) {
    1327             $friend_count = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as total_friend_count FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'total_friend_count' ) ) );
    1328             for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
    1329                 foreach ( (array) $friend_count as $fcount ) {
    1330                     if ( $fcount->id == $paged_users[$i]->id )
    1331                         $paged_users[$i]->total_friend_count = (int) $fcount->total_friend_count;
    1332                 }
    1333             }
    1334         }
    1335 
    1336         // Fetch whether or not the user is a friend
    1337         if ( bp_is_active( 'friends' ) ) {
    1338             $friend_status = $wpdb->get_results( $wpdb->prepare( "SELECT initiator_user_id, friend_user_id, is_confirmed FROM {$bp->friends->table_name} WHERE (initiator_user_id = %d AND friend_user_id IN ( {$user_ids} ) ) OR (initiator_user_id IN ( {$user_ids} ) AND friend_user_id = %d )", bp_loggedin_user_id(), bp_loggedin_user_id() ) );
    1339             for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
    1340                 foreach ( (array) $friend_status as $status ) {
    1341                     if ( $status->initiator_user_id == $paged_users[$i]->id || $status->friend_user_id == $paged_users[$i]->id )
    1342                         $paged_users[$i]->is_friend = $status->is_confirmed;
    1343                 }
    1344             }
    1345         }
    1346 
    1347         if ( 'active' != $type ) {
    1348             $user_activity = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as last_activity FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'last_activity' ) ) );
    1349             for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
    1350                 foreach ( (array) $user_activity as $activity ) {
    1351                     if ( $activity->id == $paged_users[$i]->id )
    1352                         $paged_users[$i]->last_activity = $activity->last_activity;
    1353                 }
    1354             }
    1355         }
    1356 
    1357         // Fetch the user's last_activity
    1358         if ( 'active' != $type ) {
    1359             $user_activity = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as last_activity FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'last_activity' ) ) );
    1360             for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
    1361                 foreach ( (array) $user_activity as $activity ) {
    1362                     if ( $activity->id == $paged_users[$i]->id )
    1363                         $paged_users[$i]->last_activity = $activity->last_activity;
    1364                 }
    1365             }
    1366         }
    1367 
    1368         // Fetch the user's latest update
    1369         $user_update = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as latest_update FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'bp_latest_update' ) ) );
    1370         for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
    1371             foreach ( (array) $user_update as $update ) {
    1372                 if ( $update->id == $paged_users[$i]->id )
    1373                     $paged_users[$i]->latest_update = $update->latest_update;
    1374             }
    1375         }
    1376 
    1377         return $paged_users;
    1378     }
    1379 
    1380     /**
    1381      * Get WordPress user details for a specified user.
    1382      *
    1383      * @global wpdb $wpdb WordPress database object.
    1384      *
    1385      * @param integer $user_id User ID.
    1386      * @return array Associative array.
    1387      */
    1388     public static function get_core_userdata( $user_id ) {
    1389         global $wpdb;
    1390 
    1391         if ( !$user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->users} WHERE ID = %d LIMIT 1", $user_id ) ) )
    1392             return false;
    1393 
    1394         return $user;
    1395     }
    1396 
    1397     /**
    1398      * Get last activity data for a user or set of users.
    1399      *
    1400      * @param int|array User IDs or multiple user IDs.
    1401      * @return array
    1402      */
    1403     public static function get_last_activity( $user_id ) {
    1404         global $wpdb;
    1405 
    1406         // Sanitize and remove empty values
    1407         $user_ids = array_filter( wp_parse_id_list( $user_id ) );
    1408 
    1409         if ( empty( $user_ids ) ) {
    1410             return false;
    1411         }
    1412 
    1413         $uncached_user_ids = bp_get_non_cached_ids( $user_ids, 'bp_last_activity' );
    1414         if ( ! empty( $uncached_user_ids ) ) {
    1415             $bp = buddypress();
    1416 
    1417             $user_ids_sql = implode( ',', $uncached_user_ids );
    1418             $user_count   = count( $uncached_user_ids );
    1419 
    1420             $last_activities = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, date_recorded FROM {$bp->members->table_name_last_activity} WHERE component = %s AND type = 'last_activity' AND user_id IN ({$user_ids_sql}) LIMIT {$user_count}", $bp->members->id ) );
    1421 
    1422             foreach ( $last_activities as $last_activity ) {
    1423                 wp_cache_set( $last_activity->user_id, array(
    1424                     'user_id'       => $last_activity->user_id,
    1425                     'date_recorded' => $last_activity->date_recorded,
    1426                     'activity_id'   => $last_activity->id,
    1427                 ), 'bp_last_activity' );
    1428             }
    1429         }
    1430 
    1431         // Fetch all user data from the cache
    1432         $retval = array();
    1433         foreach ( $user_ids as $user_id ) {
    1434             $retval[ $user_id ] = wp_cache_get( $user_id, 'bp_last_activity' );
    1435         }
    1436 
    1437         return $retval;
    1438     }
    1439 
    1440     /**
    1441      * Set a user's last_activity value.
    1442      *
    1443      * Will create a new entry if it does not exist. Otherwise updates the
    1444      * existing entry.
    1445      *
    1446      * @since 2.0
    1447      *
    1448      * @param int $user_id ID of the user whose last_activity you are updating.
    1449      * @param string $time MySQL-formatted time string.
    1450      * @return bool True on success, false on failure.
    1451      */
    1452     public static function update_last_activity( $user_id, $time ) {
    1453         global $wpdb;
    1454 
    1455         $table_name = buddypress()->members->table_name_last_activity;
    1456 
    1457         $activity = self::get_last_activity( $user_id );
    1458 
    1459         if ( ! empty( $activity[ $user_id ] ) ) {
    1460             $updated = $wpdb->update(
    1461                 $table_name,
    1462 
    1463                 // Data to update
    1464                 array(
    1465                     'date_recorded' => $time,
    1466                 ),
    1467 
    1468                 // WHERE
    1469                 array(
    1470                     'id' => $activity[ $user_id ]['activity_id'],
    1471                 ),
    1472 
    1473                 // Data sanitization format
    1474                 array(
    1475                     '%s',
    1476                 ),
    1477 
    1478                 // WHERE sanitization format
    1479                 array(
    1480                     '%d',
    1481                 )
    1482             );
    1483 
    1484             // add new date to existing activity entry for caching
    1485             $activity[ $user_id ]['date_recorded'] = $time;
    1486 
    1487         } else {
    1488             $updated = $wpdb->insert(
    1489                 $table_name,
    1490 
    1491                 // Data
    1492                 array(
    1493                     'user_id'       => $user_id,
    1494                     'component'     => buddypress()->members->id,
    1495                     'type'          => 'last_activity',
    1496                     'action'        => '',
    1497                     'content'       => '',
    1498                     'primary_link'  => '',
    1499                     'item_id'       => 0,
    1500                     'date_recorded' => $time,
    1501                 ),
    1502 
    1503                 // Data sanitization format
    1504                 array(
    1505                     '%d',
    1506                     '%s',
    1507                     '%s',
    1508                     '%s',
    1509                     '%s',
    1510                     '%s',
    1511                     '%d',
    1512                     '%s',
    1513                 )
    1514             );
    1515 
    1516             // set up activity array for caching
    1517             // view the foreach loop in the get_last_activity() method for format
    1518             $activity = array();
    1519             $activity[ $user_id ] = array(
    1520                 'user_id'       => $user_id,
    1521                 'date_recorded' => $time,
    1522                 'activity_id'   => $wpdb->insert_id,
    1523             );
    1524         }
    1525 
    1526         // set cache
    1527         wp_cache_set( $user_id, $activity[ $user_id ], 'bp_last_activity' );
    1528 
    1529         return $updated;
    1530     }
    1531 
    1532     /**
    1533      * Delete a user's last_activity value.
    1534      *
    1535      * @since 2.0
    1536      *
    1537      * @param int $user_id
    1538      * @return bool True on success, false on failure or if no last_activity
    1539      *         is found for the user.
    1540      */
    1541     public static function delete_last_activity( $user_id ) {
    1542         global $wpdb;
    1543 
    1544         $existing = self::get_last_activity( $user_id );
    1545 
    1546         if ( empty( $existing ) ) {
    1547             return false;
    1548         }
    1549 
    1550         $deleted = $wpdb->delete(
    1551             buddypress()->members->table_name_last_activity,
    1552 
    1553             // WHERE
    1554             array(
    1555                 'id' => $existing[ $user_id ]['activity_id'],
    1556             ),
    1557 
    1558             // WHERE sanitization format
    1559             array(
    1560                 '%s',
    1561             )
    1562         );
    1563 
    1564         wp_cache_delete( $user_id, 'bp_last_activity' );
    1565 
    1566         return $deleted;
    1567     }
    1568 }
    1569 
    1570 if ( class_exists( 'WP_Date_Query' ) ) :
    1571 /**
    1572  * BuddyPress date query class.
    1573  *
    1574  * Extends the {@link WP_Date_Query} class for use with BuddyPress.
    1575  *
    1576  * @since BuddyPress (2.1.0)
    1577  *
    1578  * @param array $date_query {
    1579  *     Date query arguments.  See first parameter of {@link WP_Date_Query::__construct()}.
    1580  * }
    1581  * @param string $column The DB column to query against.
    1582  */
    1583 class BP_Date_Query extends WP_Date_Query {
    1584     /**
    1585      * The column to query against. Can be changed via the query arguments.
    1586      *
    1587      * @var string
    1588      */
    1589     public $column;
    1590 
    1591     /**
    1592      * Constructor.
    1593      *
    1594      * @see WP_Date_Query::__construct()
    1595      */
    1596     public function __construct( $date_query, $column = '' ) {
    1597         if ( ! empty( $column ) ) {
    1598             $this->column = $column;
    1599             add_filter( 'date_query_valid_columns', array( $this, 'register_date_column' ) );
    1600         }
    1601 
    1602         parent::__construct( $date_query, $column );
    1603     }
    1604 
    1605     /**
    1606      * Destructor.
    1607      */
    1608     public function __destruct() {
    1609         remove_filter( 'date_query_valid_columns', array( $this, 'register_date_column' ) );
    1610     }
    1611 
    1612     /**
    1613      * Registers our date column with WP Date Query to pass validation.
    1614      *
    1615      * @param array $retval Current DB columns
    1616      * @return array
    1617      */
    1618     public function register_date_column( $retval = array() ) {
    1619         $retval[] = $this->column;
    1620         return $retval;
    1621     }
    1622 }
    1623 endif;
    1624 
    1625 /**
    1626  * BP_Core_Notification is deprecated.
    1627  *
    1628  * Use BP_Notifications_Notification instead.
    1629  *
    1630  * @package BuddyPress Core
    1631  * @deprecated since BuddyPress (1.9)
    1632  */
    1633 class BP_Core_Notification {
    1634 
    1635     /**
    1636      * The notification id
    1637      *
    1638      * @var integer
    1639      */
    1640     public $id;
    1641 
    1642     /**
    1643      * The ID to which the notification relates to within the component.
    1644      *
    1645      * @var integer
    1646      */
    1647     public $item_id;
    1648 
    1649     /**
    1650      * The secondary ID to which the notification relates to within the component.
    1651      *
    1652      * @var integer
    1653      */
    1654     public $secondary_item_id = null;
    1655 
    1656     /**
    1657      * The user ID for who the notification is for.
    1658      *
    1659      * @var integer
    1660      */
    1661     public $user_id;
    1662 
    1663     /**
    1664      * The name of the component that the notification is for.
    1665      *
    1666      * @var string
    1667      */
    1668     public $component_name;
    1669 
    1670     /**
    1671      * The action within the component which the notification is related to.
    1672      *
    1673      * @var string
    1674      */
    1675     public $component_action;
    1676 
    1677     /**
    1678      * The date the notification was created.
    1679      *
    1680      * @var string
    1681      */
    1682     public $date_notified;
    1683 
    1684     /**
    1685      * Is the notification new or has it already been read.
    1686      *
    1687      * @var boolean
    1688      */
    1689     public $is_new;
    1690 
    1691     /** Public Methods ********************************************************/
    1692 
    1693     /**
    1694      * Constructor
    1695      *
    1696      * @param integer $id
    1697      */
    1698     public function __construct( $id = 0 ) {
    1699         if ( !empty( $id ) ) {
    1700             $this->id = $id;
    1701             $this->populate();
    1702         }
    1703     }
    1704 
    1705     /**
    1706      * Update or insert notification details into the database.
    1707      *
    1708      * @global wpdb $wpdb WordPress database object
    1709      * @return bool Success or failure
    1710      */
    1711     public function save() {
    1712         global $wpdb;
    1713 
    1714         $bp = buddypress();
    1715 
    1716         // Update
    1717         if ( !empty( $this->id ) ) {
    1718             $sql = $wpdb->prepare( "UPDATE {$bp->core->table_name_notifications} SET item_id = %d, secondary_item_id = %d, user_id = %d, component_name = %s, component_action = %d, date_notified = %s, is_new = %d ) WHERE id = %d", $this->item_id, $this->secondary_item_id, $this->user_id, $this->component_name, $this->component_action, $this->date_notified, $this->is_new, $this->id );
    1719 
    1720         // Save
    1721         } else {
    1722             $sql = $wpdb->prepare( "INSERT INTO {$bp->core->table_name_notifications} ( item_id, secondary_item_id, user_id, component_name, component_action, date_notified, is_new ) VALUES ( %d, %d, %d, %s, %s, %s, %d )", $this->item_id, $this->secondary_item_id, $this->user_id, $this->component_name, $this->component_action, $this->date_notified, $this->is_new );
    1723         }
    1724 
    1725         if ( !$result = $wpdb->query( $sql ) )
    1726             return false;
    1727 
    1728         $this->id = $wpdb->insert_id;
    1729 
    1730         return true;
    1731     }
    1732 
    1733     /** Private Methods *******************************************************/
    1734 
    1735     /**
    1736      * Fetches the notification data from the database.
    1737      *
    1738      * @global wpdb $wpdb WordPress database object
    1739      */
    1740     public function populate() {
    1741         global $wpdb;
    1742 
    1743         $bp = buddypress();
    1744 
    1745         if ( $notification = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->core->table_name_notifications} WHERE id = %d", $this->id ) ) ) {
    1746             $this->item_id = $notification->item_id;
    1747             $this->secondary_item_id = $notification->secondary_item_id;
    1748             $this->user_id           = $notification->user_id;
    1749             $this->component_name    = $notification->component_name;
    1750             $this->component_action  = $notification->component_action;
    1751             $this->date_notified     = $notification->date_notified;
    1752             $this->is_new            = $notification->is_new;
    1753         }
    1754     }
    1755 
    1756     /** Static Methods ********************************************************/
    1757 
    1758     public static function check_access( $user_id, $notification_id ) {
    1759         global $wpdb;
    1760 
    1761         $bp = buddypress();
    1762 
    1763         return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->core->table_name_notifications} WHERE id = %d AND user_id = %d", $notification_id, $user_id ) );
    1764     }
    1765 
    1766     /**
    1767      * Fetches all the notifications in the database for a specific user.
    1768      *
    1769      * @global wpdb $wpdb WordPress database object
    1770      * @param integer $user_id User ID
    1771      * @param string $status 'is_new' or 'all'
    1772      * @return array Associative array
    1773      * @static
    1774      */
    1775     public static function get_all_for_user( $user_id, $status = 'is_new' ) {
    1776         global $wpdb;
    1777 
    1778         $bp = buddypress();
    1779 
    1780         $is_new = ( 'is_new' === $status )
    1781             ? ' AND is_new = 1 '
    1782             : '';
    1783 
    1784         return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->core->table_name_notifications} WHERE user_id = %d {$is_new}", $user_id ) );
    1785     }
    1786 
    1787     /**
    1788      * Delete all the notifications for a user based on the component name and action.
    1789      *
    1790      * @global wpdb $wpdb WordPress database object
    1791      * @param integer $user_id
    1792      * @param string $component_name
    1793      * @param string $component_action
    1794      * @static
    1795      */
    1796     public static function delete_for_user_by_type( $user_id, $component_name, $component_action ) {
    1797         global $wpdb;
    1798 
    1799         $bp = buddypress();
    1800 
    1801         return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE user_id = %d AND component_name = %s AND component_action = %s", $user_id, $component_name, $component_action ) );
    1802     }
    1803 
    1804     /**
    1805      * Delete all the notifications that have a specific item id, component name and action.
    1806      *
    1807      * @global wpdb $wpdb WordPress database object
    1808      * @param integer $user_id The ID of the user who the notifications are for.
    1809      * @param integer $item_id The item ID of the notifications we wish to delete.
    1810      * @param string $component_name The name of the component that the notifications we wish to delete.
    1811      * @param string $component_action The action of the component that the notifications we wish to delete.
    1812      * @param integer $secondary_item_id (optional) The secondary item id of the notifications that we wish to use to delete.
    1813      * @static
    1814      */
    1815     public static function delete_for_user_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id = false ) {
    1816         global $wpdb;
    1817 
    1818         $bp = buddypress();
    1819 
    1820         $secondary_item_sql = !empty( $secondary_item_id )
    1821             ? $wpdb->prepare( " AND secondary_item_id = %d", $secondary_item_id )
    1822             : '';
    1823 
    1824         return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE user_id = %d AND item_id = %d AND component_name = %s AND component_action = %s{$secondary_item_sql}", $user_id, $item_id, $component_name, $component_action ) );
    1825     }
    1826 
    1827     /**
    1828      * Deletes all the notifications sent by a specific user, by component and action.
    1829      *
    1830      * @global wpdb $wpdb WordPress database object
    1831      * @param integer $user_id The ID of the user whose sent notifications we wish to delete.
    1832      * @param string $component_name The name of the component the notification was sent from.
    1833      * @param string $component_action The action of the component the notification was sent from.
    1834      * @static
    1835      */
    1836     public static function delete_from_user_by_type( $user_id, $component_name, $component_action ) {
    1837         global $wpdb;
    1838 
    1839         $bp = buddypress();
    1840 
    1841         return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE item_id = %d AND component_name = %s AND component_action = %s", $user_id, $component_name, $component_action ) );
    1842     }
    1843 
    1844     /**
    1845      * Deletes all the notifications for all users by item id, and optional secondary item id, and component name and action.
    1846      *
    1847      * @global wpdb $wpdb WordPress database object
    1848      * @param string $item_id The item id that they notifications are to be for.
    1849      * @param string $component_name The component that the notifications are to be from.
    1850      * @param string $component_action The action that the notifications are to be from.
    1851      * @param string $secondary_item_id Optional secondary item id that the notifications are to have.
    1852      * @static
    1853      */
    1854     public static function delete_all_by_type( $item_id, $component_name, $component_action, $secondary_item_id ) {
    1855         global $wpdb;
    1856 
    1857         if ( $component_action )
    1858             $component_action_sql = $wpdb->prepare( "AND component_action = %s", $component_action );
    1859         else
    1860             $component_action_sql = '';
    1861 
    1862         if ( $secondary_item_id )
    1863             $secondary_item_sql = $wpdb->prepare( "AND secondary_item_id = %d", $secondary_item_id );
    1864         else
    1865             $secondary_item_sql = '';
    1866 
    1867         $bp = buddypress();
    1868 
    1869         return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE item_id = %d AND component_name = %s {$component_action_sql} {$secondary_item_sql}", $item_id, $component_name ) );
    1870     }
    1871 }
    1872 
    1873 /**
    1874  * API to create BuddyPress buttons.
    1875  *
    1876  * @since BuddyPress (1.2.6)
    1877  *
    1878  * @param array $args {
    1879  *     Array of arguments.
    1880  *     @type string $id String describing the button type.
    1881  *     @type string $component The name of the component the button belongs to.
    1882  *           Default: 'core'.
    1883  *     @type bool $must_be_logged_in Optional. Does the user need to be logged
    1884  *           in to see this button? Default: true.
    1885  *     @type bool $block_self Optional. True if the button should be hidden
    1886  *           when a user is viewing his own profile. Default: true.
    1887  *     @type string|bool $wrapper Optional. HTML element type that should wrap
    1888  *           the button: 'div', 'span', 'p', or 'li'. False for no wrapper at
    1889  *           all. Default: 'div'.
    1890  *     @type string $wrapper_id Optional. DOM ID of the button wrapper element.
    1891  *           Default: ''.
    1892  *     @type string $wrapper_class Optional. DOM class of the button wrapper
    1893  *           element. Default: ''.
    1894  *     @type string $link_href Optional. Destination link of the button.
    1895  *           Default: ''.
    1896  *     @type string $link_class Optional. DOM class of the button. Default: ''.
    1897  *     @type string $link_id Optional. DOM ID of the button. Default: ''.
    1898  *     @type string $link_rel Optional. DOM 'rel' attribute of the button.
    1899  *           Default: ''.
    1900  *     @type string $link_title Optional. Title attribute of the button.
    1901  *           Default: ''.
    1902  *     @type string $link_text Optional. Text to appear on the button.
    1903  *           Default: ''.
    1904  * }
    1905  */
    1906 class BP_Button {
    1907 
    1908     /** Button properties *****************************************************/
    1909 
    1910     /**
    1911      * The button ID.
    1912      *
    1913      * @var string
    1914      */
    1915     public $id = '';
    1916 
    1917     /**
    1918      * The name of the component that the button belongs to.
    1919      *
    1920      * @var string
    1921      */
    1922     public $component = 'core';
    1923 
    1924     /**
    1925      * Does the user need to be logged in to see this button?
    1926      *
    1927      * @var bool
    1928      */
    1929     public $must_be_logged_in = true;
    1930 
    1931     /**
    1932      * Whether the button should be hidden when viewing your own profile.
    1933      *
    1934      * @var bool
    1935      */
    1936     public $block_self = true;
    1937 
    1938     /** Wrapper ***************************************************************/
    1939 
    1940     /**
    1941      * The type of DOM element to use for a wrapper.
    1942      *
    1943      * @var string|bool 'div', 'span', 'p', 'li', or false for no wrapper.
    1944      */
    1945     public $wrapper = 'div';
    1946 
    1947     /**
    1948      * The DOM class of the button wrapper.
    1949      *
    1950      * @var string
    1951      */
    1952     public $wrapper_class = '';
    1953 
    1954     /**
    1955      * The DOM ID of the button wrapper.
    1956      *
    1957      * @var string
    1958      */
    1959     public $wrapper_id = '';
    1960 
    1961     /** Button ****************************************************************/
    1962 
    1963     /**
    1964      * The destination link of the button.
    1965      *
    1966      * @var string
    1967      */
    1968     public $link_href = '';
    1969 
    1970     /**
    1971      * The DOM class of the button link.
    1972      *
    1973      * @var string
    1974      */
    1975     public $link_class = '';
    1976 
    1977     /**
    1978      * The DOM ID of the button link.
    1979      *
    1980      * @var string
    1981      */
    1982     public $link_id = '';
    1983 
    1984     /**
    1985      * The DOM rel value of the button link.
    1986      *
    1987      * @var string
    1988      */
    1989     public $link_rel = '';
    1990 
    1991     /**
    1992      * Title of the button link.
    1993      *
    1994      * @var string
    1995      */
    1996     public $link_title = '';
    1997 
    1998     /**
    1999      * The contents of the button link.
    2000      *
    2001      * @var string
    2002      */
    2003     public $link_text = '';
    2004 
    2005     /** HTML result ***********************************************************/
    2006 
    2007     public $contents = '';
    2008 
    2009     /** Methods ***************************************************************/
    2010 
    2011     /**
    2012      * Builds the button based on class parameters.
    2013      *
    2014      * @since BuddyPress (1.2.6)
    2015      *
    2016      * @param array $args See {@BP_Button}.
    2017      * @return bool|null Returns false when the button is not allowed for
    2018      *         the current context.
    2019      */
    2020     public function __construct( $args = '' ) {
    2021 
    2022         $r = wp_parse_args( $args, get_class_vars( __CLASS__ ) );
    2023 
    2024         // Required button properties
    2025         $this->id                = $r['id'];
    2026         $this->component         = $r['component'];
    2027         $this->must_be_logged_in = (bool) $r['must_be_logged_in'];
    2028         $this->block_self        = (bool) $r['block_self'];
    2029         $this->wrapper           = $r['wrapper'];
    2030 
    2031         // $id and $component are required
    2032         if ( empty( $r['id'] ) || empty( $r['component'] ) )
    2033             return false;
    2034 
    2035         // No button if component is not active
    2036         if ( ! bp_is_active( $this->component ) )
    2037             return false;
    2038 
    2039         // No button for guests if must be logged in
    2040         if ( true == $this->must_be_logged_in && ! is_user_logged_in() )
    2041             return false;
    2042 
    2043         // block_self
    2044         if ( true == $this->block_self ) {
    2045             // No button if you are the current user in a members loop
    2046             // This condition takes precedence, because members loops
    2047             // can be found on user profiles
    2048             if ( bp_get_member_user_id() ) {
    2049                 if ( is_user_logged_in() && bp_loggedin_user_id() == bp_get_member_user_id() ) {
    2050                     return false;
    2051                 }
    2052 
    2053             // No button if viewing your own profile (and not in
    2054             // a members loop)
    2055             } elseif ( bp_is_my_profile() ) {
    2056                 return false;
    2057             }
    2058         }
    2059 
    2060         // Wrapper properties
    2061         if ( false !== $this->wrapper ) {
    2062 
    2063             // Wrapper ID
    2064             if ( !empty( $r['wrapper_id'] ) ) {
    2065                 $this->wrapper_id    = ' id="' . $r['wrapper_id'] . '"';
    2066             }
    2067 
    2068             // Wrapper class
    2069             if ( !empty( $r['wrapper_class'] ) ) {
    2070                 $this->wrapper_class = ' class="generic-button ' . $r['wrapper_class'] . '"';
    2071             } else {
    2072                 $this->wrapper_class = ' class="generic-button"';
    2073             }
    2074 
    2075             // Set before and after
    2076             $before = '<' . $r['wrapper'] . $this->wrapper_class . $this->wrapper_id . '>';
    2077             $after  = '</' . $r['wrapper'] . '>';
    2078 
    2079         // No wrapper
    2080         } else {
    2081             $before = $after = '';
    2082         }
    2083 
    2084         // Link properties
    2085         if ( !empty( $r['link_id']    ) ) $this->link_id    = ' id="' .    $r['link_id']    . '"';
    2086         if ( !empty( $r['link_href']  ) ) $this->link_href  = ' href="' .  $r['link_href']  . '"';
    2087         if ( !empty( $r['link_title'] ) ) $this->link_title = ' title="' . $r['link_title'] . '"';
    2088         if ( !empty( $r['link_rel']   ) ) $this->link_rel   = ' rel="' .   $r['link_rel']   . '"';
    2089         if ( !empty( $r['link_class'] ) ) $this->link_class = ' class="' . $r['link_class'] . '"';
    2090         if ( !empty( $r['link_text']  ) ) $this->link_text  =              $r['link_text'];
    2091 
    2092         // Build the button
    2093         $this->contents = $before . '<a'. $this->link_href . $this->link_title . $this->link_id . $this->link_rel . $this->link_class . '>' . $this->link_text . '</a>' . $after;
    2094 
    2095         // Allow button to be manipulated externally
    2096         $this->contents = apply_filters( 'bp_button_' . $this->component . '_' . $this->id, $this->contents, $this, $before, $after );
    2097     }
    2098 
    2099     /**
    2100      * Return the markup for the generated button.
    2101      *
    2102      * @since BuddyPress (1.2.6)
    2103      *
    2104      * @return string Button markup.
    2105      */
    2106     public function contents() {
    2107         return $this->contents;
    2108     }
    2109 
    2110     /**
    2111      * Output the markup of button.
    2112      *
    2113      * @since BuddyPress (1.2.6)
    2114      */
    2115     public function display() {
    2116         if ( !empty( $this->contents ) )
    2117             echo $this->contents;
    2118     }
    2119 }
    2120 
    2121 /**
    2122  * Enable oEmbeds in BuddyPress contexts.
    2123  *
    2124  * Extends WP_Embed class for use with BuddyPress.
    2125  *
    2126  * @since BuddyPress (1.5.0)
    2127  *
    2128  * @see WP_Embed
    2129  */
    2130 class BP_Embed extends WP_Embed {
    2131 
    2132     /**
    2133      * Constructor
    2134      *
    2135      * @global WP_Embed $wp_embed
    2136      */
    2137     public function __construct() {
    2138         global $wp_embed;
    2139 
    2140         // Make sure we populate the WP_Embed handlers array.
    2141         // These are providers that use a regex callback on the URL in question.
    2142         // Do not confuse with oEmbed providers, which require an external ping.
    2143         // Used in WP_Embed::shortcode()
    2144         $this->handlers = $wp_embed->handlers;
    2145 
    2146         if ( bp_use_embed_in_activity() ) {
    2147             add_filter( 'bp_get_activity_content_body', array( &$this, 'autoembed' ), 8 );
    2148             add_filter( 'bp_get_activity_content_body', array( &$this, 'run_shortcode' ), 7 );
    2149         }
    2150 
    2151         if ( bp_use_embed_in_activity_replies() ) {
    2152             add_filter( 'bp_get_activity_content', array( &$this, 'autoembed' ), 8 );
    2153             add_filter( 'bp_get_activity_content', array( &$this, 'run_shortcode' ), 7 );
    2154         }
    2155 
    2156         if ( bp_use_embed_in_forum_posts() ) {
    2157             add_filter( 'bp_get_the_topic_post_content', array( &$this, 'autoembed' ), 8 );
    2158             add_filter( 'bp_get_the_topic_post_content', array( &$this, 'run_shortcode' ), 7 );
    2159         }
    2160 
    2161         if ( bp_use_embed_in_private_messages() ) {
    2162             add_filter( 'bp_get_the_thread_message_content', array( &$this, 'autoembed' ), 8 );
    2163             add_filter( 'bp_get_the_thread_message_content', array( &$this, 'run_shortcode' ), 7 );
    2164         }
    2165 
    2166         do_action_ref_array( 'bp_core_setup_oembed', array( &$this ) );
    2167     }
    2168 
    2169     /**
    2170      * The {@link do_shortcode()} callback function.
    2171      *
    2172      * Attempts to convert a URL into embed HTML. Starts by checking the
    2173      * URL against the regex of the registered embed handlers. Next, checks
    2174      * the URL against the regex of registered {@link WP_oEmbed} providers
    2175      * if oEmbed discovery is false. If none of the regex matches and it's
    2176      * enabled, then the URL will be passed to {@link BP_Embed::parse_oembed()}
    2177      * for oEmbed parsing.
    2178      *
    2179      * @uses wp_parse_args()
    2180      * @uses wp_embed_defaults()
    2181      * @uses current_user_can()
    2182      * @uses _wp_oembed_get_object()
    2183      * @uses WP_Embed::maybe_make_link()
    2184      *
    2185      * @param array $attr Shortcode attributes.
    2186      * @param string $url The URL attempting to be embeded.
    2187      * @return string The embed HTML on success, otherwise the original URL.
    2188      */
    2189     public function shortcode( $attr, $url = '' ) {
    2190         if ( empty( $url ) )
    2191             return '';
    2192 
    2193         $rawattr = $attr;
    2194         $attr = wp_parse_args( $attr, wp_embed_defaults() );
    2195 
    2196         // kses converts & into &amp; and we need to undo this
    2197         // See http://core.trac.wordpress.org/ticket/11311
    2198         $url = str_replace( '&amp;', '&', $url );
    2199 
    2200         // Look for known internal handlers
    2201         ksort( $this->handlers );
    2202         foreach ( $this->handlers as $priority => $handlers ) {
    2203             foreach ( $handlers as $hid => $handler ) {
    2204                 if ( preg_match( $handler['regex'], $url, $matches ) && is_callable( $handler['callback'] ) ) {
    2205                     if ( false !== $return = call_user_func( $handler['callback'], $matches, $attr, $url, $rawattr ) ) {
    2206                         return apply_filters( 'embed_handler_html', $return, $url, $attr );
    2207                     }
    2208                 }
    2209             }
    2210         }
    2211 
    2212         // Get object ID
    2213         $id = apply_filters( 'embed_post_id', 0 );
    2214 
    2215         // Is oEmbed discovery on?
    2216         $attr['discover'] = ( apply_filters( 'bp_embed_oembed_discover', false ) && current_user_can( 'unfiltered_html' ) );
    2217 
    2218         // Set up a new WP oEmbed object to check URL with registered oEmbed providers
    2219         require_once( ABSPATH . WPINC . '/class-oembed.php' );
    2220         $oembed_obj = _wp_oembed_get_object();
    2221 
    2222         // If oEmbed discovery is true, skip oEmbed provider check
    2223         $is_oembed_link = false;
    2224         if ( !$attr['discover'] ) {
    2225             foreach ( (array) $oembed_obj->providers as $provider_matchmask => $provider ) {
    2226                 $regex = ( $is_regex = $provider[1] ) ? $provider_matchmask : '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $provider_matchmask ), '#' ) ) . '#i';
    2227 
    2228                 if ( preg_match( $regex, $url ) )
    2229                     $is_oembed_link = true;
    2230             }
    2231 
    2232             // If url doesn't match a WP oEmbed provider, stop parsing
    2233             if ( !$is_oembed_link )
    2234                 return $this->maybe_make_link( $url );
    2235         }
    2236 
    2237         return $this->parse_oembed( $id, $url, $attr, $rawattr );
    2238     }
    2239 
    2240     /**
    2241      * Base function so BP components/plugins can parse links to be embedded.
    2242      *
    2243      * View an example to add support in {@link bp_activity_embed()}.
    2244      *
    2245      * @uses apply_filters() Filters cache.
    2246      * @uses do_action() To save cache.
    2247      * @uses wp_oembed_get() Connects to oEmbed provider and returns HTML
    2248      *       on success.
    2249      * @uses WP_Embed::maybe_make_link() Process URL for hyperlinking on
    2250      *       oEmbed failure.
    2251      *
    2252      * @param int $id ID to do the caching for.
    2253      * @param string $url The URL attempting to be embedded.
    2254      * @param array $attr Shortcode attributes from {@link WP_Embed::shortcode()}.
    2255      * @param array $rawattr Untouched shortcode attributes from
    2256      *        {@link WP_Embed::shortcode()}.
    2257      * @return string The embed HTML on success, otherwise the original URL.
    2258      */
    2259     public function parse_oembed( $id, $url, $attr, $rawattr ) {
    2260         $id = intval( $id );
    2261 
    2262         if ( $id ) {
    2263             // Setup the cachekey
    2264             $cachekey = '_oembed_' . md5( $url . serialize( $attr ) );
    2265 
    2266             // Let components / plugins grab their cache
    2267             $cache = '';
    2268             $cache = apply_filters( 'bp_embed_get_cache', $cache, $id, $cachekey, $url, $attr, $rawattr );
    2269 
    2270             // Grab cache and return it if available
    2271             if ( !empty( $cache ) ) {
    2272                 return apply_filters( 'bp_embed_oembed_html', $cache, $url, $attr, $rawattr );
    2273 
    2274             // If no cache, ping the oEmbed provider and cache the result
    2275             } else {
    2276                 $html = wp_oembed_get( $url, $attr );
    2277                 $cache = ( $html ) ? $html : $url;
    2278 
    2279                 // Let components / plugins save their cache
    2280                 do_action( 'bp_embed_update_cache', $cache, $cachekey, $id );
    2281 
    2282                 // If there was a result, return it
    2283                 if ( $html )
    2284                     return apply_filters( 'bp_embed_oembed_html', $html, $url, $attr, $rawattr );
    2285             }
    2286         }
    2287 
    2288         // Still unknown
    2289         return $this->maybe_make_link( $url );
    2290     }
    2291 }
    2292 
    2293 /**
    2294  * Create HTML list of BP nav items.
    2295  *
    2296  * @since BuddyPress (1.7.0)
    2297  */
    2298 class BP_Walker_Nav_Menu extends Walker_Nav_Menu {
    2299 
    2300     /**
    2301      * Description of fields indexes for building markup.
    2302      *
    2303      * @since BuddyPress (1.7.0)
    2304      * @var array
    2305      */
    2306     var $db_fields = array( 'id' => 'css_id', 'parent' => 'parent' );
    2307 
    2308     /**
    2309      * Tree type.
    2310      *
    2311      * @since BuddyPress (1.7.0)
    2312      * @var string
    2313      */
    2314     var $tree_type = array();
    2315 
    2316     /**
    2317      * Display array of elements hierarchically.
    2318      *
    2319      * This method is almost identical to the version in {@link Walker::walk()}.
    2320      * The only change is on one line which has been commented. An IF was
    2321      * comparing 0 to a non-empty string which was preventing child elements
    2322      * being grouped under their parent menu element.
    2323      *
    2324      * This caused a problem for BuddyPress because our primary/secondary
    2325      * navigations don't have a unique numerical ID that describes a
    2326      * hierarchy (we use a slug). Obviously, WordPress Menus use Posts, and
    2327      * those have ID/post_parent.
    2328      *
    2329      * @since BuddyPress (1.7.0)
    2330      *
    2331      * @see Walker::walk()
    2332      *
    2333      * @param array $elements See {@link Walker::walk()}.
    2334      * @param int $max_depth See {@link Walker::walk()}.
    2335      * @return string See {@link Walker::walk()}.
    2336      */
    2337     public function walk( $elements, $max_depth ) {
    2338         $func_args = func_get_args();
    2339 
    2340         $args   = array_slice( $func_args, 2 );
    2341         $output = '';
    2342 
    2343         if ( $max_depth < -1 ) // invalid parameter
    2344             return $output;
    2345 
    2346         if ( empty( $elements ) ) // nothing to walk
    2347             return $output;
    2348 
    2349         $parent_field = $this->db_fields['parent'];
    2350 
    2351         // flat display
    2352         if ( -1 == $max_depth ) {
    2353 
    2354             $empty_array = array();
    2355             foreach ( $elements as $e )
    2356                 $this->display_element( $e, $empty_array, 1, 0, $args, $output );
    2357 
    2358             return $output;
    2359         }
    2360 
    2361         /*
    2362          * need to display in hierarchical order
    2363          * separate elements into two buckets: top level and children elements
    2364          * children_elements is two dimensional array, eg.
    2365          * children_elements[10][] contains all sub-elements whose parent is 10.
    2366          */
    2367         $top_level_elements = array();
    2368         $children_elements  = array();
    2369 
    2370         foreach ( $elements as $e ) {
    2371             // BuddyPress: changed '==' to '==='. This is the only change from version in Walker::walk().
    2372             if ( 0 === $e->$parent_field )
    2373                 $top_level_elements[] = $e;
    2374             else
    2375                 $children_elements[$e->$parent_field][] = $e;
    2376         }
    2377 
    2378         /*
    2379          * when none of the elements is top level
    2380          * assume the first one must be root of the sub elements
    2381          */
    2382         if ( empty( $top_level_elements ) ) {
    2383 
    2384             $first              = array_slice( $elements, 0, 1 );
    2385             $root               = $first[0];
    2386             $top_level_elements = array();
    2387             $children_elements  = array();
    2388 
    2389             foreach ( $elements as $e ) {
    2390                 if ( $root->$parent_field == $e->$parent_field )
    2391                     $top_level_elements[] = $e;
    2392                 else
    2393                     $children_elements[$e->$parent_field][] = $e;
    2394             }
    2395         }
    2396 
    2397         foreach ( $top_level_elements as $e )
    2398             $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
    2399 
    2400         /*
    2401          * if we are displaying all levels, and remaining children_elements is not empty,
    2402          * then we got orphans, which should be displayed regardless
    2403          */
    2404         if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
    2405             $empty_array = array();
    2406 
    2407             foreach ( $children_elements as $orphans )
    2408                 foreach ( $orphans as $op )
    2409                     $this->display_element( $op, $empty_array, 1, 0, $args, $output );
    2410          }
    2411 
    2412          return $output;
    2413     }
    2414 
    2415     /**
    2416      * Display the current <li> that we are on.
    2417      *
    2418      * @see Walker::start_el() for complete description of parameters .
    2419      *
    2420      * @since BuddyPress (1.7.0)
    2421      *
    2422      * @param string $output Passed by reference. Used to append
    2423      *        additional content.
    2424      * @param object $item Menu item data object.
    2425      * @param int $depth Depth of menu item. Used for padding. Optional,
    2426      *        defaults to 0.
    2427      * @param array $args Optional. See {@link Walker::start_el()}.
    2428      * @param int $current_page Menu item ID. Optional.
    2429      */
    2430     public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    2431         // If we're someway down the tree, indent the HTML with the appropriate number of tabs
    2432         $indent = $depth ? str_repeat( "\t", $depth ) : '';
    2433 
    2434         // Add HTML classes
    2435         $class_names = join( ' ', apply_filters( 'bp_nav_menu_css_class', array_filter( $item->class ), $item, $args ) );
    2436         $class_names = ! empty( $class_names ) ? ' class="' . esc_attr( $class_names ) . '"' : '';
    2437 
    2438         // Add HTML ID
    2439         $id = sanitize_html_class( $item->css_id . '-personal-li' );  // Backpat with BP pre-1.7
    2440         $id = apply_filters( 'bp_nav_menu_item_id', $id, $item, $args );
    2441         $id = ! empty( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';
    2442 
    2443         // Opening tag; closing tag is handled in Walker_Nav_Menu::end_el().
    2444         $output .= $indent . '<li' . $id . $class_names . '>';
    2445 
    2446         // Add href attribute
    2447         $attributes = ! empty( $item->link ) ? ' href="' . esc_attr( esc_url( $item->link ) ) . '"' : '';
    2448 
    2449         // Construct the link
    2450         $item_output = $args->before;
    2451         $item_output .= '<a' . $attributes . '>';
    2452         $item_output .= $args->link_before . apply_filters( 'the_title', $item->name, 0 ) . $args->link_after;
    2453         $item_output .= '</a>';
    2454         $item_output .= $args->after;
    2455 
    2456         // $output is byref
    2457         $output .= apply_filters( 'bp_walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    2458     }
    2459 }
    2460 
    2461 /**
    2462  * Create a set of BuddyPress-specific links for use in the Menus admin UI.
    2463  *
    2464  * Borrowed heavily from {@link Walker_Nav_Menu_Checklist}, but modified so as not
    2465  * to require an actual post type or taxonomy, and to force certain CSS classes
    2466  *
    2467  * @since BuddyPress (1.9.0)
    2468  */
    2469 class BP_Walker_Nav_Menu_Checklist extends Walker_Nav_Menu {
    2470 
    2471     /**
    2472      * Constructor.
    2473      *
    2474      * @see Walker_Nav_Menu::__construct() for a description of parameters.
    2475      *
    2476      * @param array $fields See {@link Walker_Nav_Menu::__construct()}.
    2477      */
    2478     public function __construct( $fields = false ) {
    2479         if ( $fields ) {
    2480             $this->db_fields = $fields;
    2481         }
    2482     }
    2483 
    2484     /**
    2485      * Create the markup to start a tree level.
    2486      *
    2487      * @see Walker_Nav_Menu::start_lvl() for description of parameters.
    2488      *
    2489      * @param string $output See {@Walker_Nav_Menu::start_lvl()}.
    2490      * @param int $depth See {@Walker_Nav_Menu::start_lvl()}.
    2491      * @param array $args See {@Walker_Nav_Menu::start_lvl()}.
    2492      */
    2493     public function start_lvl( &$output, $depth = 0, $args = array() ) {
    2494         $indent = str_repeat( "\t", $depth );
    2495         $output .= "\n$indent<ul class='children'>\n";
    2496     }
    2497 
    2498     /**
    2499      * Create the markup to end a tree level.
    2500      *
    2501      * @see Walker_Nav_Menu::end_lvl() for description of parameters.
    2502      *
    2503      * @param string $output See {@Walker_Nav_Menu::end_lvl()}.
    2504      * @param int $depth See {@Walker_Nav_Menu::end_lvl()}.
    2505      * @param array $args See {@Walker_Nav_Menu::end_lvl()}.
    2506      */
    2507     public function end_lvl( &$output, $depth = 0, $args = array() ) {
    2508         $indent = str_repeat( "\t", $depth );
    2509         $output .= "\n$indent</ul>";
    2510     }
    2511 
    2512     /**
    2513      * Create the markup to start an element.
    2514      *
    2515      * @see Walker::start_el() for description of parameters.
    2516      *
    2517      * @param string $output Passed by reference. Used to append additional
    2518      *        content.
    2519      * @param object $item Menu item data object.
    2520      * @param int $depth Depth of menu item. Used for padding.
    2521      * @param object $args See {@Walker::start_el()}.
    2522      * @param int $id See {@Walker::start_el()}.
    2523      */
    2524     function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    2525         global $_nav_menu_placeholder;
    2526 
    2527         $_nav_menu_placeholder = ( 0 > $_nav_menu_placeholder ) ? intval($_nav_menu_placeholder) - 1 : -1;
    2528         $possible_object_id = isset( $item->post_type ) && 'nav_menu_item' == $item->post_type ? $item->object_id : $_nav_menu_placeholder;
    2529         $possible_db_id = ( ! empty( $item->ID ) ) && ( 0 < $possible_object_id ) ? (int) $item->ID : 0;
    2530 
    2531         $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
    2532 
    2533         $output .= $indent . '<li>';
    2534         $output .= '<label class="menu-item-title">';
    2535         $output .= '<input type="checkbox" class="menu-item-checkbox';
    2536 
    2537         if ( property_exists( $item, 'label' ) ) {
    2538             $title = $item->label;
    2539         }
    2540 
    2541         $output .= '" name="menu-item[' . $possible_object_id . '][menu-item-object-id]" value="'. esc_attr( $item->object_id ) .'" /> ';
    2542         $output .= isset( $title ) ? esc_html( $title ) : esc_html( $item->title );
    2543         $output .= '</label>';
    2544 
    2545         if ( empty( $item->url ) ) {
    2546             $item->url = $item->guid;
    2547         }
    2548 
    2549         if ( ! in_array( array( 'bp-menu', 'bp-'. $item->post_excerpt .'-nav' ), $item->classes ) ) {
    2550             $item->classes[] = 'bp-menu';
    2551             $item->classes[] = 'bp-'. $item->post_excerpt .'-nav';
    2552         }
    2553 
    2554         // Menu item hidden fields
    2555         $output .= '<input type="hidden" class="menu-item-db-id" name="menu-item[' . $possible_object_id . '][menu-item-db-id]" value="' . $possible_db_id . '" />';
    2556         $output .= '<input type="hidden" class="menu-item-object" name="menu-item[' . $possible_object_id . '][menu-item-object]" value="'. esc_attr( $item->object ) .'" />';
    2557         $output .= '<input type="hidden" class="menu-item-parent-id" name="menu-item[' . $possible_object_id . '][menu-item-parent-id]" value="'. esc_attr( $item->menu_item_parent ) .'" />';
    2558         $output .= '<input type="hidden" class="menu-item-type" name="menu-item[' . $possible_object_id . '][menu-item-type]" value="custom" />';
    2559         $output .= '<input type="hidden" class="menu-item-title" name="menu-item[' . $possible_object_id . '][menu-item-title]" value="'. esc_attr( $item->title ) .'" />';
    2560         $output .= '<input type="hidden" class="menu-item-url" name="menu-item[' . $possible_object_id . '][menu-item-url]" value="'. esc_attr( $item->url ) .'" />';
    2561         $output .= '<input type="hidden" class="menu-item-target" name="menu-item[' . $possible_object_id . '][menu-item-target]" value="'. esc_attr( $item->target ) .'" />';
    2562         $output .= '<input type="hidden" class="menu-item-attr_title" name="menu-item[' . $possible_object_id . '][menu-item-attr_title]" value="'. esc_attr( $item->attr_title ) .'" />';
    2563         $output .= '<input type="hidden" class="menu-item-classes" name="menu-item[' . $possible_object_id . '][menu-item-classes]" value="'. esc_attr( implode( ' ', $item->classes ) ) .'" />';
    2564         $output .= '<input type="hidden" class="menu-item-xfn" name="menu-item[' . $possible_object_id . '][menu-item-xfn]" value="'. esc_attr( $item->xfn ) .'" />';
    2565     }
    2566 }
    2567 
    2568 /**
    2569  * Base class for the BuddyPress Suggestions API.
    2570  *
    2571  * Originally built to power BuddyPress' at-mentions suggestions, it's flexible enough to be used
    2572  * for similar kinds of future core requirements, or those desired by third-party developers.
    2573  *
    2574  * To implement a new suggestions service, create a new class that extends this one, and update
    2575  * the list of default services in {@link bp_core_get_suggestions()}. If you're building a plugin,
    2576  * it's recommend that you use the `bp_suggestions_services` filter to do this. :)
    2577  *
    2578  * While the implementation of the query logic is left to you, it should be as quick and efficient
    2579  * as possible. When implementing the abstract methods in this class, pay close attention to the
    2580  * recommendations provided in the phpDoc blocks, particularly the expected return types.
    2581  *
    2582  * @since BuddyPress (2.1.0)
    2583  */
    2584 abstract class BP_Suggestions {
    2585 
    2586     /**
    2587      * Default arguments common to all suggestions services.
    2588      *
    2589      * If your custom service requires further defaults, add them here.
    2590      *
    2591      * @since BuddyPress (2.1.0)
    2592      * @var array
    2593      */
    2594     protected $default_args = array(
    2595         'limit' => 16,
    2596         'term'  => '',
    2597         'type'  => '',
    2598     );
    2599 
    2600     /**
    2601      * Holds the arguments for the query (about to made to the suggestions service).
    2602      *
    2603      * This includes `$default_args`, as well as the user-supplied values.
    2604      *
    2605      * @since BuddyPress (2.1.0)
    2606      * @var array
    2607      */
    2608     protected $args = array(
    2609     );
    2610 
    2611 
    2612     /**
    2613      * Constructor.
    2614      *
    2615      * @param array $args Optional. If set, used as the parameters for the suggestions service query.
    2616      * @since BuddyPress (2.1.0)
    2617      */
    2618     public function __construct( array $args = array() ) {
    2619         if ( ! empty( $args ) ) {
    2620             $this->set_query( $args );
    2621         }
    2622     }
    2623 
    2624     /**
    2625      * Set the parameters for the suggestions service query.
    2626      *
    2627      * @param array $args {
    2628      *     @type int $limit Maximum number of results to display. Optional, default: 16.
    2629      *     @type string $type The name of the suggestion service to use for the request. Mandatory.
    2630      *     @type string $term The suggestion service will try to find results that contain this string.
    2631      *           Mandatory.
    2632      * }
    2633      * @since BuddyPress (2.1.0)
    2634      */
    2635     public function set_query( array $args = array() ) {
    2636         $this->args = wp_parse_args( $args, $this->default_args );
    2637     }
    2638 
    2639     /**
    2640      * Validate and sanitise the parameters for the suggestion service query.
    2641      *
    2642      * Be sure to call this class' version of this method when implementing it in your own service.
    2643      * If validation fails, you must return a WP_Error object.
    2644      *
    2645      * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
    2646      * @since BuddyPress (2.1.0)
    2647      */
    2648     public function validate() {
    2649         $this->args['limit'] = absint( $this->args['limit'] );
    2650         $this->args['term']  = trim( sanitize_text_field( $this->args['term'] ) );
    2651         $this->args          = apply_filters( 'bp_suggestions_args', $this->args, $this );
    2652 
    2653 
    2654         // Check for invalid or missing mandatory parameters.
    2655         if ( ! $this->args['limit'] || ! $this->args['term'] ) {
    2656             return new WP_Error( 'missing_parameter' );
    2657         }
    2658 
    2659         // Check for blocked users (e.g. deleted accounts, or spammers).
    2660         if ( is_user_logged_in() && ! bp_is_user_active( get_current_user_id() ) ) {
    2661             return new WP_Error( 'invalid_user' );
    2662         }
    2663 
    2664         return apply_filters( 'bp_suggestions_validate_args', true, $this );
    2665     }
    2666 
    2667     /**
    2668      * Find and return a list of suggestions that match the query.
    2669      *
    2670      * The return type is important. If no matches are found, an empty array must be returned.
    2671      * Matches must be returned as objects in an array.
    2672      *
    2673      * The object format for each match must be: { 'ID': string, 'image': string, 'name': string }
    2674      * For example: { 'ID': 'admin', 'image': 'http://example.com/logo.png', 'name': 'Name Surname' }
    2675      *
    2676      * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
    2677      * @since BuddyPress (2.1.0)
    2678      */
    2679     abstract public function get_suggestions();
    2680 }
    2681 
    2682 /**
    2683  * Adds support for user at-mentions to the Suggestions API.
    2684  *
    2685  * This class is in the Core component because it's required by a class in the Groups component,
    2686  * and Groups is loaded before Members (alphabetical order).
    2687  *
    2688  * @since BuddyPress (2.1.0)
    2689  */
    2690 class BP_Members_Suggestions extends BP_Suggestions {
    2691 
    2692     /**
    2693      * Default arguments for this suggestions service.
    2694      *
    2695      * @since BuddyPress (2.1.0)
    2696      * @var array $args {
    2697      *     @type int $limit Maximum number of results to display. Default: 16.
    2698      *     @type bool $only_friends If true, only match the current user's friends. Default: false.
    2699      *     @type string $term The suggestion service will try to find results that contain this string.
    2700      *           Mandatory.
    2701      * }
    2702      */
    2703     protected $default_args = array(
    2704         'limit'        => 10,
    2705         'only_friends' => false,
    2706         'term'         => '',
    2707         'type'         => '',
    2708     );
    2709 
    2710 
    2711     /**
    2712      * Validate and sanitise the parameters for the suggestion service query.
    2713      *
    2714      * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
    2715      * @since BuddyPress (2.1.0)
    2716      */
    2717     public function validate() {
    2718         $this->args['only_friends'] = (bool) $this->args['only_friends'];
    2719         $this->args                 = apply_filters( 'bp_members_suggestions_args', $this->args, $this );
    2720 
    2721         // Check for invalid or missing mandatory parameters.
    2722         if ( $this->args['only_friends'] && ( ! bp_is_active( 'friends' ) || ! is_user_logged_in() ) ) {
    2723             return new WP_Error( 'missing_requirement' );
    2724         }
    2725 
    2726         return apply_filters( 'bp_members_suggestions_validate_args', parent::validate(), $this );
    2727     }
    2728 
    2729     /**
    2730      * Find and return a list of username suggestions that match the query.
    2731      *
    2732      * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
    2733      * @since BuddyPress (2.1.0)
    2734      */
    2735     public function get_suggestions() {
    2736         $user_query = array(
    2737             'count_total'     => '',  // Prevents total count
    2738             'populate_extras' => false,
    2739             'type'            => 'alphabetical',
    2740 
    2741             'page'            => 1,
    2742             'per_page'        => $this->args['limit'],
    2743             'search_terms'    => $this->args['term'],
    2744             'search_wildcard' => 'right',
    2745         );
    2746 
    2747         // Only return matches of friends of this user.
    2748         if ( $this->args['only_friends'] && is_user_logged_in() ) {
    2749             $user_query['user_id'] = get_current_user_id();
    2750         }
    2751 
    2752         $user_query = apply_filters( 'bp_members_suggestions_query_args', $user_query, $this );
    2753         if ( is_wp_error( $user_query ) ) {
    2754             return $user_query;
    2755         }
    2756 
    2757 
    2758         $user_query = new BP_User_Query( $user_query );
    2759         $results    = array();
    2760 
    2761         foreach ( $user_query->results as $user ) {
    2762             $result        = new stdClass();
    2763             $result->ID    = $user->user_nicename;
    2764             $result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
    2765             $result->name  = bp_core_get_user_displayname( $user->ID );
    2766 
    2767             $results[] = $result;
    2768         }
    2769 
    2770         return apply_filters( 'bp_members_suggestions_get_suggestions', $results, $this );
    2771     }
    2772 }
    2773 
    2774 /**
    2775  * Base class for creating query classes that generate SQL fragments for filtering results based on recursive query params.
    2776  *
    2777  * @since BuddyPress (2.2.0)
    2778  */
    2779 abstract class BP_Recursive_Query {
    2780 
    2781     /**
    2782      * Query arguments passed to the constructor.
    2783      *
    2784      * @since BuddyPress (2.2.0)
    2785      * @access public
    2786      * @var array
    2787      */
    2788     public $queries = array();
    2789 
    2790     /**
    2791      * Generate SQL clauses to be appended to a main query.
    2792      *
    2793      * Extending classes should call this method from within a publicly
    2794      * accessible get_sql() method, and manipulate the SQL as necessary.
    2795      * For example, {@link BP_XProfile_Query::get_sql()} is merely a wrapper for
    2796      * get_sql_clauses(), while {@link BP_Activity_Query::get_sql()} discards
    2797      * the empty 'join' clause, and only passes the 'where' clause.
    2798      *
    2799      * @since BuddyPress (2.2.0)
    2800      * @access protected
    2801      *
    2802      * @param  string $primary_table
    2803      * @param  string $primary_id_column
    2804      * @return array
    2805      */
    2806     protected function get_sql_clauses() {
    2807         $sql = $this->get_sql_for_query( $this->queries );
    2808 
    2809         if ( ! empty( $sql['where'] ) ) {
    2810             $sql['where'] = ' AND ' . "\n" . $sql['where'] . "\n";
    2811         }
    2812 
    2813         return $sql;
    2814     }
    2815 
    2816     /**
    2817      * Generate SQL clauses for a single query array.
    2818      *
    2819      * If nested subqueries are found, this method recurses the tree to
    2820      * produce the properly nested SQL.
    2821      *
    2822      * Subclasses generally do not need to call this method. It is invoked
    2823      * automatically from get_sql_clauses().
    2824      *
    2825      * @since BuddyPress (2.2.0)
    2826      * @access protected
    2827      *
    2828      * @param  array $query Query to parse.
    2829      * @param  int   $depth Optional. Number of tree levels deep we
    2830      *                      currently are. Used to calculate indentation.
    2831      * @return array
    2832      */
    2833     protected function get_sql_for_query( $query, $depth = 0 ) {
    2834         $sql_chunks = array(
    2835             'join'  => array(),
    2836             'where' => array(),
    2837         );
    2838 
    2839         $sql = array(
    2840             'join'  => '',
    2841             'where' => '',
    2842         );
    2843 
    2844         $indent = '';
    2845         for ( $i = 0; $i < $depth; $i++ ) {
    2846             $indent .= "\t";
    2847         }
    2848 
    2849         foreach ( $query as $key => $clause ) {
    2850             if ( 'relation' === $key ) {
    2851                 $relation = $query['relation'];
    2852             } elseif ( is_array( $clause ) ) {
    2853                 // This is a first-order clause
    2854                 if ( $this->is_first_order_clause( $clause ) ) {
    2855                     $clause_sql = $this->get_sql_for_clause( $clause, $query );
    2856 
    2857                     $where_count = count( $clause_sql['where'] );
    2858                     if ( ! $where_count ) {
    2859                         $sql_chunks['where'][] = '';
    2860                     } elseif ( 1 === $where_count ) {
    2861                         $sql_chunks['where'][] = $clause_sql['where'][0];
    2862                     } else {
    2863                         $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
    2864                     }
    2865 
    2866                     $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
    2867                 // This is a subquery
    2868                 } else {
    2869                     $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
    2870 
    2871                     $sql_chunks['where'][] = $clause_sql['where'];
    2872                     $sql_chunks['join'][]  = $clause_sql['join'];
    2873                 }
    2874             }
    2875         }
    2876 
    2877         // Filter empties
    2878         $sql_chunks['join']  = array_filter( $sql_chunks['join'] );
    2879         $sql_chunks['where'] = array_filter( $sql_chunks['where'] );
    2880 
    2881         if ( empty( $relation ) ) {
    2882             $relation = 'AND';
    2883         }
    2884 
    2885         if ( ! empty( $sql_chunks['join'] ) ) {
    2886             $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
    2887         }
    2888 
    2889         if ( ! empty( $sql_chunks['where'] ) ) {
    2890             $sql['where'] = '( ' . "\n\t" . $indent . implode( ' ' . "\n\t" . $indent . $relation . ' ' . "\n\t" . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')' . "\n";
    2891         }
    2892 
    2893         return $sql;
    2894     }
    2895 
    2896     /**
    2897      * Recursive-friendly query sanitizer.
    2898      *
    2899      * Ensures that each query-level clause has a 'relation' key, and that
    2900      * each first-order clause contains all the necessary keys from
    2901      * $defaults.
    2902      *
    2903      * Extend this method if your class uses different sanitizing logic.
    2904      *
    2905      * @since BuddyPress (2.2.0)
    2906      * @access public
    2907      *
    2908      * @param  array $queries Array of query clauses.
    2909      * @return array Sanitized array of query clauses.
    2910      */
    2911     protected function sanitize_query( $queries ) {
    2912         $clean_queries = array();
    2913 
    2914         if ( ! is_array( $queries ) ) {
    2915             return $clean_queries;
    2916         }
    2917 
    2918         foreach ( $queries as $key => $query ) {
    2919             if ( 'relation' === $key ) {
    2920                 $relation = $query;
    2921 
    2922             } elseif ( ! is_array( $query ) ) {
    2923                 continue;
    2924 
    2925             // First-order clause.
    2926             } elseif ( $this->is_first_order_clause( $query ) ) {
    2927                 if ( isset( $query['value'] ) && array() === $query['value'] ) {
    2928                     unset( $query['value'] );
    2929                 }
    2930 
    2931                 $clean_queries[] = $query;
    2932 
    2933             // Otherwise, it's a nested query, so we recurse.
    2934             } else {
    2935                 $cleaned_query = $this->sanitize_query( $query );
    2936 
    2937                 if ( ! empty( $cleaned_query ) ) {
    2938                     $clean_queries[] = $cleaned_query;
    2939                 }
    2940             }
    2941         }
    2942 
    2943         if ( empty( $clean_queries ) ) {
    2944             return $clean_queries;
    2945         }
    2946 
    2947         // Sa