Skip to:
Content

BuddyPress.org

Ticket #4933: bp-core-classes.php

File bp-core-classes.php, 67.3 KB (added by dontdream, 12 years ago)

edited copy of bp-core-classes.php

Line 
1<?php
2
3// Exit if accessed directly
4if ( !defined( 'ABSPATH' ) ) exit;
5
6/**
7 * BuddyPress User Query class
8 *
9 * Used for querying users in a BuddyPress context, in situations where
10 * WP_User_Query won't do the trick: Member directories, the Friends component,
11 * etc.
12 *
13 * Accepted parameters:
14 *   type            - Determines sort order. Select from 'newest', 'active',
15 *                     'online', 'random', 'popular', 'alphabetical'
16 *   per_page        - Number of results to return
17 *   page            - Page offset (together with per_page)
18 *   user_id         - Pass a single numeric user id to limit results to
19 *                     friends of that user. Requires the Friends component
20 *   search_terms    - Terms to search by. Search happens across xprofile
21 *                     fields. Requires XProfile component
22 *   include         - An array or comma-separated list of user ids. Results
23 *                     will be limited to users in this list
24 *   exclude         - An array or comma-separated list of user ids. Results
25 *                     will not include any users in this list
26 *   user_ids        - An array or comma-separated list of user ids. When
27 *                     this parameter is passed, it will override all other
28 *                     parameters; BP User objects will be constructed using
29 *                     these IDs only
30 *   meta_key        - Limit results to users that have usermeta associated
31 *                     with this meta_key. Usually used with meta_value
32 *   meta_value      - When used with meta_key, limits results to users whose
33 *                     usermeta value associated with meta_key matches
34 *                     meta_value
35 *   populate_extras - Boolean. True if you want to fetch extra metadata about
36 *                     returned users, such as total group and friend counts
37 *   count_total     - Determines how BP_User_Query will do a count of total
38 *                     users matching the other filter criteria. Default value
39 *                     is 'count_query', which does a separate SELECT COUNT
40 *                     query to determine the total. 'sql_count_found_rows'
41 *                     uses SQL_COUNT_FOUND_ROWS and SELECT FOUND_ROWS(). Pass
42 *                     an empty string to skip the total user count query.
43 *
44 * @since BuddyPress (1.7)
45 */
46class BP_User_Query {
47
48        /** Variables *************************************************************/
49
50        /**
51         * Array of variables to query with
52         *
53         * @since BuddyPress (1.7)
54         * @var array
55         */
56        public $query_vars = array();
57
58        /**
59         * List of found users and their respective data
60         *
61         * @since BuddyPress (1.7)
62         * @access public To allow components to manipulate them
63         * @var array
64         */
65        public $results = array();
66
67        /**
68         * Total number of found users for the current query
69         *
70         * @since BuddyPress (1.7)
71         * @access public To allow components to manipulate it
72         * @var int
73         */
74        public $total_users = 0;
75
76        /**
77         * List of found user ID's
78         *
79         * @since BuddyPress (1.7)
80         * @access public To allow components to manipulate it
81         * @var array
82         */
83        public $user_ids = array();
84
85        /**
86         * SQL clauses for the user ID query
87         *
88         * @since BuddyPress (1.7)
89         * @access public To allow components to manipulate it
90         * @var array()
91         */
92        public $uid_clauses = array();
93
94        /**
95         * SQL database column name to order by
96         *
97         * @since BuddyPress (1.7)
98         * @var string
99         */
100        public $uid_name = '';
101
102        /**
103         * Standard response when the query should not return any rows.
104         *
105         * @since BuddyPress (1.7)
106         * @access protected
107         * @var string
108         */
109        protected $no_results = array( 'join' => '', 'where' => '0 = 1' );
110
111
112        /** Methods ***************************************************************/
113
114        /**
115         * Constructor
116         *
117         * @since BuddyPress (1.7)
118         *
119         * @param string|array $query The query variables
120         */
121        public function __construct( $query = null ) {
122                if ( ! empty( $query ) ) {
123                        $this->query_vars = wp_parse_args( $query, array(
124                                'type'            => 'newest',
125                                'per_page'        => 0,
126                                'page'            => 1,
127                                'user_id'         => 0,
128                                'search_terms'    => false,
129                                'include'         => false,
130                                'exclude'         => false,
131                                'user_ids'        => false,
132                                'meta_key'        => false,
133                                'meta_value'      => false,
134                                'populate_extras' => true,
135                                'count_total'     => 'count_query'
136                        ) );
137
138                        // Plugins can use this filter to modify query args
139                        // before the query is constructed
140                        do_action_ref_array( 'bp_pre_user_query_construct', array( &$this ) );
141
142                        // Get user ids
143                        // If the user_ids param is present, we skip the query
144                        if ( false !== $this->query_vars['user_ids'] ) {
145                                $this->user_ids = wp_parse_id_list( $this->query_vars['user_ids'] );
146                        } else {
147                                $this->prepare_user_ids_query();
148                                $this->do_user_ids_query();
149                        }
150                }
151
152                // Bail if no user IDs were found
153                if ( empty( $this->user_ids ) ) {
154                        return;
155                }
156
157                // Fetch additional data. First, using WP_User_Query
158                $this->do_wp_user_query();
159
160                // Get BuddyPress specific user data
161                $this->populate_extras();
162        }
163
164        /**
165         * Prepare the query for user_ids
166         *
167         * @since BuddyPress (1.7)
168         */
169        public function prepare_user_ids_query() {
170                global $wpdb, $bp;
171
172                // Default query variables used here
173                $type         = '';
174                $per_page     = 0;
175                $page         = 1;
176                $user_id      = 0;
177                $include      = false;
178                $search_terms = false;
179                $exclude      = false;
180                $meta_key     = false;
181                $meta_value   = false;
182
183                extract( $this->query_vars );
184
185                // Setup the main SQL query container
186                $sql = array(
187                        'select'  => '',
188                        'where'   => array(),
189                        'orderby' => '',
190                        'order'   => '',
191                        'limit'   => ''
192                );
193
194                /** TYPE **************************************************************/
195
196                // Determines the sort order, which means it also determines where the
197                // user IDs are drawn from (the SELECT and WHERE statements)
198                switch ( $type ) {
199
200                        // 'online' query happens against the last_activity usermeta key
201                        case 'online' :
202                                $this->uid_name = 'user_id';
203                                $sql['select']  = "SELECT DISTINCT u.{$this->uid_name} as id FROM {$wpdb->usermeta} u";
204                                $sql['where'][] = $wpdb->prepare( "u.meta_key = %s", bp_get_user_meta_key( 'last_activity' ) );
205                                $sql['where'][] = 'u.meta_value >= DATE_SUB( UTC_TIMESTAMP(), INTERVAL 5 MINUTE )';
206                                $sql['orderby'] = "ORDER BY u.meta_value";
207                                $sql['order']   = "DESC";
208
209                                break;
210
211                        // 'active', 'newest', and 'random' queries
212                        // all happen against the last_activity usermeta key
213                        case 'active' :
214                        case 'newest' :
215                        case 'random' :
216                                $this->uid_name = 'user_id';
217                                $sql['select']  = "SELECT DISTINCT u.{$this->uid_name} as id FROM {$wpdb->usermeta} u";
218                                $sql['where'][] = $wpdb->prepare( "u.meta_key = %s", bp_get_user_meta_key( 'last_activity' ) );
219
220                                if ( 'newest' == $type ) {
221                                        $sql['orderby'] = "ORDER BY u.user_id";
222                                        $sql['order'] = "DESC";
223                                } else if ( 'random' == $type ) {
224                                        $sql['orderby'] = "ORDER BY rand()";
225                                } else {
226                                        $sql['orderby'] = "ORDER BY u.meta_value";
227                                        $sql['order'] = "DESC";
228                                }
229
230                                break;
231
232                        // 'popular' sorts by the 'total_friend_count' usermeta
233                        case 'popular' :
234                                $this->uid_name = 'user_id';
235                                $sql['select']  = "SELECT DISTINCT u.{$this->uid_name} as id FROM {$wpdb->usermeta} u";
236                                $sql['where'][] = $wpdb->prepare( "u.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) );
237                                $sql['orderby'] = "ORDER BY u.meta_value";
238                                $sql['order']   = "DESC";
239
240                                break;
241
242                        // 'alphabetical' sorts depend on the xprofile setup
243                        case 'alphabetical' :
244
245                                // We prefer to do alphabetical sorts against the display_name field
246                                // of wp_users, because the table is smaller and better indexed. We
247                                // can do so if xprofile sync is enabled, or if xprofile is inactive.
248                                //
249                                // @todo remove need for bp_is_active() check
250                                if ( ! bp_disable_profile_sync() || ! bp_is_active( 'xprofile' ) ) {
251                                        $this->uid_name = 'ID';
252                                        $sql['select']  = "SELECT DISTINCT u.{$this->uid_name} as id FROM {$wpdb->users} u";
253                                        $sql['orderby'] = "ORDER BY u.display_name";
254                                        $sql['order']   = "ASC";
255
256                                // When profile sync is disabled, alphabetical sorts must happen against
257                                // the xprofile table
258                                } else {
259                                        $fullname_field_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE name = %s", bp_xprofile_fullname_field_name() ) );
260
261                                        $this->uid_name = 'user_id';
262                                        $sql['select']  = "SELECT DISTINCT u.{$this->uid_name} as id FROM {$bp->profile->table_name_data} u";
263                                        $sql['where'][] = "u.field_id = {$fullname_field_id}";
264                                        $sql['orderby'] = "ORDER BY u.value";
265                                        $sql['order']   = "ASC";
266                                }
267
268                                break;
269
270                        // Any other 'type' falls through
271                        default :
272                                $this->uid_name = 'ID';
273                                $sql['select']  = "SELECT DISTINCT u.{$this->uid_name} as id FROM {$wpdb->users} u";
274
275                                // In this case, we assume that a plugin is
276                                // handling order, so we leave those clauses
277                                // blank
278
279                                break;
280                }
281
282                /** WHERE *************************************************************/
283
284                // 'include' - User ids to include in the results
285                if ( false !== $include ) {
286                        $include        = wp_parse_id_list( $include );
287                        $include_ids    = $wpdb->escape( implode( ',', (array) $include ) );
288                        $sql['where'][] = "u.{$this->uid_name} IN ({$include_ids})";
289                }
290
291                // 'exclude' - User ids to exclude from the results
292                if ( false !== $exclude ) {
293                        $exclude        = wp_parse_id_list( $exclude );
294                        $exclude_ids    = $wpdb->escape( implode( ',', (array) $exclude ) );
295                        $sql['where'][] = "u.{$this->uid_name} NOT IN ({$exclude_ids})";
296                }
297
298                // 'user_id' - When a user id is passed, limit to the friends of the user
299                // Only parse this if no 'include' param is passed, to account for
300                // friend request queries
301                // @todo remove need for bp_is_active() check
302                if ( empty( $include ) && ! empty( $user_id ) && bp_is_active( 'friends' ) ) {
303                        $friend_ids = friends_get_friend_user_ids( $user_id );
304                        $friend_ids = $wpdb->escape( implode( ',', (array) $friend_ids ) );
305
306                        if ( ! empty( $friend_ids ) ) {
307                                $sql['where'][] = "u.{$this->uid_name} IN ({$friend_ids})";
308
309                        // If the user has no friends, and we're not including specific users, make sure the query returns null
310                        } elseif ( empty( $include ) ) {
311                                $sql['where'][] = $this->no_results['where'];
312                        }
313                }
314
315                /** Search Terms ******************************************************/
316
317                // 'search_terms' searches the xprofile fields
318                // To avoid global joins, do a separate query
319                // @todo remove need for bp_is_active() check
320                if ( false !== $search_terms && bp_is_active( 'xprofile' ) ) {
321//                      $found_user_ids = $wpdb->get_col( $wpdb->prepare( "SELECT user_id FROM {$bp->profile->table_name_data} WHERE value LIKE %s", '%%' . like_escape( $search_terms ) . '%%' ) );
322                        $found_user_ids = $wpdb->get_col( $wpdb->prepare( "SELECT user_id FROM {$bp->profile->table_name_data} WHERE value LIKE %s", '%' . mysql_real_escape_string( $search_terms ) . '%' ) );
323
324                        if ( ! empty( $found_user_ids ) ) {
325                                $sql['where'][] = "u.{$this->uid_name} IN (" . implode( ',', wp_parse_id_list( $found_user_ids ) ) . ")";
326                        } else {
327                                $sql['where'][] = $this->no_results['where'];
328                        }
329                }
330
331                // 'meta_key', 'meta_value' allow usermeta search
332                // To avoid global joins, do a separate query
333                if ( false !== $meta_key ) {
334                        $meta_sql = $wpdb->prepare( "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key );
335
336                        if ( false !== $meta_value ) {
337                                $meta_sql .= $wpdb->prepare( " AND meta_value = %s", $meta_value );
338                        }
339
340                        $found_user_ids = $wpdb->get_col( $meta_sql );
341
342                        if ( ! empty( $found_user_ids ) ) {
343                                $sql['where'][] = "u.{$this->uid_name} IN (" . implode( ',', wp_parse_id_list( $found_user_ids ) ) . ")";
344                        }
345                }
346
347                // 'per_page', 'page' - handles LIMIT
348                if ( !empty( $per_page ) && !empty( $page ) ) {
349                        $sql['limit'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $per_page ), intval( $per_page ) );
350                } else {
351                        $sql['limit'] = '';
352                }
353
354                // Assemble the query chunks
355                $this->uid_clauses['select']  = $sql['select'];
356                $this->uid_clauses['where']   = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : '';
357                $this->uid_clauses['orderby'] = $sql['orderby'];
358                $this->uid_clauses['order']   = $sql['order'];
359                $this->uid_clauses['limit']   = $sql['limit'];
360
361                do_action_ref_array( 'bp_pre_user_query', array( &$this ) );
362        }
363
364        /**
365         * Perform a database query to specifically get only user IDs, using
366         * existing query variables set previously in the constructor.
367         *
368         * Also used to quickly perform user total counts.
369         *
370         * @since BuddyPress (1.7)
371         */
372        public function do_user_ids_query() {
373                global $wpdb;
374
375                // If counting using SQL_CALC_FOUND_ROWS, set it up here
376                if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) {
377                        $this->uid_clauses['select'] = str_replace( 'SELECT', 'SELECT SQL_CALC_FOUND_ROWS', $this->uid_clauses['select'] );
378                }
379
380                // Get the specific user ids
381                $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']}" );
382
383                // Get the total user count
384                if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) {
385                        $this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "SELECT FOUND_ROWS()", $this ) );
386                } elseif ( 'count_query' == $this->query_vars['count_total'] ) {
387                        $count_select      = preg_replace( '/^SELECT.*?FROM (\S+) u/', "SELECT COUNT(DISTINCT u.{$this->uid_name}) FROM $1 u", $this->uid_clauses['select'] );
388                        $this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "{$count_select} {$this->uid_clauses['where']}", $this ) );
389                }
390        }
391
392        /**
393         * Perform a database query using the WP_User_Query() object, using existing
394         * fields, variables, and user ID's set previously in this class.
395         *
396         * @since BuddyPress (1.7)
397         */
398        public function do_wp_user_query() {
399                $wp_user_query = new WP_User_Query( apply_filters( 'bp_wp_user_query_args', array(
400
401                        // Relevant
402                        'fields'      => array( 'ID', 'user_registered', 'user_login', 'user_nicename', 'display_name', 'user_email' ),
403                        'include'     => $this->user_ids,
404
405                        // Overrides
406                        'blog_id'     => 0,    // BP does not require blog roles
407                        'count_total' => false // We already have a count
408
409                ), $this ) );
410
411                // Reindex for easier matching
412                $r = array();
413                foreach ( $wp_user_query->results as $u ) {
414                        $r[ $u->ID ] = $u;
415                }
416
417                // Match up to the user ids from the main query
418                foreach ( $this->user_ids as $uid ) {
419                        if ( isset( $r[ $uid ] ) ) {
420                                $this->results[ $uid ] = $r[ $uid ];
421
422                                // The BP template functions expect an 'id'
423                                // (as opposed to 'ID') property
424                                $this->results[ $uid ]->id = $uid;
425                        }
426                }
427        }
428
429        /**
430         * Perform a database query to populate any extra metadata we might need.
431         * Different components will hook into the 'bp_user_query_populate_extras'
432         * action to loop in the things they want.
433         *
434         * @since BuddyPress (1.7)
435         *
436         * @global BuddyPress $bp
437         * @global WPDB $wpdb
438         * @return
439         */
440        public function populate_extras() {
441                global $wpdb;
442
443                // Bail if no users
444                if ( empty( $this->user_ids ) || empty( $this->results ) ) {
445                        return;
446                }
447
448                // Bail if the populate_extras flag is set to false
449                // In the case of the 'popular' sort type, we force
450                // populate_extras to true, because we need the friend counts
451                if ( 'popular' == $this->query_vars['type'] ) {
452                        $this->query_vars['populate_extras'] = 1;
453                }
454
455                if ( ! (bool) $this->query_vars['populate_extras'] ) {
456                        return;
457                }
458
459                // Turn user ID's into a query-usable, comma separated value
460                $user_ids_sql = implode( ',', wp_parse_id_list( $this->user_ids ) );
461
462                /**
463                 * Use this action to independently populate your own custom extras.
464                 *
465                 * Note that anything you add here should query using $user_ids_sql, to
466                 * avoid running multiple queries per user in the loop.
467                 *
468                 * Two BuddyPress components currently do this:
469                 * - XProfile: To override display names
470                 * - Friends:  To set whether or not a user is the current users friend
471                 *
472                 * @see bp_xprofile_filter_user_query_populate_extras()
473                 * @see bp_friends_filter_user_query_populate_extras()
474                 */
475                do_action_ref_array( 'bp_user_query_populate_extras', array( $this, $user_ids_sql ) );
476
477                // Fetch usermeta data
478                // We want the three following pieces of info from usermeta:
479                // - friend count
480                // - last activity
481                // - latest update
482                $total_friend_count_key = bp_get_user_meta_key( 'total_friend_count' );
483                $last_activity_key      = bp_get_user_meta_key( 'last_activity'      );
484                $bp_latest_update_key   = bp_get_user_meta_key( 'bp_latest_update'   );
485
486                // total_friend_count must be set for each user, even if its
487                // value is 0
488                foreach ( $this->results as $uindex => $user ) {
489                        $this->results[$uindex]->total_friend_count = 0;
490                }
491
492                // Create, prepare, and run the seperate usermeta query
493                $user_metas = $wpdb->get_results( $wpdb->prepare( "SELECT user_id, meta_key, meta_value FROM {$wpdb->usermeta} WHERE meta_key IN (%s,%s,%s) AND user_id IN ({$user_ids_sql})", $total_friend_count_key, $last_activity_key, $bp_latest_update_key ) );
494
495                // The $members_template global expects the index key to be different
496                // from the meta_key in some cases, so we rejig things here.
497                foreach ( $user_metas as $user_meta ) {
498                        switch ( $user_meta->meta_key ) {
499                                case $total_friend_count_key :
500                                        $key = 'total_friend_count';
501                                        break;
502
503                                case $last_activity_key :
504                                        $key = 'last_activity';
505                                        break;
506
507                                case $bp_latest_update_key :
508                                        $key = 'latest_update';
509                                        break;
510                        }
511
512                        if ( isset( $this->results[ $user_meta->user_id ] ) ) {
513                                $this->results[ $user_meta->user_id ]->{$key} = $user_meta->meta_value;
514                        }
515                }
516
517                // When meta_key or meta_value have been passed to the query,
518                // fetch the resulting values for use in the template functions
519                if ( ! empty( $this->query_vars['meta_key'] ) ) {
520                        $meta_sql = array(
521                                'select' => "SELECT user_id, meta_key, meta_value",
522                                'from'   => "FROM $wpdb->usermeta",
523                                'where'  => $wpdb->prepare( "WHERE meta_key = %s", $this->query_vars['meta_key'] )
524                        );
525
526                        if ( false !== $this->query_vars['meta_value'] ) {
527                                $meta_sql['where'] .= $wpdb->prepare( " AND meta_value = %s", $this->query_vars['meta_value'] );
528                        }
529
530                        $metas = $wpdb->get_results( "{$meta_sql['select']} {$meta_sql['from']} {$meta_sql['where']}" );
531
532                        if ( ! empty( $metas ) ) {
533                                foreach ( $metas as $meta ) {
534                                        if ( isset( $this->results[ $meta->user_id ] ) ) {
535                                                $this->results[ $meta->user_id ]->meta_key = $meta->meta_key;
536
537                                                if ( ! empty( $meta->meta_value ) ) {
538                                                        $this->results[ $meta->user_id ]->meta_value = $meta->meta_value;
539                                                }
540                                        }
541                                }
542                        }
543                }
544        }
545}
546
547/**
548 * BP_Core_User class can be used by any component. It will fetch useful
549 * details for any user when provided with a user_id.
550 *
551 * Example:
552 *    $user = new BP_Core_User( $user_id );
553 *    $user_avatar = $user->avatar;
554 *        $user_email = $user->email;
555 *    $user_status = $user->status;
556 *    etc.
557 *
558 * @package BuddyPress Core
559 */
560class BP_Core_User {
561
562        /**
563         * ID of the user which the object relates to.
564         *
565         * @var integer
566         */
567        var $id;
568
569        /**
570         * The URL to the full size of the avatar for the user.
571         *
572         * @var string
573         */
574        var $avatar;
575
576        /**
577         * The URL to the thumb size of the avatar for the user.
578         *
579         * @var string
580         */
581        var $avatar_thumb;
582
583        /**
584         * The URL to the mini size of the avatar for the user.
585         *
586         * @var string
587         */
588        var $avatar_mini;
589
590        /**
591         * The full name of the user
592         *
593         * @var string
594         */
595        var $fullname;
596
597        /**
598         * The email for the user.
599         *
600         * @var string
601         */
602        var $email;
603
604        /**
605         * The absolute url for the user's profile.
606         *
607         * @var string
608         */
609        var $user_url;
610
611        /**
612         * The HTML for the user link, with the link text being the user's full name.
613         *
614         * @var string
615         */
616        var $user_link;
617
618        /**
619         * Contains a formatted string when the last time the user was active.
620         *
621         * Example: "active 2 hours and 50 minutes ago"
622         *
623         * @var string
624         */
625        var $last_active;
626
627        /* Extras */
628
629        /**
630         * The total number of "Friends" the user has on site.
631         *
632         * @var integer
633         */
634        var $total_friends;
635
636        /**
637         * The total number of blog posts posted by the user
638         *
639         * @var integer
640         * @deprecated No longer used
641         */
642        var $total_blogs;
643
644        /**
645         * The total number of groups the user is a part of.
646         *
647         * Example: "1 group", "2 groups"
648         *
649         * @var string
650         */
651        var $total_groups;
652
653        /**
654         * Profile information for the specific user.
655         *
656         * @since BuddyPress (1.2)
657         * @var array
658         */
659        public $profile_data;
660
661        /** Public Methods *******************************************************/
662
663        /**
664         * Class constructor.
665         *
666         * @param integer $user_id The ID for the user
667         * @param boolean $populate_extras Whether to fetch extra information such as group/friendship counts or not.
668         */
669        function __construct( $user_id, $populate_extras = false ) {
670                if ( !empty( $user_id ) ) {
671                        $this->id = $user_id;
672                        $this->populate();
673
674                        if ( !empty( $populate_extras ) ) {
675                                $this->populate_extras();
676                        }
677                }
678        }
679
680        /** Private Methods *******************************************************/
681
682        /**
683         * Populate the instantiated class with data based on the User ID provided.
684         *
685         * @uses bp_core_get_userurl() Returns the URL with no HTML markup for a user based on their user id
686         * @uses bp_core_get_userlink() Returns a HTML formatted link for a user with the user's full name as the link text
687         * @uses bp_core_get_user_email() Returns the email address for the user based on user ID
688         * @uses bp_get_user_meta() BP function returns the value of passed usermeta name from usermeta table
689         * @uses bp_core_fetch_avatar() Returns HTML formatted avatar for a user
690         * @uses bp_profile_last_updated_date() Returns the last updated date for a user.
691         */
692        function populate() {
693
694                if ( bp_is_active( 'xprofile' ) )
695                        $this->profile_data = $this->get_profile_data();
696
697                if ( !empty( $this->profile_data ) ) {
698                        $full_name_field_name = bp_xprofile_fullname_field_name();
699
700                        $this->user_url  = bp_core_get_user_domain( $this->id, $this->profile_data['user_nicename'], $this->profile_data['user_login'] );
701                        $this->fullname  = esc_attr( $this->profile_data[$full_name_field_name]['field_data'] );
702                        $this->user_link = "<a href='{$this->user_url}' title='{$this->fullname}'>{$this->fullname}</a>";
703                        $this->email     = esc_attr( $this->profile_data['user_email'] );
704                } else {
705                        $this->user_url  = bp_core_get_user_domain( $this->id );
706                        $this->user_link = bp_core_get_userlink( $this->id );
707                        $this->fullname  = esc_attr( bp_core_get_user_displayname( $this->id ) );
708                        $this->email     = esc_attr( bp_core_get_user_email( $this->id ) );
709                }
710
711                // Cache a few things that are fetched often
712                wp_cache_set( 'bp_user_fullname_' . $this->id, $this->fullname, 'bp' );
713                wp_cache_set( 'bp_user_email_' . $this->id, $this->email, 'bp' );
714                wp_cache_set( 'bp_user_url_' . $this->id, $this->user_url, 'bp' );
715
716                $this->avatar       = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'full', 'alt' => sprintf( __( 'Avatar of %s', 'buddypress' ), $this->fullname ) ) );
717                $this->avatar_thumb = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'thumb', 'alt' => sprintf( __( 'Avatar of %s', 'buddypress' ), $this->fullname ) ) );
718                $this->avatar_mini  = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'thumb', 'alt' => sprintf( __( 'Avatar of %s', 'buddypress' ), $this->fullname ), 'width' => 30, 'height' => 30 ) );
719                $this->last_active  = bp_core_get_last_activity( bp_get_user_meta( $this->id, 'last_activity', true ), __( 'active %s', 'buddypress' ) );
720        }
721
722        /**
723         * Populates extra fields such as group and friendship counts.
724         */
725        function populate_extras() {
726
727                if ( bp_is_active( 'friends' ) ) {
728                        $this->total_friends = BP_Friends_Friendship::total_friend_count( $this->id );
729                }
730
731                if ( bp_is_active( 'groups' ) ) {
732                        $this->total_groups = BP_Groups_Member::total_group_count( $this->id );
733                        $this->total_groups = sprintf( _n( '%d group', '%d groups', $this->total_groups, 'buddypress' ), $this->total_groups );
734                }
735        }
736
737        function get_profile_data() {
738                return BP_XProfile_ProfileData::get_all_for_user( $this->id );
739        }
740
741        /** Static Methods ********************************************************/
742
743        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 ) {
744                global $wpdb, $bp;
745
746                _deprecated_function( __METHOD__, '1.7', 'BP_User_Query' );
747
748                $sql = array();
749
750                $sql['select_main'] = "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.display_name, u.user_email";
751
752                if ( 'active' == $type || 'online' == $type || 'newest' == $type  ) {
753                        $sql['select_active'] = ", um.meta_value as last_activity";
754                }
755
756                if ( 'popular' == $type ) {
757                        $sql['select_popular'] = ", um.meta_value as total_friend_count";
758                }
759
760                if ( 'alphabetical' == $type ) {
761                        $sql['select_alpha'] = ", pd.value as fullname";
762                }
763
764                if ( $meta_key ) {
765                        $sql['select_meta'] = ", umm.meta_key";
766
767                        if ( $meta_value ) {
768                                $sql['select_meta'] .= ", umm.meta_value";
769                        }
770                }
771
772                $sql['from'] = "FROM {$wpdb->users} u LEFT JOIN {$wpdb->usermeta} um ON um.user_id = u.ID";
773
774                // We search against xprofile fields, so we must join the table
775                if ( $search_terms && bp_is_active( 'xprofile' ) ) {
776                        $sql['join_profiledata_search'] = "LEFT JOIN {$bp->profile->table_name_data} spd ON u.ID = spd.user_id";
777                }
778
779                // Alphabetical sorting is done by the xprofile Full Name field
780                if ( 'alphabetical' == $type ) {
781                        $sql['join_profiledata_alpha'] = "LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id";
782                }
783
784                if ( $meta_key ) {
785                        $sql['join_meta'] = "LEFT JOIN {$wpdb->usermeta} umm ON umm.user_id = u.ID";
786                }
787
788                $sql['where'] = 'WHERE ' . bp_core_get_status_sql( 'u.' );
789
790                if ( 'active' == $type || 'online' == $type || 'newest' == $type ) {
791                        $sql['where_active'] = $wpdb->prepare( "AND um.meta_key = %s", bp_get_user_meta_key( 'last_activity' ) );
792                }
793
794                if ( 'popular' == $type ) {
795                        $sql['where_popular'] = $wpdb->prepare( "AND um.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) );
796                }
797
798                if ( 'online' == $type ) {
799                        $sql['where_online'] = "AND DATE_ADD( um.meta_value, INTERVAL 5 MINUTE ) >= UTC_TIMESTAMP()";
800                }
801
802                if ( 'alphabetical' == $type ) {
803                        $sql['where_alpha'] = "AND pd.field_id = 1";
804                }
805
806                if ( !empty( $exclude ) ) {
807                        $sql['where_exclude'] = "AND u.ID NOT IN ({$exclude})";
808                }
809
810                // Passing an $include value of 0 or '0' will necessarily result in an empty set
811                // returned. The default value of false will hit the 'else' clause.
812                if ( 0 === $include || '0' === $include ) {
813                        $sql['where_users'] = "AND 0 = 1";
814                } else {
815                        if ( !empty( $include ) ) {
816                                if ( is_array( $include ) ) {
817                                        $uids = $wpdb->escape( implode( ',', (array) $include ) );
818                                } else {
819                                        $uids = $wpdb->escape( $include );
820                                }
821
822                                if ( !empty( $uids ) ) {
823                                        $sql['where_users'] = "AND u.ID IN ({$uids})";
824                                }
825                        } elseif ( !empty( $user_id ) && bp_is_active( 'friends' ) ) {
826                                $friend_ids = friends_get_friend_user_ids( $user_id );
827                                $friend_ids = $wpdb->escape( implode( ',', (array) $friend_ids ) );
828
829                                if ( !empty( $friend_ids ) ) {
830                                        $sql['where_friends'] = "AND u.ID IN ({$friend_ids})";
831
832                                // User has no friends, return false since there will be no users to fetch.
833                                } else {
834                                        return false;
835                                }
836                        }
837                }
838
839                if ( !empty( $search_terms ) && bp_is_active( 'xprofile' ) ) {
840                        $search_terms             = like_escape( $wpdb->escape( $search_terms ) );
841                        $sql['where_searchterms'] = "AND spd.value LIKE '%%$search_terms%%'";
842                }
843
844                if ( !empty( $meta_key ) ) {
845                        $sql['where_meta'] = $wpdb->prepare( " AND umm.meta_key = %s", $meta_key );
846
847                        // If a meta value is provided, match it
848                        if ( $meta_value ) {
849                                $sql['where_meta'] .= $wpdb->prepare( " AND umm.meta_value = %s", $meta_value );
850                        }
851                }
852
853                switch ( $type ) {
854                        case 'active': case 'online': default:
855                                $sql[] = "ORDER BY um.meta_value DESC";
856                                break;
857                        case 'newest':
858                                $sql[] = "ORDER BY u.ID DESC";
859                                break;
860                        case 'alphabetical':
861                                $sql[] = "ORDER BY pd.value ASC";
862                                break;
863                        case 'random':
864                                $sql[] = "ORDER BY rand()";
865                                break;
866                        case 'popular':
867                                $sql[] = "ORDER BY CONVERT(um.meta_value, SIGNED) DESC";
868                                break;
869                }
870
871                if ( !empty( $limit ) && !empty( $page ) ) {
872                        $sql['pagination'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
873                }
874
875                // Get paginated results
876                $paged_users_sql = apply_filters( 'bp_core_get_paged_users_sql', join( ' ', (array) $sql ), $sql );
877                $paged_users     = $wpdb->get_results( $paged_users_sql );
878
879                // Re-jig the SQL so we can get the total user count
880                unset( $sql['select_main'] );
881
882                if ( !empty( $sql['select_active'] ) ) {
883                        unset( $sql['select_active'] );
884                }
885
886                if ( !empty( $sql['select_popular'] ) ) {
887                        unset( $sql['select_popular'] );
888                }
889
890                if ( !empty( $sql['select_alpha'] ) ) {
891                        unset( $sql['select_alpha'] );
892                }
893
894                if ( !empty( $sql['pagination'] ) ) {
895                        unset( $sql['pagination'] );
896                }
897
898                array_unshift( $sql, "SELECT COUNT(DISTINCT u.ID)" );
899
900                // Get total user results
901                $total_users_sql = apply_filters( 'bp_core_get_total_users_sql', join( ' ', (array) $sql ), $sql );
902                $total_users     = $wpdb->get_var( $total_users_sql );
903
904                /***
905                 * Lets fetch some other useful data in a separate queries, this will be faster than querying the data for every user in a list.
906                 * 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)
907                 */
908                if ( !empty( $populate_extras ) ) {
909                        $user_ids = array();
910
911                        foreach ( (array) $paged_users as $user ) {
912                                $user_ids[] = $user->id;
913                        }
914
915                        $user_ids = $wpdb->escape( join( ',', (array) $user_ids ) );
916
917                        // Add additional data to the returned results
918                        $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids, $type );
919                }
920
921                return array( 'users' => $paged_users, 'total' => $total_users );
922        }
923
924
925        /**
926         * Fetches the user details for all the users who username starts with the letter given.
927         *
928         * @global BuddyPress $bp The one true BuddyPress instance
929         * @global wpdb $wpdb WordPress database object
930         * @param string $letter The letter the users names are to start with.
931         * @param integer $limit The number of users we wish to retrive.
932         * @param integer $page The page number we are currently on, used in conjunction with $limit to get the start position for the limit.
933         * @param boolean $populate_extras Populate extra user fields?
934         * @param string $exclude Comma-separated IDs of users whose results aren't to be fetched.
935         * @return mixed False on error, otherwise associative array of results.
936         * @static
937         */
938        function get_users_by_letter( $letter, $limit = null, $page = 1, $populate_extras = true, $exclude = '' ) {
939                global $bp, $wpdb;
940
941                $pag_sql = '';
942                if ( $limit && $page ) {
943                        $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
944                }
945
946                // Multibyte compliance
947                if ( function_exists( 'mb_strlen' ) ) {
948                        if ( mb_strlen( $letter, 'UTF-8' ) > 1 || is_numeric( $letter ) || !$letter ) {
949                                return false;
950                        }
951                } else {
952                        if ( strlen( $letter ) > 1 || is_numeric( $letter ) || !$letter ) {
953                                return false;
954                        }
955                }
956
957                $letter     = like_escape( $wpdb->escape( $letter ) );
958                $status_sql = bp_core_get_status_sql( 'u.' );
959
960                $exclude_sql = ( !empty( $exclude ) ) ? " AND u.ID NOT IN ({$exclude})" : "";
961
962                $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 '{$letter}%%'  ORDER BY pd.value ASC", bp_xprofile_fullname_field_name() ) );
963                $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 '{$letter}%%' ORDER BY pd.value ASC{$pag_sql}", bp_xprofile_fullname_field_name() ) );
964
965                $total_users = $wpdb->get_var( $total_users_sql );
966                $paged_users = $wpdb->get_results( $paged_users_sql );
967
968                /***
969                 * Lets fetch some other useful data in a separate queries, this will be
970                 * faster than querying the data for every user in a list. We can't add
971                 * these to the main query above since only users who have this
972                 * information will be returned (since the much of the data is in
973                 * usermeta and won't support any type of directional join)
974                 */
975                $user_ids = array();
976                foreach ( (array) $paged_users as $user )
977                        $user_ids[] = $user->id;
978
979                $user_ids = $wpdb->escape( join( ',', (array) $user_ids ) );
980
981                // Add additional data to the returned results
982                if ( $populate_extras ) {
983                        $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids );
984                }
985
986                return array( 'users' => $paged_users, 'total' => $total_users );
987        }
988
989        /**
990         * Get details of specific users from the database
991         *
992         * @global wpdb $wpdb WordPress database object
993         * @param array $user_ids The user IDs of the users who we wish to fetch information on.
994         * @param integer $limit The limit of results we want.
995         * @param integer $page The page we are on for pagination.
996         * @param boolean $populate_extras Populate extra user fields?
997         * @return array Associative array
998         * @static
999         */
1000        function get_specific_users( $user_ids, $limit = null, $page = 1, $populate_extras = true ) {
1001                global $wpdb;
1002
1003                $pag_sql = '';
1004                if ( $limit && $page )
1005                        $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
1006
1007                $status_sql = bp_core_get_status_sql();
1008
1009                $total_users_sql = apply_filters( 'bp_core_get_specific_users_count_sql', "SELECT COUNT(DISTINCT ID) FROM {$wpdb->users} WHERE {$status_sql} AND ID IN ( " . $wpdb->escape( $user_ids ) . " ) " );
1010                $paged_users_sql = apply_filters( 'bp_core_get_specific_users_count_sql', "SELECT DISTINCT ID as id, user_registered, user_nicename, user_login, user_email FROM {$wpdb->users} WHERE {$status_sql} AND ID IN ( " . $wpdb->escape( $user_ids ) . " ) {$pag_sql}" );
1011
1012                $total_users = $wpdb->get_var( $total_users_sql );
1013                $paged_users = $wpdb->get_results( $paged_users_sql );
1014
1015                /***
1016                 * Lets fetch some other useful data in a separate queries, this will be
1017                 * faster than querying the data for every user in a list. We can't add
1018                 * these to the main query above since only users who have this
1019                 * information will be returned (since the much of the data is in
1020                 * usermeta and won't support any type of directional join)
1021                 */
1022
1023                // Add additional data to the returned results
1024                if ( !empty( $populate_extras ) ) {
1025                        $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids );
1026                }
1027
1028                return array( 'users' => $paged_users, 'total' => $total_users );
1029        }
1030
1031        /**
1032         * Find users who match on the value of an xprofile data.
1033         *
1034         * @global BuddyPress $bp The one true BuddyPress instance
1035         * @global wpdb $wpdb WordPress database object
1036         * @param string $search_terms The terms to search the profile table value column for.
1037         * @param integer $limit The limit of results we want.
1038         * @param integer $page The page we are on for pagination.
1039         * @param boolean $populate_extras Populate extra user fields?
1040         * @return array Associative array
1041         * @static
1042         */
1043        function search_users( $search_terms, $limit = null, $page = 1, $populate_extras = true ) {
1044                global $bp, $wpdb;
1045
1046                $user_ids = array();
1047                $pag_sql  = $limit && $page ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * intval( $limit ) ), intval( $limit ) ) : '';
1048
1049                $search_terms = like_escape( $wpdb->escape( $search_terms ) );
1050                $status_sql   = bp_core_get_status_sql( 'u.' );
1051
1052                $total_users_sql = apply_filters( 'bp_core_search_users_count_sql', "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 '%%{$search_terms}%%' ORDER BY pd.value ASC", $search_terms );
1053                $paged_users_sql = apply_filters( 'bp_core_search_users_sql',       "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 '%%{$search_terms}%%' ORDER BY pd.value ASC{$pag_sql}", $search_terms, $pag_sql );
1054
1055                $total_users = $wpdb->get_var( $total_users_sql );
1056                $paged_users = $wpdb->get_results( $paged_users_sql );
1057
1058                /***
1059                 * Lets fetch some other useful data in a separate queries, this will be faster than querying the data for every user in a list.
1060                 * 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)
1061                 */
1062                foreach ( (array) $paged_users as $user )
1063                        $user_ids[] = $user->id;
1064
1065                $user_ids = $wpdb->escape( join( ',', (array) $user_ids ) );
1066
1067                // Add additional data to the returned results
1068                if ( $populate_extras )
1069                        $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids );
1070
1071                return array( 'users' => $paged_users, 'total' => $total_users );
1072        }
1073
1074        /**
1075         * Fetch extra user information, such as friend count and last profile update message.
1076         *
1077         * Accepts multiple user IDs to fetch data for.
1078         *
1079         * @global BuddyPress $bp The one true BuddyPress instance
1080         * @global wpdb $wpdb WordPress database object
1081         * @param array $paged_users an array of stdClass containing the users
1082         * @param string $user_ids the user ids to select information about
1083         * @param string $type the type of fields we wish to get
1084         * @return mixed False on error, otherwise associative array of results.
1085         * @static
1086         */
1087        function get_user_extras( &$paged_users, &$user_ids, $type = false ) {
1088                global $bp, $wpdb;
1089
1090                if ( empty( $user_ids ) )
1091                        return $paged_users;
1092
1093                // Fetch the user's full name
1094                if ( bp_is_active( 'xprofile' ) && 'alphabetical' != $type ) {
1095                        $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() ) );
1096                        for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
1097                                foreach ( (array) $names as $name ) {
1098                                        if ( $name->id == $paged_users[$i]->id )
1099                                                $paged_users[$i]->fullname = $name->fullname;
1100                                }
1101                        }
1102                }
1103
1104                // Fetch the user's total friend count
1105                if ( 'popular' != $type ) {
1106                        $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' ) ) );
1107                        for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
1108                                foreach ( (array) $friend_count as $fcount ) {
1109                                        if ( $fcount->id == $paged_users[$i]->id )
1110                                                $paged_users[$i]->total_friend_count = (int) $fcount->total_friend_count;
1111                                }
1112                        }
1113                }
1114
1115                // Fetch whether or not the user is a friend
1116                if ( bp_is_active( 'friends' ) ) {
1117                        $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() ) );
1118                        for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
1119                                foreach ( (array) $friend_status as $status ) {
1120                                        if ( $status->initiator_user_id == $paged_users[$i]->id || $status->friend_user_id == $paged_users[$i]->id )
1121                                                $paged_users[$i]->is_friend = $status->is_confirmed;
1122                                }
1123                        }
1124                }
1125
1126                if ( 'active' != $type ) {
1127                        $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' ) ) );
1128                        for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
1129                                foreach ( (array) $user_activity as $activity ) {
1130                                        if ( $activity->id == $paged_users[$i]->id )
1131                                                $paged_users[$i]->last_activity = $activity->last_activity;
1132                                }
1133                        }
1134                }
1135
1136                // Fetch the user's last_activity
1137                if ( 'active' != $type ) {
1138                        $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' ) ) );
1139                        for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
1140                                foreach ( (array) $user_activity as $activity ) {
1141                                        if ( $activity->id == $paged_users[$i]->id )
1142                                                $paged_users[$i]->last_activity = $activity->last_activity;
1143                                }
1144                        }
1145                }
1146
1147                // Fetch the user's latest update
1148                $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' ) ) );
1149                for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
1150                        foreach ( (array) $user_update as $update ) {
1151                                if ( $update->id == $paged_users[$i]->id )
1152                                        $paged_users[$i]->latest_update = $update->latest_update;
1153                        }
1154                }
1155
1156                return $paged_users;
1157        }
1158
1159        /**
1160         * Get WordPress user details for a specified user.
1161         *
1162         * @global wpdb $wpdb WordPress database object
1163         * @param integer $user_id User ID
1164         * @return array Associative array
1165         * @static
1166         */
1167        function get_core_userdata( $user_id ) {
1168                global $wpdb;
1169
1170                if ( !$user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->users} WHERE ID = %d LIMIT 1", $user_id ) ) )
1171                        return false;
1172
1173                return $user;
1174        }
1175}
1176
1177
1178/**
1179 * BP_Core_Notification class can be used by any component.
1180 * It will handle the fetching, saving and deleting of a user notification.
1181 *
1182 * @package BuddyPress Core
1183 */
1184
1185class BP_Core_Notification {
1186
1187        /**
1188         * The notification id
1189         *
1190         * @var integer
1191         */
1192        var $id;
1193
1194        /**
1195         * The ID to which the notification relates to within the component.
1196         *
1197         * @var integer
1198         */
1199        var $item_id;
1200
1201        /**
1202         * The secondary ID to which the notification relates to within the component.
1203         *
1204         * @var integer
1205         */
1206        var $secondary_item_id = null;
1207
1208        /**
1209         * The user ID for who the notification is for.
1210         *
1211         * @var integer
1212         */
1213        var $user_id;
1214
1215        /**
1216         * The name of the component that the notification is for.
1217         *
1218         * @var string
1219         */
1220        var $component_name;
1221
1222        /**
1223         * The action within the component which the notification is related to.
1224         *
1225         * @var string
1226         */
1227        var $component_action;
1228
1229        /**
1230         * The date the notification was created.
1231         *
1232         * @var string
1233         */
1234        var $date_notified;
1235
1236        /**
1237         * Is the notification new or has it already been read.
1238         *
1239         * @var boolean
1240         */
1241        var $is_new;
1242
1243        /** Public Methods ********************************************************/
1244
1245        /**
1246         * Constructor
1247         *
1248         * @param integer $id
1249         */
1250        function __construct( $id = 0 ) {
1251                if ( !empty( $id ) ) {
1252                        $this->id = $id;
1253                        $this->populate();
1254                }
1255        }
1256
1257        /**
1258         * Update or insert notification details into the database.
1259         *
1260         * @global BuddyPress $bp The one true BuddyPress instance
1261         * @global wpdb $wpdb WordPress database object
1262         * @return bool Success or failure
1263         */
1264        function save() {
1265                global $bp, $wpdb;
1266
1267                // Update
1268                if ( !empty( $this->id ) ) {
1269                        $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 );
1270
1271                // Save
1272                } else {
1273                        $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 );
1274                }
1275
1276                if ( !$result = $wpdb->query( $sql ) )
1277                        return false;
1278
1279                $this->id = $wpdb->insert_id;
1280
1281                return true;
1282        }
1283
1284        /** Private Methods *******************************************************/
1285
1286        /**
1287         * Fetches the notification data from the database.
1288         *
1289         * @global BuddyPress $bp The one true BuddyPress instance
1290         * @global wpdb $wpdb WordPress database object
1291         */
1292        function populate() {
1293                global $bp, $wpdb;
1294
1295                if ( $notification = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->core->table_name_notifications} WHERE id = %d", $this->id ) ) ) {
1296                        $this->item_id = $notification->item_id;
1297                        $this->secondary_item_id = $notification->secondary_item_id;
1298                        $this->user_id           = $notification->user_id;
1299                        $this->component_name    = $notification->component_name;
1300                        $this->component_action  = $notification->component_action;
1301                        $this->date_notified     = $notification->date_notified;
1302                        $this->is_new            = $notification->is_new;
1303                }
1304        }
1305
1306        /** Static Methods ********************************************************/
1307
1308        function check_access( $user_id, $notification_id ) {
1309                global $wpdb, $bp;
1310
1311                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 ) );
1312        }
1313
1314        /**
1315         * Fetches all the notifications in the database for a specific user.
1316         *
1317         * @global BuddyPress $bp The one true BuddyPress instance
1318         * @global wpdb $wpdb WordPress database object
1319         * @param integer $user_id User ID
1320         * @param str $status 'is_new' or 'all'
1321         * @return array Associative array
1322         * @static
1323         */
1324        function get_all_for_user( $user_id, $status = 'is_new' ) {
1325                global $bp, $wpdb;
1326
1327                $is_new = ( 'is_new' == $status ) ? ' AND is_new = 1 ' : '';
1328
1329                return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->core->table_name_notifications} WHERE user_id = %d {$is_new}", $user_id ) );
1330        }
1331
1332        /**
1333         * Delete all the notifications for a user based on the component name and action.
1334         *
1335         * @global BuddyPress $bp The one true BuddyPress instance
1336         * @global wpdb $wpdb WordPress database object
1337         * @param integer $user_id
1338         * @param string $component_name
1339         * @param string $component_action
1340         * @static
1341         */
1342        function delete_for_user_by_type( $user_id, $component_name, $component_action ) {
1343                global $bp, $wpdb;
1344
1345                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 ) );
1346        }
1347
1348        /**
1349         * Delete all the notifications that have a specific item id, component name and action.
1350         *
1351         * @global BuddyPress $bp The one true BuddyPress instance
1352         * @global wpdb $wpdb WordPress database object
1353         * @param integer $user_id The ID of the user who the notifications are for.
1354         * @param integer $item_id The item ID of the notifications we wish to delete.
1355         * @param string $component_name The name of the component that the notifications we wish to delete.
1356         * @param string $component_action The action of the component that the notifications we wish to delete.
1357         * @param integer $secondary_item_id (optional) The secondary item id of the notifications that we wish to use to delete.
1358         * @static
1359         */
1360        function delete_for_user_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id = false ) {
1361                global $bp, $wpdb;
1362
1363                $secondary_item_sql = !empty( $secondary_item_id ) ? $wpdb->prepare( " AND secondary_item_id = %d", $secondary_item_id ) : '';
1364
1365                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 ) );
1366        }
1367
1368        /**
1369         * Deletes all the notifications sent by a specific user, by component and action.
1370         *
1371         * @global BuddyPress $bp The one true BuddyPress instance
1372         * @global wpdb $wpdb WordPress database object
1373         * @param integer $user_id The ID of the user whose sent notifications we wish to delete.
1374         * @param string $component_name The name of the component the notification was sent from.
1375         * @param string $component_action The action of the component the notification was sent from.
1376         * @static
1377         */
1378        function delete_from_user_by_type( $user_id, $component_name, $component_action ) {
1379                global $bp, $wpdb;
1380
1381                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 ) );
1382        }
1383
1384        /**
1385         * Deletes all the notifications for all users by item id, and optional secondary item id, and component name and action.
1386         *
1387         * @global BuddyPress $bp The one true BuddyPress instance
1388         * @global wpdb $wpdb WordPress database object
1389         * @param string $item_id The item id that they notifications are to be for.
1390         * @param string $component_name The component that the notifications are to be from.
1391         * @param string $component_action The action that the notificationsa are to be from.
1392         * @param string $secondary_item_id Optional secondary item id that the notifications are to have.
1393         * @static
1394         */
1395        function delete_all_by_type( $item_id, $component_name, $component_action, $secondary_item_id ) {
1396                global $bp, $wpdb;
1397
1398                if ( $component_action )
1399                        $component_action_sql = $wpdb->prepare( "AND component_action = %s", $component_action );
1400                else
1401                        $component_action_sql = '';
1402
1403                if ( $secondary_item_id )
1404                        $secondary_item_sql = $wpdb->prepare( "AND secondary_item_id = %d", $secondary_item_id );
1405                else
1406                        $secondary_item_sql = '';
1407
1408                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 ) );
1409        }
1410}
1411
1412/**
1413 * BP_Button
1414 *
1415 * API to create BuddyPress buttons
1416 *
1417 * component: Which component this button is for
1418 * must_be_logged_in: Button only appears for logged in users
1419 * block_self: Button will not appear when viewing your own profile.
1420 * wrapper: div|span|p|li|false for no wrapper
1421 * wrapper_id: The DOM ID of the button wrapper
1422 * wrapper_class: The DOM class of the button wrapper
1423 * link_href: The destination link of the button
1424 * link_title: Title of the button
1425 * link_id: The DOM ID of the button
1426 * link_class: The DOM class of the button
1427 * link_rel: The DOM rel of the button
1428 * link_text: The text of the button
1429 * contents: The contents of the button
1430 *
1431 * @package BuddyPress Core
1432 * @since BuddyPress (1.2.6)
1433 */
1434class BP_Button {
1435
1436        /** Button properties *****************************************************/
1437
1438        /**
1439         * The button ID
1440         *
1441         * @var integer
1442         */
1443        public $id = '';
1444
1445        /**
1446         * The component name that button belongs to.
1447         *
1448         * @var string
1449         */
1450        public $component = 'core';
1451
1452        /**
1453         * Does the user need to be logged in to see this button?
1454         *
1455         * @var boolean
1456         */
1457        public $must_be_logged_in = true;
1458
1459        /**
1460         * True or false if the button should not be displayed while viewing your
1461         * own profile.
1462         *
1463         * @var boolean
1464         */
1465        public $block_self = true;
1466
1467        /** Wrapper ***************************************************************/
1468
1469        /**
1470         * What type of DOM element to use for a wrapper.
1471         *
1472         *
1473         * @var mixed div|span|p|li, or false for no wrapper
1474         */
1475        public $wrapper = 'div';
1476
1477        /**
1478         * The DOM class of the button wrapper
1479         *
1480         * @var string
1481         */
1482        public $wrapper_class = '';
1483
1484        /**
1485         * The DOM ID of the button wrapper
1486         *
1487         * @var string
1488         */
1489        public $wrapper_id = '';
1490
1491        /** Button ****************************************************************/
1492
1493        /**
1494         * The destination link of the button
1495         *
1496         * @var string
1497         */
1498        public $link_href = '';
1499
1500        /**
1501         * The DOM class of the button link
1502         *
1503         * @var string
1504         */
1505        public $link_class = '';
1506
1507        /**
1508         * The DOM ID of the button link
1509         *
1510         * @var string
1511         */
1512        public $link_id = '';
1513
1514        /**
1515         * The DOM rel value of the button link
1516         *
1517         * @var string
1518         */
1519        public $link_rel = '';
1520
1521        /**
1522         * Title of the button link
1523         *
1524         * @var string
1525         */
1526        public $link_title = '';
1527
1528        /**
1529         * The contents of the button link
1530         *
1531         * @var string
1532         */
1533        public $link_text = '';
1534
1535        /** HTML result ***********************************************************/
1536
1537        public $contents = '';
1538
1539        /** Methods ***************************************************************/
1540
1541        /**
1542         * Builds the button based on class parameters:
1543         *
1544         * @since BuddyPress (1.2.6)
1545         *
1546         * @param array $args
1547         * @return bool False if not allowed
1548         */
1549        public function __construct( $args = '' ) {
1550
1551                $r = wp_parse_args( $args, get_class_vars( __CLASS__ ) );
1552
1553                // Required button properties
1554                $this->id                = $r['id'];
1555                $this->component         = $r['component'];
1556                $this->must_be_logged_in = (bool) $r['must_be_logged_in'];
1557                $this->block_self        = (bool) $r['block_self'];
1558                $this->wrapper           = $r['wrapper'];
1559
1560                // $id and $component are required
1561                if ( empty( $r['id'] ) || empty( $r['component'] ) )
1562                        return false;
1563
1564                // No button if component is not active
1565                if ( ! bp_is_active( $this->component ) )
1566                        return false;
1567
1568                // No button for guests if must be logged in
1569                if ( true == $this->must_be_logged_in && ! is_user_logged_in() )
1570                        return false;
1571
1572                // No button if viewing your own profile
1573                if ( true == $this->block_self && bp_is_my_profile() )
1574                        return false;
1575
1576                // Wrapper properties
1577                if ( false !== $this->wrapper ) {
1578
1579                        // Wrapper ID
1580                        if ( !empty( $r['wrapper_id'] ) ) {
1581                                $this->wrapper_id    = ' id="' . $r['wrapper_id'] . '"';
1582                        }
1583
1584                        // Wrapper class
1585                        if ( !empty( $r['wrapper_class'] ) ) {
1586                                $this->wrapper_class = ' class="generic-button ' . $r['wrapper_class'] . '"';
1587                        } else {
1588                                $this->wrapper_class = ' class="generic-button"';
1589                        }
1590
1591                        // Set before and after
1592                        $before = '<' . $r['wrapper'] . $this->wrapper_class . $this->wrapper_id . '>';
1593                        $after  = '</' . $r['wrapper'] . '>';
1594
1595                // No wrapper
1596                } else {
1597                        $before = $after = '';
1598                }
1599
1600                // Link properties
1601                if ( !empty( $r['link_id']    ) ) $this->link_id    = ' id="' .    $r['link_id']    . '"';
1602                if ( !empty( $r['link_href']  ) ) $this->link_href  = ' href="' .  $r['link_href']  . '"';
1603                if ( !empty( $r['link_title'] ) ) $this->link_title = ' title="' . $r['link_title'] . '"';
1604                if ( !empty( $r['link_rel']   ) ) $this->link_rel   = ' rel="' .   $r['link_rel']   . '"';
1605                if ( !empty( $r['link_class'] ) ) $this->link_class = ' class="' . $r['link_class'] . '"';
1606                if ( !empty( $r['link_text']  ) ) $this->link_text  =              $r['link_text'];
1607
1608                // Build the button
1609                $this->contents = $before . '<a'. $this->link_href . $this->link_title . $this->link_id . $this->link_rel . $this->link_class . '>' . $this->link_text . '</a>' . $after;
1610
1611                // Allow button to be manipulated externally
1612                $this->contents = apply_filters( 'bp_button_' . $this->component . '_' . $this->id, $this->contents, $this, $before, $after );
1613        }
1614
1615        /**
1616         * Return contents of button
1617         *
1618         * @since BuddyPress (1.2.6)
1619         *
1620         * @return string
1621         */
1622        public function contents() {
1623                return $this->contents;
1624        }
1625
1626        /**
1627         * Output contents of button
1628         *
1629         * @since BuddyPress (1.2.6)
1630         */
1631        public function display() {
1632                if ( !empty( $this->contents ) )
1633                        echo $this->contents;
1634        }
1635}
1636
1637/**
1638 * BP_Embed
1639 *
1640 * Extends WP_Embed class for use with BuddyPress.
1641 *
1642 * @package BuddyPress Core
1643 * @since BuddyPress (1.5)
1644 * @see WP_Embed
1645 */
1646class BP_Embed extends WP_Embed {
1647
1648        /**
1649         * Constructor
1650         *
1651         * @global unknown $wp_embed
1652         */
1653        function __construct() {
1654                global $wp_embed;
1655
1656                // Make sure we populate the WP_Embed handlers array.
1657                // These are providers that use a regex callback on the URL in question.
1658                // Do not confuse with oEmbed providers, which require an external ping.
1659                // Used in WP_Embed::shortcode()
1660                $this->handlers = $wp_embed->handlers;
1661
1662                if ( bp_use_embed_in_activity() ) {
1663                        add_filter( 'bp_get_activity_content_body', array( &$this, 'autoembed' ), 8 );
1664                        add_filter( 'bp_get_activity_content_body', array( &$this, 'run_shortcode' ), 7 );
1665                }
1666
1667                if ( bp_use_embed_in_activity_replies() ) {
1668                        add_filter( 'bp_get_activity_content', array( &$this, 'autoembed' ), 8 );
1669                        add_filter( 'bp_get_activity_content', array( &$this, 'run_shortcode' ), 7 );
1670                }
1671
1672                if ( bp_use_embed_in_forum_posts() ) {
1673                        add_filter( 'bp_get_the_topic_post_content', array( &$this, 'autoembed' ), 8 );
1674                        add_filter( 'bp_get_the_topic_post_content', array( &$this, 'run_shortcode' ), 7 );
1675                }
1676
1677                if ( bp_use_embed_in_private_messages() ) {
1678                        add_filter( 'bp_get_the_thread_message_content', array( &$this, 'autoembed' ), 8 );
1679                        add_filter( 'bp_get_the_thread_message_content', array( &$this, 'run_shortcode' ), 7 );
1680                }
1681
1682                do_action_ref_array( 'bp_core_setup_oembed', array( &$this ) );
1683        }
1684
1685        /**
1686         * The {@link do_shortcode()} callback function.
1687         *
1688         * Attempts to convert a URL into embed HTML. Starts by checking the URL against the regex of the registered embed handlers.
1689         * Next, checks the URL against the regex of registered {@link WP_oEmbed} providers if oEmbed discovery is false.
1690         * If none of the regex matches and it's enabled, then the URL will be passed to {@link BP_Embed::parse_oembed()} for oEmbed parsing.
1691         *
1692         * @uses wp_parse_args()
1693         * @uses wp_embed_defaults()
1694         * @uses current_user_can()
1695         * @uses _wp_oembed_get_object()
1696         * @uses WP_Embed::maybe_make_link()
1697         *
1698         * @param array $attr Shortcode attributes.
1699         * @param string $url The URL attempting to be embeded.
1700         * @return string The embed HTML on success, otherwise the original URL.
1701         */
1702        function shortcode( $attr, $url = '' ) {
1703                if ( empty( $url ) )
1704                        return '';
1705
1706                $rawattr = $attr;
1707                $attr = wp_parse_args( $attr, wp_embed_defaults() );
1708
1709                // kses converts & into &amp; and we need to undo this
1710                // See http://core.trac.wordpress.org/ticket/11311
1711                $url = str_replace( '&amp;', '&', $url );
1712
1713                // Look for known internal handlers
1714                ksort( $this->handlers );
1715                foreach ( $this->handlers as $priority => $handlers ) {
1716                        foreach ( $handlers as $hid => $handler ) {
1717                                if ( preg_match( $handler['regex'], $url, $matches ) && is_callable( $handler['callback'] ) ) {
1718                                        if ( false !== $return = call_user_func( $handler['callback'], $matches, $attr, $url, $rawattr ) ) {
1719                                                return apply_filters( 'embed_handler_html', $return, $url, $attr );
1720                                        }
1721                                }
1722                        }
1723                }
1724
1725                // Get object ID
1726                $id = apply_filters( 'embed_post_id', 0 );
1727
1728                // Is oEmbed discovery on?
1729                $attr['discover'] = ( apply_filters( 'bp_embed_oembed_discover', false ) && current_user_can( 'unfiltered_html' ) );
1730
1731                // Set up a new WP oEmbed object to check URL with registered oEmbed providers
1732                require_once( ABSPATH . WPINC . '/class-oembed.php' );
1733                $oembed_obj = _wp_oembed_get_object();
1734
1735                // If oEmbed discovery is true, skip oEmbed provider check
1736                $is_oembed_link = false;
1737                if ( !$attr['discover'] ) {
1738                        foreach ( (array) $oembed_obj->providers as $provider_matchmask => $provider ) {
1739                                $regex = ( $is_regex = $provider[1] ) ? $provider_matchmask : '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $provider_matchmask ), '#' ) ) . '#i';
1740
1741                                if ( preg_match( $regex, $url ) )
1742                                        $is_oembed_link = true;
1743                        }
1744
1745                        // If url doesn't match a WP oEmbed provider, stop parsing
1746                        if ( !$is_oembed_link )
1747                                return $this->maybe_make_link( $url );
1748                }
1749
1750                return $this->parse_oembed( $id, $url, $attr, $rawattr );
1751        }
1752
1753        /**
1754         * Base function so BP components / plugins can parse links to be embedded.
1755         * View an example to add support in {@link bp_activity_embed()}.
1756         *
1757         * @uses apply_filters() Filters cache.
1758         * @uses do_action() To save cache.
1759         * @uses wp_oembed_get() Connects to oEmbed provider and returns HTML on success.
1760         * @uses WP_Embed::maybe_make_link() Process URL for hyperlinking on oEmbed failure.
1761         * @param int $id ID to do the caching for.
1762         * @param string $url The URL attempting to be embedded.
1763         * @param array $attr Shortcode attributes from {@link WP_Embed::shortcode()}.
1764         * @param array $rawattr Untouched shortcode attributes from {@link WP_Embed::shortcode()}.
1765         * @return string The embed HTML on success, otherwise the original URL.
1766         */
1767        function parse_oembed( $id, $url, $attr, $rawattr ) {
1768                $id = intval( $id );
1769
1770                if ( $id ) {
1771                        // Setup the cachekey
1772                        $cachekey = '_oembed_' . md5( $url . serialize( $attr ) );
1773
1774                        // Let components / plugins grab their cache
1775                        $cache = '';
1776                        $cache = apply_filters( 'bp_embed_get_cache', $cache, $id, $cachekey, $url, $attr, $rawattr );
1777
1778                        // Grab cache and return it if available
1779                        if ( !empty( $cache ) ) {
1780                                return apply_filters( 'bp_embed_oembed_html', $cache, $url, $attr, $rawattr );
1781
1782                        // If no cache, ping the oEmbed provider and cache the result
1783                        } else {
1784                                $html = wp_oembed_get( $url, $attr );
1785                                $cache = ( $html ) ? $html : $url;
1786
1787                                // Let components / plugins save their cache
1788                                do_action( 'bp_embed_update_cache', $cache, $cachekey, $id );
1789
1790                                // If there was a result, return it
1791                                if ( $html )
1792                                        return apply_filters( 'bp_embed_oembed_html', $html, $url, $attr, $rawattr );
1793                        }
1794                }
1795
1796                // Still unknown
1797                return $this->maybe_make_link( $url );
1798        }
1799}
1800
1801/**
1802 * Create HTML list of BP nav items
1803 *
1804 * @since BuddyPress (1.7)
1805 */
1806class BP_Walker_Nav_Menu extends Walker_Nav_Menu {
1807        /**
1808         * @since BuddyPress (1.7)
1809         * @var array
1810         */
1811        var $db_fields = array( 'id' => 'css_id', 'parent' => 'parent' );
1812
1813        /**
1814         * @since BuddyPress (1.7)
1815         * @var string
1816         */
1817        var $tree_type = array();
1818
1819        /**
1820         * Display array of elements hierarchically.
1821         *
1822         * This method is almost identical to the version in {@link Walker::walk()}. The only change is on one line
1823         * which has been commented. An IF was comparing 0 to a non-empty string which was preventing child elements
1824         * being grouped under their parent menu element.
1825         *
1826         * This caused a problem for BuddyPress because our primary/secondary navigations doesn't have a unique numerical
1827         * ID that describes a hierarchy (we use a slug). Obviously, WordPress Menus use Posts, and those have ID/post_parent.
1828         *
1829         * @param array $elements
1830         * @param int $max_depth
1831         * @return string
1832         * @see Walker::walk()
1833         * @since BuddyPress (1.7)
1834         */
1835        function walk( $elements, $max_depth ) {
1836                $args   = array_slice( func_get_args(), 2 );
1837                $output = '';
1838
1839                if ( $max_depth < -1 ) // invalid parameter
1840                        return $output;
1841
1842                if ( empty( $elements ) ) // nothing to walk
1843                        return $output;
1844
1845                $id_field     = $this->db_fields['id'];
1846                $parent_field = $this->db_fields['parent'];
1847
1848                // flat display
1849                if ( -1 == $max_depth ) {
1850
1851                        $empty_array = array();
1852                        foreach ( $elements as $e )
1853                                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
1854
1855                        return $output;
1856                }
1857
1858                /*
1859                 * need to display in hierarchical order
1860                 * separate elements into two buckets: top level and children elements
1861                 * children_elements is two dimensional array, eg.
1862                 * children_elements[10][] contains all sub-elements whose parent is 10.
1863                 */
1864                $top_level_elements = array();
1865                $children_elements  = array();
1866
1867                foreach ( $elements as $e ) {
1868                        // BuddyPress: changed '==' to '==='. This is the only change from version in Walker::walk().
1869                        if ( 0 === $e->$parent_field )
1870                                $top_level_elements[] = $e;
1871                        else
1872                                $children_elements[$e->$parent_field][] = $e;
1873                }
1874
1875                /*
1876                 * when none of the elements is top level
1877                 * assume the first one must be root of the sub elements
1878                 */
1879                if ( empty( $top_level_elements ) ) {
1880
1881                        $first              = array_slice( $elements, 0, 1 );
1882                        $root               = $first[0];
1883                        $top_level_elements = array();
1884                        $children_elements  = array();
1885
1886                        foreach ( $elements as $e ) {
1887                                if ( $root->$parent_field == $e->$parent_field )
1888                                        $top_level_elements[] = $e;
1889                                else
1890                                        $children_elements[$e->$parent_field][] = $e;
1891                        }
1892                }
1893
1894                foreach ( $top_level_elements as $e )
1895                        $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
1896
1897                /*
1898                 * if we are displaying all levels, and remaining children_elements is not empty,
1899                 * then we got orphans, which should be displayed regardless
1900                 */
1901                if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
1902                        $empty_array = array();
1903
1904                        foreach ( $children_elements as $orphans )
1905                                foreach ( $orphans as $op )
1906                                        $this->display_element( $op, $empty_array, 1, 0, $args, $output );
1907                 }
1908
1909                 return $output;
1910        }
1911
1912        /**
1913         * Displays the current <li> that we are on.
1914         *
1915         * @param string $output Passed by reference. Used to append additional content.
1916         * @param object $item Menu item data object.
1917         * @param int $depth Depth of menu item. Used for padding. Optional, defaults to 0.
1918         * @param array $args Optional
1919         * @param int $current_page Menu item ID. Optional.
1920         * @since BuddyPress (1.7)
1921         */
1922        function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
1923                // If we're someway down the tree, indent the HTML with the appropriate number of tabs
1924                $indent = $depth ? str_repeat( "\t", $depth ) : '';
1925
1926                // Add HTML classes
1927                $class_names = join( ' ', apply_filters( 'bp_nav_menu_css_class', array_filter( $item->class ), $item, $args ) );
1928                $class_names = ! empty( $class_names ) ? ' class="' . esc_attr( $class_names ) . '"' : '';
1929
1930                // Add HTML ID
1931                $id = sanitize_html_class( $item->css_id . '-personal-li' );  // Backpat with BP pre-1.7
1932                $id = apply_filters( 'bp_nav_menu_item_id', $id, $item, $args );
1933                $id = ! empty( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';
1934
1935                // Opening tag; closing tag is handled in Walker_Nav_Menu::end_el().
1936                $output .= $indent . '<li' . $id . $class_names . '>';
1937
1938                // Add href attribute
1939                $attributes = ! empty( $item->link ) ? ' href="' . esc_attr( esc_url( $item->link ) ) . '"' : '';
1940
1941                // Construct the link
1942                $item_output = $args->before;
1943                $item_output .= '<a' . $attributes . '>';
1944                $item_output .= $args->link_before . apply_filters( 'the_title', $item->name, 0 ) . $args->link_after;
1945                $item_output .= '</a>';
1946                $item_output .= $args->after;
1947
1948                // $output is byref
1949                $output .= apply_filters( 'bp_walker_nav_menu_start_el', $item_output, $item, $depth, $args );
1950        }
1951}