Skip to:
Content

BuddyPress.org

Changeset 8557


Ignore:
Timestamp:
07/03/2014 08:02:18 PM (12 years ago)
Author:
djpaul
Message:

at-mentions: overhaul the mentions implementation for Private Messages and Groups (admin) components.

Previously, these components had seperate implementations of username auto-suggestions, which weren't
reusable outside of where they'd been built, nor easily extended by developers, or other plugins.
This change creates a central API for auto-suggestions, currently only for usernames, but easily
extensible for other kinds of auto-suggestions in the future (i.e. group names, or #hashtags).

See #3278

Location:
trunk
Files:
3 added
5 edited

Legend:

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

    r8541 r8557  
    24082408        }
    24092409}
     2410
     2411/**
     2412 * Base class for the BuddyPress Suggestions API.
     2413 *
     2414 * Originally built to power BuddyPress' at-mentions suggestions, it's flexible enough to be used
     2415 * for similar kinds of future core requirements, or those desired by third-party developers.
     2416 *
     2417 * To implement a new suggestions service, create a new class that extends this one, and update
     2418 * the list of default services in {@link bp_core_get_suggestions()}. If you're building a plugin,
     2419 * it's recommend that you use the `bp_suggestions_services` filter to do this. :)
     2420 *
     2421 * While the implementation of the query logic is left to you, it should be as quick and efficient
     2422 * as possible. When implementing the abstract methods in this class, pay close attention to the
     2423 * recommendations provided in the phpDoc blocks, particularly the expected return types.
     2424 *
     2425 * @since BuddyPress (2.1.0)
     2426 */
     2427abstract class BP_Suggestions {
     2428
     2429        /**
     2430         * Default arguments common to all suggestions services.
     2431         *
     2432         * If your custom service requires further defaults, add them here.
     2433         *
     2434         * @since BuddyPress (2.1.0)
     2435         * @var array
     2436         */
     2437        protected $default_args = array(
     2438                'limit' => 16,
     2439                'term'  => '',
     2440                'type'  => '',
     2441        );
     2442
     2443        /**
     2444         * Holds the arguments for the query (about to made to the suggestions service).
     2445         *
     2446         * This includes `$default_args`, as well as the user-supplied values.
     2447         *
     2448         * @since BuddyPress (2.1.0)
     2449         * @var array
     2450         */
     2451        protected $args = array(
     2452        );
     2453
     2454
     2455        /**
     2456         * Constructor.
     2457         *
     2458         * @param array $args Optional. If set, used as the parameters for the suggestions service query.
     2459         * @since BuddyPress (2.1.0)
     2460         */
     2461        public function __construct( array $args = array() ) {
     2462                if ( ! empty( $args ) ) {
     2463                        $this->set_query( $args );
     2464                }
     2465        }
     2466
     2467        /**
     2468         * Set the parameters for the suggestions service query.
     2469         *
     2470         * @param array $args {
     2471         *     @type int $limit Maximum number of results to display. Optional, default: 16.
     2472         *     @type string $type The name of the suggestion service to use for the request. Mandatory.
     2473         *     @type string $term The suggestion service will try to find results that contain this string.
     2474         *           Mandatory.
     2475         * }
     2476         * @since BuddyPress (2.1.0)
     2477         */
     2478        public function set_query( array $args = array() ) {
     2479                $this->args = wp_parse_args( $args, $this->default_args );
     2480        }
     2481
     2482        /**
     2483         * Validate and sanitise the parameters for the suggestion service query.
     2484         *
     2485         * Be sure to call this class' version of this method when implementing it in your own service.
     2486         * If validation fails, you must return a WP_Error object.
     2487         *
     2488         * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
     2489         * @since BuddyPress (2.1.0)
     2490         */
     2491        public function validate() {
     2492                $this->args['limit'] = absint( $this->args['limit'] );
     2493                $this->args['term']  = trim( sanitize_text_field( $this->args['term'] ) );
     2494                $this->args          = apply_filters( 'bp_suggestions_args', $this->args, $this );
     2495
     2496
     2497                // Check for invalid or missing mandatory parameters.
     2498                if ( ! $this->args['limit'] || ! $this->args['term'] ) {
     2499                        return new WP_Error( 'missing_parameter' );
     2500                }
     2501
     2502                // Check for blocked users (e.g. deleted accounts, or spammers).
     2503                if ( is_user_logged_in() && ! bp_is_user_active( get_current_user_id() ) ) {
     2504                        return new WP_Error( 'invalid_user' );
     2505                }
     2506
     2507                return apply_filters( 'bp_suggestions_validate_args', true, $this );
     2508        }
     2509
     2510        /**
     2511         * Find and return a list of suggestions that match the query.
     2512         *
     2513         * The return type is important. If no matches are found, an empty array must be returned.
     2514         * Matches must be returned as objects in an array.
     2515         *
     2516         * The object format for each match must be: { 'ID': string, 'image': string, 'name': string }
     2517         * For example: { 'ID': 'admin', 'image': 'http://example.com/logo.png', 'name': 'Name Surname' }
     2518         *
     2519         * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
     2520         * @since BuddyPress (2.1.0)
     2521         */
     2522        abstract public function get_suggestions();
     2523}
     2524
     2525/**
     2526 * Adds support for user at-mentions to the Suggestions API.
     2527 *
     2528 * This class is in the Core component because it's required by a class in the Groups component,
     2529 * and Groups is loaded before Members (alphabetical order).
     2530 *
     2531 * @since BuddyPress (2.1.0)
     2532 */
     2533class BP_Members_Suggestions extends BP_Suggestions {
     2534
     2535        /**
     2536         * Default arguments for this suggestions service.
     2537         *
     2538         * @since BuddyPress (2.1.0)
     2539         * @var array $args {
     2540         *     @type int $limit Maximum number of results to display. Default: 16.
     2541         *     @type bool $only_friends If true, only match the current user's friends. Default: false.
     2542         *     @type string $term The suggestion service will try to find results that contain this string.
     2543         *           Mandatory.
     2544         * }
     2545         */
     2546        protected $default_args = array(
     2547                'limit'        => 16,
     2548                'only_friends' => false,
     2549                'term'         => '',
     2550                'type'         => '',
     2551        );
     2552
     2553
     2554        /**
     2555         * Validate and sanitise the parameters for the suggestion service query.
     2556         *
     2557         * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
     2558         * @since BuddyPress (2.1.0)
     2559         */
     2560        public function validate() {
     2561                $this->args['only_friends'] = (bool) $this->args['only_friends'];
     2562                $this->args                 = apply_filters( 'bp_members_suggestions_args', $this->args, $this );
     2563
     2564                // Check for invalid or missing mandatory parameters.
     2565                if ( $this->args['only_friends'] && ( ! bp_is_active( 'friends' ) || ! is_user_logged_in() ) ) {
     2566                        return new WP_Error( 'missing_requirement' );
     2567                }
     2568
     2569                return apply_filters( 'bp_members_suggestions_validate_args', parent::validate(), $this );
     2570        }
     2571
     2572        /**
     2573         * Find and return a list of username suggestions that match the query.
     2574         *
     2575         * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
     2576         * @since BuddyPress (2.1.0)
     2577         */
     2578        public function get_suggestions() {
     2579                $user_query = array(
     2580                        'count_total'     => '',  // Prevents total count
     2581                        'populate_extras' => false,
     2582                        'type'            => 'alphabetical',
     2583
     2584                        'page'            => 1,
     2585                        'per_page'        => $this->args['limit'],
     2586                        'search_terms'    => $this->args['term'],
     2587                );
     2588
     2589                // Only return matches of friends of this user.
     2590                if ( $this->args['only_friends'] && is_user_logged_in() ) {
     2591                        $user_query['user_id'] = get_current_user_id();
     2592                }
     2593
     2594                $user_query = apply_filters( 'bp_members_suggestions_query_args', $user_query, $this );
     2595                if ( is_wp_error( $user_query ) ) {
     2596                        return $user_query;
     2597                }
     2598
     2599
     2600                $user_query = new BP_User_Query( $user_query );
     2601                $results    = array();
     2602
     2603                foreach ( $user_query->results as $user ) {
     2604                        $result        = new stdClass();
     2605                        $result->ID    = $user->user_nicename;
     2606                        $result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
     2607                        $result->name  = bp_core_get_user_displayname( $user->ID );
     2608
     2609                        $results[] = $result;
     2610                }
     2611
     2612                return apply_filters( 'bp_members_suggestions_get_suggestions', $results, $this );
     2613        }
     2614}
  • trunk/src/bp-core/bp-core-functions.php

    r8541 r8557  
    19221922        return $nav_item_url;
    19231923}
     1924
     1925/** Suggestions***************************************************************/
     1926
     1927/**
     1928 * BuddyPress Suggestions API for types of at-mentions.
     1929 *
     1930 * This is used to power BuddyPress' at-mentions suggestions, but it is flexible enough to be used
     1931 * for similar kinds of future requirements, or those implemented by third-party developers.
     1932 *
     1933 * @param array $args
     1934 * @return array|WP_Error Array of results. If there were any problems, returns a WP_Error object.
     1935 * @since BuddyPress (2.1.0)
     1936 */
     1937function bp_core_get_suggestions( $args ) {
     1938        $args = wp_parse_args( $args );
     1939
     1940        if ( ! $args['type'] ) {
     1941                return new WP_Error( 'missing_parameter' );
     1942        }
     1943
     1944        // Members @name suggestions.
     1945        if ( $args['type'] === 'members' ) {
     1946                $class = 'BP_Members_Suggestions';
     1947
     1948                // Members @name suggestions for users in a specific Group.
     1949                if ( isset( $args['group_id'] ) ) {
     1950                        $class = 'BP_Groups_Member_Suggestions';
     1951                }
     1952
     1953        } else {
     1954                // If you've built a custom suggestions service, use this to tell BP the name of your class.
     1955                $class = apply_filters( 'bp_suggestions_services', '', $args );
     1956        }
     1957
     1958        if ( ! $class || ! class_exists( $class ) ) {
     1959                return new WP_Error( 'missing_parameter' );
     1960        }
     1961
     1962
     1963        $suggestions = new $class( $args );
     1964        $validation  = $suggestions->validate();
     1965
     1966        if ( is_wp_error( $validation ) ) {
     1967                $retval = $validation;
     1968        } else {
     1969                $retval = $suggestions->get_suggestions();
     1970        }
     1971
     1972        return apply_filters( 'bp_core_get_suggestions', $retval, $args );
     1973}
  • trunk/src/bp-groups/bp-groups-admin.php

    r8485 r8557  
    985985
    986986        // Bail if user user shouldn't be here, or is a large network
    987         if ( ! current_user_can( 'bp_moderate' ) || ( is_multisite() && wp_is_large_network( 'users' ) ) )
     987        if ( ! current_user_can( 'bp_moderate' ) || ( is_multisite() && wp_is_large_network( 'users' ) ) ) {
    988988                wp_die( -1 );
    989 
    990         $return = array();
    991 
    992         // Exclude current group members
    993         $group_id = isset( $_GET['group_id'] ) ? wp_parse_id_list( $_GET['group_id'] ) : array();
    994         $group_member_query = new BP_Group_Member_Query( array(
    995                 'group_id'        => $group_id,
    996                 'per_page'        => 0, // show all
    997                 'group_role'      => array( 'member', 'mod', 'admin', ),
    998                 'populate_extras' => false,
    999                 'count_total'     => false,
     989        }
     990
     991        $term     = isset( $_GET['term'] )     ? sanitize_text_field( $_GET['term'] ) : '';
     992        $group_id = isset( $_GET['group_id'] ) ? absint( $_GET['group_id'] )          : 0;
     993
     994        if ( ! $term || ! $group_id ) {
     995                wp_die( -1 );
     996        }
     997
     998        $suggestions = bp_core_get_suggestions( array(
     999                'group_id' => -$group_id,  // A negative value will exclude this group's members from the suggestions.
     1000                'limit'    => 10,
     1001                'term'     => $term,
     1002                'type'     => 'members',
    10001003        ) );
    10011004
    1002         $group_members = ! empty( $group_member_query->results ) ? wp_list_pluck( $group_member_query->results, 'ID' ) : array();
    1003 
    1004         $terms = isset( $_GET['term'] ) ? $_GET['term'] : '';
    1005         $users = bp_core_get_users( array(
    1006                 'type'            => 'alphabetical',
    1007                 'search_terms'    => $terms,
    1008                 'exclude'         => $group_members,
    1009                 'per_page'        => 10,
    1010                 'populate_extras' => false
    1011         ) );
    1012 
    1013         foreach ( (array) $users['users'] as $user ) {
    1014                 $return[] = array(
    1015                         /* translators: 1: user_login, 2: user_email */
    1016                         'label' => sprintf( __( '%1$s (%2$s)', 'buddypress' ), bp_is_username_compatibility_mode() ? $user->user_login : $user->user_nicename, $user->user_email ),
    1017                         'value' => $user->user_nicename,
    1018                 );
    1019         }
    1020 
    1021         wp_die( json_encode( $return ) );
     1005        $matches = array();
     1006
     1007        if ( $suggestions && ! is_wp_error( $suggestions ) ) {
     1008                foreach ( $suggestions as $user ) {
     1009
     1010                        $matches[] = array(
     1011                                // translators: 1: user_login, 2: user_email
     1012                                'label' => sprintf( __( '%1$s (%2$s)', 'buddypress' ), $user->name, $user->ID ),
     1013                                'value' => $user->ID,
     1014                        );
     1015                }
     1016        }
     1017
     1018        wp_die( json_encode( $matches ) );
    10221019}
    10231020add_action( 'wp_ajax_bp_group_admin_member_autocomplete', 'bp_groups_admin_autocomplete_handler' );
  • trunk/src/bp-groups/bp-groups-classes.php

    r8541 r8557  
    40774077        ' ), 11 );
    40784078}
     4079
     4080/**
     4081 * Adds support for user at-mentions (for users in a specific Group) to the Suggestions API.
     4082 *
     4083 * @since BuddyPress (2.1.0)
     4084 */
     4085class BP_Groups_Member_Suggestions extends BP_Members_Suggestions {
     4086
     4087        /**
     4088         * Default arguments for this suggestions service.
     4089         *
     4090         * @since BuddyPress (2.1.0)
     4091         * @var array $args {
     4092         *     @type int $group_id Positive integers will restrict the search to members in that group.
     4093         *           Negative integers will restrict the search to members in every other group.
     4094         *     @type int $limit Maximum number of results to display. Default: 16.
     4095         *     @type bool $only_friends If true, only match the current user's friends. Default: false.
     4096         *     @type string $term The suggestion service will try to find results that contain this string.
     4097         *           Mandatory.
     4098         * }
     4099         */
     4100        protected $default_args = array(
     4101                'group_id'     => 0,
     4102                'limit'        => 16,
     4103                'only_friends' => false,
     4104                'term'         => '',
     4105                'type'         => '',
     4106        );
     4107
     4108
     4109        /**
     4110         * Validate and sanitise the parameters for the suggestion service query.
     4111         *
     4112         * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
     4113         * @since BuddyPress (2.1.0)
     4114         */
     4115        public function validate() {
     4116                $this->args['group_id'] = (int) $this->args['group_id'];
     4117                $this->args             = apply_filters( 'bp_groups_member_suggestions_args', $this->args, $this );
     4118
     4119                // Check for invalid or missing mandatory parameters.
     4120                if ( ! $this->args['group_id'] || ! bp_is_active( 'groups' ) ) {
     4121                        return new WP_Error( 'missing_requirement' );
     4122                }
     4123
     4124                // Check that the specified group_id exists, and that the current user can access it.
     4125                $the_group = groups_get_group( array(
     4126                        'group_id'        => absint( $this->args['group_id'] ),
     4127                        'populate_extras' => true,
     4128                ) );
     4129
     4130                if ( $the_group->id === 0 || ! $the_group->user_has_access ) {
     4131                        return new WP_Error( 'access_denied' );
     4132                }
     4133
     4134                return apply_filters( 'bp_groups_member_suggestions_validate_args', parent::validate(), $this );
     4135        }
     4136
     4137        /**
     4138         * Find and return a list of username suggestions that match the query.
     4139         *
     4140         * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
     4141         * @since BuddyPress (2.1.0)
     4142         */
     4143        public function get_suggestions() {
     4144                $user_query = array(
     4145                        'count_total'     => '',  // Prevents total count
     4146                        'populate_extras' => false,
     4147                        'type'            => 'alphabetical',
     4148
     4149                        'group_role'      => array( 'admin', 'member', 'mod' ),
     4150                        'page'            => 1,
     4151                        'per_page'        => $this->args['limit'],
     4152                        'search_terms'    => $this->args['term'],
     4153                );
     4154
     4155                // Only return matches of friends of this user.
     4156                if ( $this->args['only_friends'] && is_user_logged_in() ) {
     4157                        $user_query['user_id'] = get_current_user_id();
     4158                }
     4159
     4160                // Positive Group IDs will restrict the search to members in that group.
     4161                if ( $this->args['group_id'] > 0 ) {
     4162                        $user_query['group_id'] = $this->args['group_id'];
     4163
     4164                // Negative Group IDs will restrict the search to members in every other group.
     4165                } else {
     4166                        $group_query = array(
     4167                                'count_total'     => '',  // Prevents total count
     4168                                'populate_extras' => false,
     4169                                'type'            => 'alphabetical',
     4170
     4171                                'group_id'        => absint( $this->args['group_id'] ),
     4172                                'group_role'      => array( 'admin', 'member', 'mod' ),
     4173                                'page'            => 1,
     4174                        );
     4175                        $group_users = new BP_Group_Member_Query( $group_query );
     4176
     4177                        if ( $group_users->results ) {
     4178                                $user_query['exclude'] = wp_list_pluck( $group_users->results, 'ID' );
     4179                        } else {
     4180                                $user_query['include'] = array( 0 );
     4181                        }
     4182                }
     4183
     4184                $user_query = apply_filters( 'bp_groups_member_suggestions_query_args', $user_query, $this );
     4185                if ( is_wp_error( $user_query ) ) {
     4186                        return $user_query;
     4187                }
     4188
     4189
     4190                if ( isset( $user_query['group_id'] ) ) {
     4191                        $user_query = new BP_Group_Member_Query( $user_query );
     4192                } else {
     4193                        $user_query = new BP_User_Query( $user_query );
     4194                }
     4195
     4196                $results = array();
     4197                foreach ( $user_query->results as $user ) {
     4198                        $result        = new stdClass();
     4199                        $result->ID    = $user->user_nicename;
     4200                        $result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
     4201                        $result->name  = bp_core_get_user_displayname( $user->ID );
     4202
     4203                        $results[] = $result;
     4204                }
     4205
     4206                return apply_filters( 'bp_groups_member_suggestions_get_suggestions', $results, $this );
     4207        }
     4208}
  • trunk/src/bp-templates/bp-legacy/buddypress-functions.php

    r8341 r8557  
    13841384 */
    13851385function bp_legacy_theme_ajax_messages_autocomplete_results() {
    1386 
     1386        $limit = isset( $_GET['limit'] ) ? absint( $_GET['limit'] )          : (int) apply_filters( 'bp_autocomplete_max_results', 10 );
     1387        $term  = isset( $_GET['q'] )     ? sanitize_text_field( $_GET['q'] ) : '';
     1388       
    13871389        // Include everyone in the autocomplete, or just friends?
    1388         if ( bp_is_current_component( bp_get_messages_slug() ) )
    1389                 $autocomplete_all = buddypress()->messages->autocomplete_all;
    1390 
    1391         $pag_page     = 1;
    1392         $limit        = (int) $_GET['limit'] ? $_GET['limit'] : apply_filters( 'bp_autocomplete_max_results', 10 );
    1393         $search_terms = isset( $_GET['q'] ) ? $_GET['q'] : '';
    1394 
    1395         $user_query_args = array(
    1396                 'search_terms' => $search_terms,
    1397                 'page'         => intval( $pag_page ),
    1398                 'per_page'     => intval( $limit ),
    1399         );
    1400 
    1401         // If only matching against friends, get an $include param for
    1402         // BP_User_Query
    1403         if ( ! $autocomplete_all && bp_is_active( 'friends' ) ) {
    1404                 $include = BP_Friends_Friendship::get_friend_user_ids( bp_loggedin_user_id() );
    1405 
    1406                 // Ensure zero matches if no friends are found
    1407                 if ( empty( $include ) ) {
    1408                         $include = array( 0 );
    1409                 }
    1410 
    1411                 $user_query_args['include'] = $include;
    1412         }
    1413 
    1414         $user_query = new BP_User_Query( $user_query_args );
    1415 
    1416         // Backward compatibility - if a plugin is expecting a legacy
    1417         // filter, pass the IDs through the filter and requery (groan)
    1418         if ( has_filter( 'bp_core_autocomplete_ids' ) || has_filter( 'bp_friends_autocomplete_ids' ) ) {
    1419                 $found_user_ids = wp_list_pluck( $user_query->results, 'ID' );
    1420 
    1421                 if ( $autocomplete_all ) {
    1422                         $found_user_ids = apply_filters( 'bp_core_autocomplete_ids', $found_user_ids );
    1423                 } else {
    1424                         $found_user_ids = apply_filters( 'bp_friends_autocomplete_ids', $found_user_ids );
    1425                 }
    1426 
    1427                 if ( empty( $found_user_ids ) ) {
    1428                         $found_user_ids = array( 0 );
    1429                 }
    1430 
    1431                 // Repopulate the $user_query variable
    1432                 $user_query = new BP_User_Query( array(
    1433                         'include' => $found_user_ids,
    1434                 ) );
    1435         }
    1436 
    1437         if ( ! empty( $user_query->results ) ) {
    1438                 foreach ( $user_query->results as $user ) {
    1439                         if ( bp_is_username_compatibility_mode() ) {
    1440                                 // Sanitize for spaces. Use urlencode() rather
    1441                                 // than rawurlencode() because %20 breaks JS
    1442                                 $username = urlencode( $user->user_login );
    1443                         } else {
    1444                                 $username = $user->user_nicename;
    1445                         }
     1390        if ( bp_is_current_component( bp_get_messages_slug() ) ) {
     1391                $only_friends = ( buddypress()->messages->autocomplete_all === false );
     1392        } else {
     1393                $only_friends = true;
     1394        }
     1395
     1396        $suggestions = bp_core_get_suggestions( array(
     1397                'limit'        => $limit,
     1398                'only_friends' => $only_friends,
     1399                'term'         => $term,
     1400                'type'         => 'members',
     1401        ) );
     1402
     1403        if ( $suggestions && ! is_wp_error( $suggestions ) ) {
     1404                foreach ( $suggestions as $user ) {
    14461405
    14471406                        // Note that the final line break acts as a delimiter for the
    14481407                        // autocomplete javascript and thus should not be removed
    1449                         echo '<span id="link-' . esc_attr( $username ) . '" href="' . bp_core_get_user_domain( $user->ID ) . '"></span>' . bp_core_fetch_avatar( array( 'item_id' => $user->ID, 'type' => 'thumb', 'width' => 15, 'height' => 15, 'alt' => $user->display_name ) ) . ' &nbsp;' . bp_core_get_user_displayname( $user->ID ) . ' (' . esc_html( $username ) . ')' . "\n";
    1450                 }
    1451         }
    1452 
    1453         exit;
    1454 }
     1408                        printf( '<span id="%s" href="#"></span><img src="%s" style="width: 15px"> &nbsp; %s (%s)' . "\n",
     1409                                esc_attr( 'link-' . $user->ID ),
     1410                                esc_url( $user->image ),
     1411                                esc_html( $user->name ),
     1412                                esc_html( $user->ID )
     1413                        );
     1414                }
     1415        }
     1416
     1417        exit;
     1418}
Note: See TracChangeset for help on using the changeset viewer.