Skip to:
Content

BuddyPress.org

Ticket #4938: bp-core-classes.php

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