Skip to:
Content

BuddyPress.org


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

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

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

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

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

Fixes #6083

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-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                 // Sanitize the 'relation' key provided in the query.
    2948                 if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
    2949                         $clean_queries['relation'] = 'OR';
    2950 
    2951                 /*
    2952                  * If there is only a single clause, call the relation 'OR'.
    2953                  * This value will not actually be used to join clauses, but it
    2954                  * simplifies the logic around combining key-only queries.
    2955                  */
    2956                 } elseif ( 1 === count( $clean_queries ) ) {
    2957                         $clean_queries['relation'] = 'OR';
    2958 
    2959                 // Default to AND.
    2960                 } else {
    2961                         $clean_queries['relation'] = 'AND';
    2962                 }
    2963 
    2964                 return $clean_queries;
    2965         }
    2966 
    2967         /**
    2968          * Generate JOIN and WHERE clauses for a first-order clause.
    2969          *
    2970          * Must be overridden in a subclass.
    2971          *
    2972          * @since BuddyPress (2.2.0)
    2973          * @access protected
    2974          *
    2975          * @param  array $clause       Array of arguments belonging to the clause.
    2976          * @param  array $parent_query Parent query to which the clause belongs.
    2977          * @return array {
    2978          *     @type array $join  Array of subclauses for the JOIN statement.
    2979          *     @type array $where Array of subclauses for the WHERE statement.
    2980          * }
    2981          */
    2982         abstract protected function get_sql_for_clause( $clause, $parent_query );
    2983 
    2984         /**
    2985          * Determine whether a clause is first-order.
    2986          *
    2987          * Must be overridden in a subclass.
    2988          *
    2989          * @since BuddyPress (2.2.0)
    2990          * @access protected
    2991          *
    2992          * @param  array $q Clause to check.
    2993          * @return bool
    2994          */
    2995         abstract protected function is_first_order_clause( $query );
    2996 }
     12require __DIR__ . '/classes/class-bp-user-query.php';
     13require __DIR__ . '/classes/class-bp-core-user.php';
     14require __DIR__ . '/classes/class-bp-date-query.php';
     15require __DIR__ . '/classes/class-bp-core-notification.php';
     16require __DIR__ . '/classes/class-bp-button.php';
     17require __DIR__ . '/classes/class-bp-embed.php';
     18require __DIR__ . '/classes/class-bp-walker-nav-menu.php';
     19require __DIR__ . '/classes/class-bp-walker-nav-menu-checklist.php';
     20require __DIR__ . '/classes/class-bp-suggestions.php';
     21require __DIR__ . '/classes/class-bp-members-suggestions.php';
     22require __DIR__ . '/classes/class-bp-recursive-query.php';
Note: See TracChangeset for help on using the changeset viewer.