Skip to:
Content

BuddyPress.org

Changeset 8557


Ignore:
Timestamp:
07/03/2014 08:02:18 PM (7 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.