From 3c4b6b991f7a882a1567122698678a115a47376d Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 13:34:35 +0100
Subject: [PATCH 01/35] Add current Suggestions API work.

---
 src/bp-core/bp-core-functions.php                  |  70 ++++
 src/bp-members/bp-members-filters.php              |  13 +
 src/bp-members/bp-members-functions.php            |  62 ++++
 .../phpunit/testcases/apis/suggestions-nonauth.php | 107 ++++++
 tests/phpunit/testcases/apis/suggestions.php       | 386 +++++++++++++++++++++
 5 files changed, 638 insertions(+)
 create mode 100644 tests/phpunit/testcases/apis/suggestions-nonauth.php
 create mode 100644 tests/phpunit/testcases/apis/suggestions.php

diff --git a/src/bp-core/bp-core-functions.php b/src/bp-core/bp-core-functions.php
index 9e46100..75cb9f5 100644
--- a/src/bp-core/bp-core-functions.php
+++ b/src/bp-core/bp-core-functions.php
@@ -1921,3 +1921,73 @@ function bp_nav_menu_get_item_url( $slug ) {
 
 	return $nav_item_url;
 }
+
+/** Suggestions***************************************************************/
+
+/**
+ * BuddyPress Suggestions API for types of at-mentions.
+ *
+ * This is used to power BuddyPress' at-mentions suggestions, but it is flexible enough to be used
+ * for similar kinds of requirements.
+ *
+ * If a Component or a third-party plugin wants to provide suggestions for a custom type of object,
+ * use the bp_suggestions_get_types filter and return an associative array with its key as the new
+ * "type", and its value as a function callback.
+ *
+ * The callback must validate any custom requirements in $args and fetch results that match $term
+ * in some relevant way. If validation fails, a WP_Error object must be returned. If no matches are
+ * found, an empty array must be returned. Matches must be returned as objects in an array.
+ *
+ * The object format for each match must be: { 'ID': string, 'image': string, 'name': string }
+ * For example: { 'ID': 'admin', 'image': 'http://example.com/logo.png', 'name': 'Name Surname' }
+ *
+ * @param array $args {
+ *     @type int limit Maximum number of results to display. Default: 16.
+ *     @type int|string $object_id Specifies a particular object that may be used by a suggestion
+ *           service. Accepts either an integer or a string. For example, this could be used by a
+ *           Groups suggestion type that searches for members who are members of a specific group.
+ *     @type string $type The name of the suggestion service to use for the request. Mandatory.
+ *     @type string $term The suggestion service will try to find results that contain this string.
+ *           Mandatory.
+ * }
+ * @return array|WP_Error Array of results. If there were any problems, returns a WP_Error object.
+ * @since BuddyPress (2.1.0)
+ */
+function bp_core_get_suggestions( $args ) {
+	$defaults = array(
+		'limit'     => 16,
+		'object_id' => 0,
+		'type'      => '',
+		'term'      => '',
+	);
+	$args = wp_parse_args( $args, $defaults );
+
+	if ( ctype_digit( $args['object_id'] ) ) {
+		$args['object_id'] = absint( $args['object_id'] );
+	} else {
+		$args['object_id'] = sanitize_text_field( $args['object_id'] );
+	}
+
+	$args['limit'] = absint( $args['limit'] );
+	$args['term']  = trim( sanitize_text_field( $args['term'] ) );
+
+	$args      = apply_filters( 'bp_suggestions_args', $args );
+	$callbacks = apply_filters( 'bp_suggestions_get_types', array() );
+
+	if ( ! in_array( $args['type'], array_keys( $callbacks ) ) ) {
+		$args['type'] = null;
+	}
+
+	if ( ! $args['limit'] || is_null( $args['type'] ) || ! is_callable( $callbacks[ $args['type'] ] ) || ! $args['term'] ) {
+		// Invalid or missing mandatory parameter.
+		return new WP_Error( 'missing_parameter' );
+	}
+
+	if ( is_user_logged_in() && ! bp_is_user_active( get_current_user_id() ) ) {
+		// Blocked user account (e.g. deleted or spammer).
+		return new WP_Error( 'invalid_user' );
+	}
+
+	$results = call_user_func( $callbacks[ $args['type'] ], $args );
+	return apply_filters( 'bp_core_get_suggestions', $results, $args );
+}
\ No newline at end of file
diff --git a/src/bp-members/bp-members-filters.php b/src/bp-members/bp-members-filters.php
index fed76b6..3c0c610 100644
--- a/src/bp-members/bp-members-filters.php
+++ b/src/bp-members/bp-members-filters.php
@@ -77,3 +77,16 @@ function bp_members_edit_profile_url( $url, $user_id, $scheme = 'admin' ) {
 	return apply_filters( 'bp_members_edit_profile_url', $profile_link, $url, $user_id, $scheme );
 }
 add_filter( 'edit_profile_url', 'bp_members_edit_profile_url', 10, 3 );
+
+/**
+ * Add support for @username mentions to BuddyPress.
+ *
+ * @param array $types Array of registered extensions to the Suggestion API.
+ * @return array
+ * @see bp_core_get_suggestions()
+ */
+function bp_members_register_suggestion_types( $types ) {
+	$types['members'] = 'bp_members_get_suggestions';
+	return $types;
+}
+add_filter( 'bp_suggestions_get_types', 'bp_members_register_suggestion_types' );
diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index 6e6a529..6e28ec3 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2085,3 +2085,65 @@ function bp_live_spammer_login_error() {
 	add_action( 'login_head', 'wp_shake_js', 12 );
 }
 add_action( 'login_form_bp-spam', 'bp_live_spammer_login_error' );
+
+/**
+ * Implement support for @username mentions.
+ *
+ * The $args parameter may include other parameters not documented here; if so, they are not used
+ * by this particular Suggestions API extension.
+ *
+ * @param array $args {
+ *     @type int limit Maximum number of results to display. Default: 16.
+ *     @type string $term The suggestion service will try to find results that contain this string.
+ *           Mandatory.
+ * }
+ * @return array|WP_Error See {@see bp_core_get_suggestions()}. If any problems, return a WP_Error.
+ * @see bp_core_get_suggestions()
+ * @since BuddyPress (2.1.0)
+ */
+function bp_members_get_suggestions( $args ) {
+	$defaults = array(
+		'limit'        => 16,     // Sanitised upstream
+		'only_friends' => false,
+		'term'         => '',     // Sanitised upstream
+	);
+	$args = apply_filters( 'bp_members_get_suggestions_args', wp_parse_args( $args, $defaults ) );
+
+	if ( $args['only_friends'] && ! bp_is_active( 'friends' ) ) {
+		// Friends component is missing.
+		return new WP_Error( 'missing_requirement' );
+	}
+
+	$user_query = array(
+		'count_total'     => '',  // Prevents total count
+		'populate_extras' => false,
+		'type'            => 'alphabetical',
+
+		'page'            => 1,
+		'per_page'        => $args['limit'],
+		'search_terms'    => $args['term'],
+	);
+
+	if ( $args['only_friends'] ) {
+		$user_query['user_id'] = get_current_user_id();
+	}
+
+
+	$user_query = new BP_User_Query( $user_query );
+	$results    = array();
+
+	foreach ( $user_query->results as $user ) {
+		$result        = new stdClass();
+		$result->ID    = $user->user_nicename;
+		$result->image = get_avatar( $user );
+		$result->name  = $user->display_name;
+
+		if ( bp_disable_profile_sync() ) {
+			$result->name = BP_XProfile_Field::get_fullname( $user->ID );
+		}
+
+		$results[] = $result;
+	}
+
+	return apply_filters( 'bp_members_get_suggestions', $results, $args );
+}
\ No newline at end of file
diff --git a/tests/phpunit/testcases/apis/suggestions-nonauth.php b/tests/phpunit/testcases/apis/suggestions-nonauth.php
new file mode 100644
index 0000000..0e1be23
--- /dev/null
+++ b/tests/phpunit/testcases/apis/suggestions-nonauth.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Suggestions API tests specifically for non-authenticated (anonymous) users.
+ *
+ * @group api
+ * @group suggestions
+ */
+class BP_Tests_Suggestions_Non_Authenticated extends BP_UnitTestCase {
+	protected $group_ids    = array();
+	protected $group_slugs  = array();
+	protected $user_ids     = array();
+
+	public function setUp() {
+		parent::setUp();
+
+		$users = array(
+			// user_login, display_name
+			array( 'aardvark',    'Bob Smith' ),
+			array( 'alpaca red',  'William Quinn' ),
+			array( 'cat',         'Lauren Curtis' ),
+			array( 'caterpillar', 'Eldon Burrows' ),
+			array( 'dog green',   'Reece Thornton' ),
+			array( 'pig',         'Joshua Barton' ),
+			array( 'rabbit blue', 'Amber Hooper' ),
+			array( 'smith',       'Robert Bar' ),
+			array( 'snake',       'Eleanor Moore' ),
+			array( 'zoom',        'Lisa Smithy' ),
+		);
+
+		// Create some dummy users.
+		foreach( $users as $user ) {
+			$this->user_ids[ $user[0] ] = $this->factory->user->create( array(
+				'display_name' => $user[1],
+				'user_login'   => $user[0],
+			) );
+		}
+
+		$this->group_slugs['hidden']  = 'the-maw';
+		$this->group_slugs['public']  = 'the-great-journey';
+		$this->group_slugs['private'] = 'tsavo-highway';
+
+		// Create dummy groups.
+		$this->group_ids['hidden']  = $this->factory->group->create( array( 'slug' => $this->group_slugs['hidden'],  'status' => 'hidden' ) );
+		$this->group_ids['public']  = $this->factory->group->create( array( 'slug' => $this->group_slugs['public'],  'status' => 'public' ) );
+		$this->group_ids['private'] = $this->factory->group->create( array( 'slug' => $this->group_slugs['private'], 'status' => 'private' ) );
+
+		// Add dummy users to dummy hidden groups.
+		groups_join_group( $this->group_ids['hidden'], $this->user_ids['pig'] );
+		groups_join_group( $this->group_ids['hidden'], $this->user_ids['alpaca red'] );
+
+		// Add dummy users to dummy public groups.
+		groups_join_group( $this->group_ids['public'], $this->user_ids['aardvark'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['smith'] );
+
+		// Add dummy users to dummy private groups.
+		groups_join_group( $this->group_ids['private'], $this->user_ids['cat'] );
+		groups_join_group( $this->group_ids['private'], $this->user_ids['caterpillar'] );
+	}
+
+
+	/**
+	 * Tests below this point are expected to fail.
+	 */
+
+	public function test_suggestions_with_type_members_and_only_friends() {
+		// only_friends requires authenticated requests
+		$suggestions = bp_core_get_suggestions( array(
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_group_and_only_friends() {
+		// only_friends requires authenticated requests
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id'    => $this->group_ids['public'],
+			'only_friends' => true,
+			'type'         => 'group-members',
+			'term'         => 'smith',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_group_hidden() {
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['hidden'],
+			'type'      => 'group-members',
+			'term'      => 'pig',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_group_private() {
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['private'],
+			'type'      => 'group-members',
+			'term'      => 'cat',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+}
\ No newline at end of file
diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
new file mode 100644
index 0000000..7fc85d7
--- /dev/null
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -0,0 +1,386 @@
+<?php
+/**
+ * Suggestions API tests for authenticated (logged in) users.
+ *
+ * @group api
+ * @group suggestions
+ */
+class BP_Tests_Suggestions_Authenticated extends BP_UnitTestCase {
+	protected $current_user = null;
+	protected $group_ids    = array();
+	protected $group_slugs  = array();
+	protected $old_user_id  = 0;
+	protected $user_ids     = array();
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->old_user_id  = get_current_user_id();
+		$this->current_user = $this->factory->user->create( array(
+			'display_name' => 'Katie Parker',
+			'user_login'   => 'admin',
+		) );
+
+		$this->set_current_user( $this->current_user );
+
+		$users = array(
+			// user_login, display_name
+			array( 'aardvark',    'Bob Smith' ),
+			array( 'alpaca red',  'William Quinn' ),
+			array( 'cat',         'Lauren Curtis' ),
+			array( 'caterpillar', 'Eldon Burrows' ),
+			array( 'dog green',   'Reece Thornton' ),
+			array( 'pig',         'Joshua Barton' ),
+			array( 'rabbit blue', 'Amber Hooper' ),
+			array( 'smith',       'Robert Bar' ),
+			array( 'snake',       'Eleanor Moore' ),
+			array( 'zoom',        'Lisa Smithy' ),
+		);
+
+		// Create some dummy users.
+		foreach( $users as $user ) {
+			$this->user_ids[ $user[0] ] = $this->factory->user->create( array(
+				'display_name' => $user[1],
+				'user_login'   => $user[0],
+			) );
+		}
+
+		// Create some dummy friendships.
+		friends_add_friend( $this->current_user, $this->user_ids['aardvark'], true );
+		friends_add_friend( $this->current_user, $this->user_ids['cat'], true );
+		friends_add_friend( $this->current_user, $this->user_ids['caterpillar'], true );
+		friends_add_friend( $this->current_user, $this->user_ids['pig'], true );
+
+		$this->group_slugs['hidden']  = 'the-maw';
+		$this->group_slugs['public']  = 'the-great-journey';
+		$this->group_slugs['private'] = 'tsavo-highway';
+
+		// Create dummy groups.
+		$this->group_ids['hidden']  = $this->factory->group->create( array( 'slug' => $this->group_slugs['hidden'],  'status' => 'hidden' ) );
+		$this->group_ids['public']  = $this->factory->group->create( array( 'slug' => $this->group_slugs['public'],  'status' => 'public' ) );
+		$this->group_ids['private'] = $this->factory->group->create( array( 'slug' => $this->group_slugs['private'], 'status' => 'private' ) );
+
+		// Add dummy users to dummy hidden groups.
+		groups_join_group( $this->group_ids['hidden'], $this->user_ids['pig'] );
+		groups_join_group( $this->group_ids['hidden'], $this->user_ids['alpaca red'] );
+
+		// Add dummy users to dummy public groups.
+		groups_join_group( $this->group_ids['public'], $this->current_user );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['aardvark'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['smith'] );
+
+		// Add dummy users to dummy private groups.
+		groups_join_group( $this->group_ids['private'], $this->user_ids['cat'] );
+		groups_join_group( $this->group_ids['private'], $this->user_ids['caterpillar'] );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_user_id );
+	}
+
+
+	public function test_suggestions_with_type_members() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'smith',
+		) );
+
+		$this->assertEquals( 3, count( $suggestions ) );  // aardvark, smith, zoom.
+	}
+
+	public function test_suggestions_with_type_members_and_limit() {
+		$suggestions = bp_core_get_suggestions( array(
+			'limit' => 2,
+			'type'  => 'members',
+			'term'  => 'smith',
+		) );
+
+		$this->assertEquals( 2, count( $suggestions ) );  // two of: aardvark, smith, zoom.
+	}
+
+	public function test_suggestions_with_type_members_and_only_friends() {
+		$suggestions = bp_core_get_suggestions( array(
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+
+		$suggestions = bp_core_get_suggestions( array(
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'cat',
+		) );
+		$this->assertEquals( 2, count( $suggestions ) );  // cat, caterpillar.
+	}
+
+	public function test_suggestions_with_type_members_and_term_as_displayname() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'aardvark',
+		) );
+
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+	}
+
+	public function test_suggestions_with_type_members_and_term_as_usernicename() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'eleanor',
+		) );
+
+		$this->assertEquals( 1, count( $suggestions ) );  // snake.
+	}
+
+	public function test_suggestions_with_term_as_current_user() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'katie',
+		) );
+		$this->assertEquals( 1, count( $suggestions ) );
+		$this->assertSame( 'admin', $suggestions[0]->id );
+	}
+
+
+	public function test_suggestions_with_type_groupmembers_public() {
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['public'],
+			'type'      => 'group-members',
+			'term'      => 'smith',
+		) );
+
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_limit() {
+		$suggestions = bp_core_get_suggestions( array(
+			'limit'     => 1,
+			'object_id' => $this->group_ids['public'],
+			'type'      => 'group-members',
+			'term'      => 'smith',
+		) );
+
+		$this->assertEquals( 1, count( $suggestions ) );  // one of: aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_only_friends() {
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id'    => $this->group_ids['public'],
+			'only_friends' => true,
+			'type'         => 'group-members',
+			'term'         => 'smith',
+		) );
+
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_term_as_displayname() {
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['public'],
+			'type'      => 'group-members',
+			'term'      => 'aardvark',
+		) );
+
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_term_as_usernicename() {
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['public'],
+			'type'      => 'group-members',
+			'term'      => 'robert',
+		) );
+
+		$this->assertEquals( 1, count( $suggestions ) );  // smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_as_id() {
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['public'],
+			'type'      => 'group-members',
+			'term'      => 'smith',
+		) );
+
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_as_slug() {
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_slugs['public'],
+			'type'      => 'group-members',
+			'term'      => 'smith',
+		) );
+
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden() {
+		// current_user isn't a member of the hidden group
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['hidden'],
+			'type'      => 'group-members',
+			'term'      => 'pig',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+		// "alpaca red" is in the hidden group
+		$this->set_current_user( $this->users['alpaca red'] );
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['hidden'],
+			'type'      => 'group-members',
+			'term'      => 'pig',
+		) );
+		$this->assertEquals( 1, count( $suggestions ) );  // pig
+	}
+
+	public function test_suggestions_with_type_groupmembers_private() {
+		// current_user isn't a member of the private group
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['private'],
+			'type'      => 'group-members',
+			'term'      => 'cat',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+		// "caterpillar" is in the private group
+		$this->set_current_user( $this->users['caterpillar'] );
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => $this->group_ids['private'],
+			'type'      => 'group-members',
+			'term'      => 'cat',
+		) );
+		$this->assertEquals( 2, count( $suggestions ) );  // cat, caterpillar
+	}
+
+
+	/**
+	 * These next tests check the format of the response from the Suggestions API.
+	 */
+
+	public function test_suggestions_response_no_matches() {
+		$suggestions = bp_core_get_suggestions( array(
+			'term' => 'abcdefghijklmnopqrstuvwxyz',
+		) );
+
+		$this->assertInternalType( 'array', $suggestions );
+		$this->assertEmpty( $suggestions );
+	}
+
+	public function test_suggestions_response_single_match() {
+		$suggestion = bp_core_get_suggestions( array(
+			'term' => 'zoom',
+		) );
+
+		$this->assertInternalType( 'array', $suggestion );
+		$this->assertNotEmpty( $suggestion );
+
+		$suggestion = array_shift( $suggestion );
+
+		$this->assertInternalType( 'object', $suggestion );
+		$this->assertAttributeNotEmpty( 'image', $suggestion );
+		$this->assertAttributeNotEmpty( 'id', $suggestion );
+		$this->assertAttributeNotEmpty( 'name', $suggestion );
+	}
+
+	public function test_suggestions_response_multiple_matches() {
+		$suggestions = bp_core_get_suggestions( array(
+			'term' => 'cat',
+		) );
+
+		$this->assertInternalType( 'array', $suggestions );
+		$this->assertNotEmpty( $suggestions );
+
+		foreach ( $suggestions as $suggestion ) {
+			$this->assertInternalType( 'object', $suggestion );
+			$this->assertAttributeNotEmpty( 'image', $suggestion );
+			$this->assertAttributeNotEmpty( 'id', $suggestion );
+			$this->assertAttributeNotEmpty( 'name', $suggestion );
+		}
+	}
+
+	public function test_suggestions_term_is_case_insensitive() {
+		$lowercase = bp_core_get_suggestions( array(
+			'term' => 'lisa',
+		) );
+		$this->assertEquals( 1, count( $lowercase ) );
+
+		$uppercase = bp_core_get_suggestions( array(
+			'term' => 'LISA',
+		) );
+		$this->assertEquals( 1, count( $uppercase ) );
+
+		$this->assertSame( $lowercase[0]->id, $uppercase[0]->id );
+		$this->assertSame( 'zoom', $lowercase[0]->id );
+	}
+
+	public function test_suggestions_response_property_types() {
+		$suggestion = bp_core_get_suggestions( array(
+			'term' => 'zoom',
+		) );
+
+		$this->assertInternalType( 'array', $suggestion );
+		$this->assertNotEmpty( $suggestion );
+
+		$suggestion = array_shift( $suggestion );
+
+		$this->assertInternalType( 'object', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'image', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'id', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'name', $suggestion );
+	}
+
+
+	/**
+	 * Tests below this point are expected to fail.
+	 */
+
+	public function test_suggestions_with_bad_type() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'fake_type',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_and_bad_object_id() {
+		// object_id must be a positive integer.
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => -12,
+			'type'      => 'group-members',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+		// object_id can also be a group slug.
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => 'fake-group-slug',
+			'type'      => 'group-members',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+		// object_id must be set when scope=group
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'group-members',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_members_and_object_id() {
+		// object_id cannot be set when scope=global
+		$suggestions = bp_core_get_suggestions( array(
+			'object_id' => 12,
+			'type'      => 'members',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_bad_term() {
+		// a non-empty term is mandatory
+		$suggestions = bp_core_get_suggestions( array(
+			'term' => '',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+}
\ No newline at end of file
-- 
1.9.3


From adb7f2705e15a288c79e51c42b28a035a83ec94b Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 14:00:10 +0100
Subject: [PATCH 02/35] Suggestions: sanitise only_friends param

---
 src/bp-members/bp-members-functions.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index 6e28ec3..9f8a498 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2107,7 +2107,6 @@ function bp_members_get_suggestions( $args ) {
 		'only_friends' => false,
 		'term'         => '',     // Sanitised upstream
 	);
-	$args = apply_filters( 'bp_members_get_suggestions_args', wp_parse_args( $args, $defaults ) );
 
 	if ( $args['only_friends'] && ! bp_is_active( 'friends' ) ) {
 		// Friends component is missing.
-- 
1.9.3


From 30569b070333aee5d2e8c870a5e033c758d3bc64 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 14:00:38 +0100
Subject: [PATCH 03/35] Suggestions: sanitise only_friends param
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

(Let’s try to commit the whole thing this time)
---
 src/bp-members/bp-members-functions.php | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index 9f8a498..bccedc1 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2107,6 +2107,9 @@ function bp_members_get_suggestions( $args ) {
 		'only_friends' => false,
 		'term'         => '',     // Sanitised upstream
 	);
+	$args                 = apply_filters( 'bp_members_get_suggestions_args', wp_parse_args( $args, $defaults ) );
+	$args['only_friends'] = (bool) $args['only_friends'];
+
 
 	if ( $args['only_friends'] && ! bp_is_active( 'friends' ) ) {
 		// Friends component is missing.
-- 
1.9.3


From 760fa37bef55193ca38b387741c66f73df364c52 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 14:51:50 +0100
Subject: [PATCH 04/35] phpDoc tweaks

---
 src/bp-members/bp-members-filters.php   | 2 +-
 src/bp-members/bp-members-functions.php | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/bp-members/bp-members-filters.php b/src/bp-members/bp-members-filters.php
index 3c0c610..8860609 100644
--- a/src/bp-members/bp-members-filters.php
+++ b/src/bp-members/bp-members-filters.php
@@ -89,4 +89,4 @@ function bp_members_register_suggestion_types( $types ) {
 	$types['members'] = 'bp_members_get_suggestions';
 	return $types;
 }
-add_filter( 'bp_suggestions_get_types', 'bp_members_register_suggestion_types' );
+add_filter( 'bp_suggestions_get_types', 'bp_members_register_suggestion_types' );
\ No newline at end of file
diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index bccedc1..63b6465 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2093,7 +2093,8 @@ function bp_live_spammer_login_error() {
  * by this particular Suggestions API extension.
  *
  * @param array $args {
- *     @type int limit Maximum number of results to display. Default: 16.
+ *     @type int $limit Maximum number of results to display. Default: 16.
+ *     @type bool $only_friends If true, only return matches who are friends with the current user. Default: false.
  *     @type string $term The suggestion service will try to find results that contain this string.
  *           Mandatory.
  * }
-- 
1.9.3


From a9e1785ca59cde277377ce6751a91d27841e5002 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 15:45:21 +0100
Subject: [PATCH 05/35] Suggestions: move user friends logic into Friends
 component

---
 src/bp-friends/bp-friends-filters.php   |  2 ++
 src/bp-friends/bp-friends-functions.php | 20 ++++++++++++++++++++
 src/bp-members/bp-members-functions.php | 11 +++--------
 3 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/src/bp-friends/bp-friends-filters.php b/src/bp-friends/bp-friends-filters.php
index 6ada0e7..69b39dd 100644
--- a/src/bp-friends/bp-friends-filters.php
+++ b/src/bp-friends/bp-friends-filters.php
@@ -50,3 +50,5 @@ function bp_friends_filter_user_query_populate_extras( BP_User_Query $user_query
 	}
 }
 add_filter( 'bp_user_query_populate_extras', 'bp_friends_filter_user_query_populate_extras', 4, 2 );
+
+add_filter( 'bp_suggestions_members_query_args', 'bp_friends_suggestions_filter_query_args', 10, 2 );
\ No newline at end of file
diff --git a/src/bp-friends/bp-friends-functions.php b/src/bp-friends/bp-friends-functions.php
index 5f797c7..dce5320 100644
--- a/src/bp-friends/bp-friends-functions.php
+++ b/src/bp-friends/bp-friends-functions.php
@@ -566,3 +566,23 @@ function friends_remove_data( $user_id ) {
 add_action( 'wpmu_delete_user',  'friends_remove_data' );
 add_action( 'delete_user',       'friends_remove_data' );
 add_action( 'bp_make_spam_user', 'friends_remove_data' );
+
+/**
+ * Extend the members' component's @username suggestions by adding support for friendships.
+ *
+ * Assumes there is currently a logged-in user.
+ *
+ * @param array $query_args The parameters that will be passed to {@see BP_User_Query}.
+ * @param array $suggestion_args Arguments originally sent to the {@see bp_core_get_suggestions()}.
+ * @return array The parameters that will be passed to {@see BP_User_Query}.
+ * @see bp_members_get_suggestions()
+ * @since BuddyPress (2.1.0)
+ */
+function bp_friends_suggestions_filter_query_args( $query_args, $suggestion_args ) {
+	if ( $suggestion_args['only_friends'] ) {
+		// Only return matches of friends of this user.
+		$query_args['user_id'] = get_current_user_id();
+	}
+
+	return $query_args;
+}
\ No newline at end of file
diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index 63b6465..d182256 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2094,7 +2094,7 @@ function bp_live_spammer_login_error() {
  *
  * @param array $args {
  *     @type int $limit Maximum number of results to display. Default: 16.
- *     @type bool $only_friends If true, only return matches who are friends with the current user. Default: false.
+ *     @type bool $only_friends If true, only match the current user's friends. Default: false.
  *     @type string $term The suggestion service will try to find results that contain this string.
  *           Mandatory.
  * }
@@ -2112,7 +2112,7 @@ function bp_members_get_suggestions( $args ) {
 	$args['only_friends'] = (bool) $args['only_friends'];
 
 
-	if ( $args['only_friends'] && ! bp_is_active( 'friends' ) ) {
+	if ( $args['only_friends'] && ( ! bp_is_active( 'friends' ) || ! is_user_logged_in() ) ) {
 		// Friends component is missing.
 		return new WP_Error( 'missing_requirement' );
 	}
@@ -2127,12 +2127,7 @@ function bp_members_get_suggestions( $args ) {
 		'search_terms'    => $args['term'],
 	);
 
-	if ( $args['only_friends'] ) {
-		$user_query['user_id'] = get_current_user_id();
-	}
-
-
-	$user_query = new BP_User_Query( $user_query );
+	$user_query = new BP_User_Query( apply_filters( 'bp_suggestions_members_query_args', $user_query, $args ) );
 	$results    = array();
 
 	foreach ( $user_query->results as $user ) {
-- 
1.9.3


From 3ceb230e58d8c688eeea73376cd1b7664b351a71 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 15:47:19 +0100
Subject: [PATCH 06/35] Suggestions: remove object_id logic for now.

Re-implementing/renaming/re-working.
---
 src/bp-core/bp-core-functions.php     | 10 ------
 src/bp-groups/bp-groups-filters.php   | 13 +++++++
 src/bp-groups/bp-groups-functions.php | 66 +++++++++++++++++++++++++++++++++++
 3 files changed, 79 insertions(+), 10 deletions(-)

diff --git a/src/bp-core/bp-core-functions.php b/src/bp-core/bp-core-functions.php
index 75cb9f5..837113a 100644
--- a/src/bp-core/bp-core-functions.php
+++ b/src/bp-core/bp-core-functions.php
@@ -1943,9 +1943,6 @@ function bp_nav_menu_get_item_url( $slug ) {
  *
  * @param array $args {
  *     @type int limit Maximum number of results to display. Default: 16.
- *     @type int|string $object_id Specifies a particular object that may be used by a suggestion
- *           service. Accepts either an integer or a string. For example, this could be used by a
- *           Groups suggestion type that searches for members who are members of a specific group.
  *     @type string $type The name of the suggestion service to use for the request. Mandatory.
  *     @type string $term The suggestion service will try to find results that contain this string.
  *           Mandatory.
@@ -1956,18 +1953,11 @@ function bp_nav_menu_get_item_url( $slug ) {
 function bp_core_get_suggestions( $args ) {
 	$defaults = array(
 		'limit'     => 16,
-		'object_id' => 0,
 		'type'      => '',
 		'term'      => '',
 	);
 	$args = wp_parse_args( $args, $defaults );
 
-	if ( ctype_digit( $args['object_id'] ) ) {
-		$args['object_id'] = absint( $args['object_id'] );
-	} else {
-		$args['object_id'] = sanitize_text_field( $args['object_id'] );
-	}
-
 	$args['limit'] = absint( $args['limit'] );
 	$args['term']  = trim( sanitize_text_field( $args['term'] ) );
 
diff --git a/src/bp-groups/bp-groups-filters.php b/src/bp-groups/bp-groups-filters.php
index 5ec2d91..8df573e 100644
--- a/src/bp-groups/bp-groups-filters.php
+++ b/src/bp-groups/bp-groups-filters.php
@@ -205,3 +205,16 @@ function groups_filter_forums_root_page_sql( $sql ) {
 	return apply_filters( 'groups_filter_bbpress_root_page_sql', 't.topic_id' );
 }
 add_filter( 'get_latest_topics_fields', 'groups_filter_forums_root_page_sql' );
+
+/**
+ * Add support for @username mentions for people who belong to a specific Group.
+ *
+ * @param array $types Array of registered extensions to the Suggestion API.
+ * @return array
+ * @see bp_core_get_suggestions()
+ */
+function bp_groups_register_suggestion_types( $types ) {
+	$types['group-members'] = 'bp_groups_get_member_suggestions';
+	return $types;
+}
+add_filter( 'bp_suggestions_get_types', 'bp_groups_register_suggestion_types' );
\ No newline at end of file
diff --git a/src/bp-groups/bp-groups-functions.php b/src/bp-groups/bp-groups-functions.php
index 971f8b1..d3cdfdd 100644
--- a/src/bp-groups/bp-groups-functions.php
+++ b/src/bp-groups/bp-groups-functions.php
@@ -1153,3 +1153,69 @@ function groups_remove_data_for_user( $user_id ) {
 add_action( 'wpmu_delete_user',  'groups_remove_data_for_user' );
 add_action( 'delete_user',       'groups_remove_data_for_user' );
 add_action( 'bp_make_spam_user', 'groups_remove_data_for_user' );
+
+
+/**
+ * Implement support for @username mentions for people who belong to a specific Group.
+ *
+ * The $args parameter may include other parameters not documented here; if so, they are not used
+ * by this particular Suggestions API extension.
+ *
+ * @param array $args {
+ *     @type int $limit Maximum number of results to display. Default: 16.
+ *     @type bool $only_friends If true, only return matches who are friends with the current user. Default: false.
+ *     @type string $term The suggestion service will try to find results that contain this string.
+ *           Mandatory.
+ * }
+ * @return array|WP_Error See {@see bp_core_get_suggestions()}. If any problems, return a WP_Error.
+ * @see bp_core_get_suggestions()
+ * @since BuddyPress (2.1.0)
+ */
+function bp_groups_get_member_suggestions( $args ) {
+	$defaults = array(
+		'limit'        => 16,     // Sanitised upstream
+		'only_friends' => false,
+		'term'         => '',     // Sanitised upstream
+	);
+	$args                 = apply_filters( 'bp_groups_get_member_suggestions_args', wp_parse_args( $args, $defaults ) );
+	$args['only_friends'] = (bool) $args['only_friends'];
+
+
+	if ( $args['only_friends'] && ! bp_is_active( 'friends' ) ) {
+		// Friends component is missing.
+		return new WP_Error( 'missing_requirement' );
+	}
+
+	$user_query = array(
+		'count_total'     => '',  // Prevents total count
+		'populate_extras' => false,
+		'type'            => 'alphabetical',
+
+		'page'            => 1,
+		'per_page'        => $args['limit'],
+		'search_terms'    => $args['term'],
+	);
+
+	if ( $args['only_friends'] ) {
+		$user_query['user_id'] = get_current_user_id();
+	}
+
+
+	$user_query = new BP_User_Query( $user_query );
+	$results    = array();
+
+	foreach ( $user_query->results as $user ) {
+		$result        = new stdClass();
+		$result->ID    = $user->user_nicename;
+		$result->image = get_avatar( $user );
+		$result->name  = $user->display_name;
+
+		if ( bp_disable_profile_sync() ) {
+			$result->name = BP_XProfile_Field::get_fullname( $user->ID );
+		}
+
+		$results[] = $result;
+	}
+
+	return apply_filters( 'bp_groups_get_member_suggestions', $results, $args );
+}
\ No newline at end of file
-- 
1.9.3


From e164fbc0aa1bb7b5ff21706383e806dc95108fa9 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 16:10:23 +0100
Subject: [PATCH 07/35] Removing code that shouldn't been committed yet.

---
 src/bp-groups/bp-groups-functions.php | 65 -----------------------------------
 1 file changed, 65 deletions(-)

diff --git a/src/bp-groups/bp-groups-functions.php b/src/bp-groups/bp-groups-functions.php
index d3cdfdd..a8cbab9 100644
--- a/src/bp-groups/bp-groups-functions.php
+++ b/src/bp-groups/bp-groups-functions.php
@@ -1154,68 +1154,3 @@ function groups_remove_data_for_user( $user_id ) {
 add_action( 'delete_user',       'groups_remove_data_for_user' );
 add_action( 'bp_make_spam_user', 'groups_remove_data_for_user' );
 
-
-/**
- * Implement support for @username mentions for people who belong to a specific Group.
- *
- * The $args parameter may include other parameters not documented here; if so, they are not used
- * by this particular Suggestions API extension.
- *
- * @param array $args {
- *     @type int $limit Maximum number of results to display. Default: 16.
- *     @type bool $only_friends If true, only return matches who are friends with the current user. Default: false.
- *     @type string $term The suggestion service will try to find results that contain this string.
- *           Mandatory.
- * }
- * @return array|WP_Error See {@see bp_core_get_suggestions()}. If any problems, return a WP_Error.
- * @see bp_core_get_suggestions()
- * @since BuddyPress (2.1.0)
- */
-function bp_groups_get_member_suggestions( $args ) {
-	$defaults = array(
-		'limit'        => 16,     // Sanitised upstream
-		'only_friends' => false,
-		'term'         => '',     // Sanitised upstream
-	);
-	$args                 = apply_filters( 'bp_groups_get_member_suggestions_args', wp_parse_args( $args, $defaults ) );
-	$args['only_friends'] = (bool) $args['only_friends'];
-
-
-	if ( $args['only_friends'] && ! bp_is_active( 'friends' ) ) {
-		// Friends component is missing.
-		return new WP_Error( 'missing_requirement' );
-	}
-
-	$user_query = array(
-		'count_total'     => '',  // Prevents total count
-		'populate_extras' => false,
-		'type'            => 'alphabetical',
-
-		'page'            => 1,
-		'per_page'        => $args['limit'],
-		'search_terms'    => $args['term'],
-	);
-
-	if ( $args['only_friends'] ) {
-		$user_query['user_id'] = get_current_user_id();
-	}
-
-
-	$user_query = new BP_User_Query( $user_query );
-	$results    = array();
-
-	foreach ( $user_query->results as $user ) {
-		$result        = new stdClass();
-		$result->ID    = $user->user_nicename;
-		$result->image = get_avatar( $user );
-		$result->name  = $user->display_name;
-
-		if ( bp_disable_profile_sync() ) {
-			$result->name = BP_XProfile_Field::get_fullname( $user->ID );
-		}
-
-		$results[] = $result;
-	}
-
-	return apply_filters( 'bp_groups_get_member_suggestions', $results, $args );
-}
\ No newline at end of file
-- 
1.9.3


From 74d23221b179788b566ae55d9ffbb2b142d0dd11 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 17:03:00 +0100
Subject: [PATCH 08/35] Allow components to bail out of suggestions if the
 request is bad

---
 src/bp-members/bp-members-functions.php | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index d182256..2fbcd17 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2118,6 +2118,7 @@ function bp_members_get_suggestions( $args ) {
 	}
 
 	$user_query = array(
+	$user_query = apply_filters( 'bp_suggestions_members_query_args', array(
 		'count_total'     => '',  // Prevents total count
 		'populate_extras' => false,
 		'type'            => 'alphabetical',
@@ -2125,9 +2126,15 @@ function bp_members_get_suggestions( $args ) {
 		'page'            => 1,
 		'per_page'        => $args['limit'],
 		'search_terms'    => $args['term'],
-	);
+	), $args );
+
+	// Allow components to bail us out if the request is bad.
+	if ( is_wp_error( $user_query ) ) {
+		return $user_query;
+	}
+
 
-	$user_query = new BP_User_Query( apply_filters( 'bp_suggestions_members_query_args', $user_query, $args ) );
+	$user_query = new BP_User_Query( $user_query );
 	$results    = array();
 
 	foreach ( $user_query->results as $user ) {
-- 
1.9.3


From 6bd552b446ac2d8879c1cacb4854a6deeb2e4bef Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 17:03:54 +0100
Subject: [PATCH 09/35] Suggestions, Members: support filtering by group.

---
 src/bp-friends/bp-friends-functions.php |  2 +-
 src/bp-groups/bp-groups-filters.php     | 13 +---------
 src/bp-groups/bp-groups-functions.php   | 42 +++++++++++++++++++++++++++++++++
 src/bp-members/bp-members-functions.php |  9 ++++++-
 4 files changed, 52 insertions(+), 14 deletions(-)

diff --git a/src/bp-friends/bp-friends-functions.php b/src/bp-friends/bp-friends-functions.php
index dce5320..fa7ff37 100644
--- a/src/bp-friends/bp-friends-functions.php
+++ b/src/bp-friends/bp-friends-functions.php
@@ -574,7 +574,7 @@ function friends_remove_data( $user_id ) {
  *
  * @param array $query_args The parameters that will be passed to {@see BP_User_Query}.
  * @param array $suggestion_args Arguments originally sent to the {@see bp_core_get_suggestions()}.
- * @return array The parameters that will be passed to {@see BP_User_Query}.
+ * @return array|WP_Error The parameters that will be passed to {@see BP_User_Query}.
  * @see bp_members_get_suggestions()
  * @since BuddyPress (2.1.0)
  */
diff --git a/src/bp-groups/bp-groups-filters.php b/src/bp-groups/bp-groups-filters.php
index 8df573e..77cbf27 100644
--- a/src/bp-groups/bp-groups-filters.php
+++ b/src/bp-groups/bp-groups-filters.php
@@ -206,15 +206,4 @@ function groups_filter_forums_root_page_sql( $sql ) {
 }
 add_filter( 'get_latest_topics_fields', 'groups_filter_forums_root_page_sql' );
 
-/**
- * Add support for @username mentions for people who belong to a specific Group.
- *
- * @param array $types Array of registered extensions to the Suggestion API.
- * @return array
- * @see bp_core_get_suggestions()
- */
-function bp_groups_register_suggestion_types( $types ) {
-	$types['group-members'] = 'bp_groups_get_member_suggestions';
-	return $types;
-}
-add_filter( 'bp_suggestions_get_types', 'bp_groups_register_suggestion_types' );
\ No newline at end of file
+add_filter( 'bp_suggestions_members_query_args', 'bp_groups_suggestions_filter_query_args', 10, 2 );
\ No newline at end of file
diff --git a/src/bp-groups/bp-groups-functions.php b/src/bp-groups/bp-groups-functions.php
index a8cbab9..a1b27df 100644
--- a/src/bp-groups/bp-groups-functions.php
+++ b/src/bp-groups/bp-groups-functions.php
@@ -1154,3 +1154,45 @@ function groups_remove_data_for_user( $user_id ) {
 add_action( 'delete_user',       'groups_remove_data_for_user' );
 add_action( 'bp_make_spam_user', 'groups_remove_data_for_user' );
 
+/*** Suggestions API ************************************************************/
+
+/**
+ * Extend the members' component's @username suggestions by adding support for group memberships.
+ *
+ * @param array $query_args The parameters that will be passed to {@see BP_User_Query}.
+ * @param array $suggestion_args Arguments originally sent to the {@see bp_core_get_suggestions()}.
+ * @return array|WP_Error The parameters that will be passed to {@see BP_User_Query}.
+ * @see bp_members_get_suggestions()
+ * @since BuddyPress (2.1.0)
+ */
+function bp_groups_suggestions_filter_query_args( $query_args, $suggestion_args ) {
+	if ( ! $suggestion_args['group_id'] ) {
+		return $query_args;
+	}
+
+	$the_group = groups_get_group( array(
+		'group_id'        => $suggestion_args['group_id'],
+		'populate_extras' => true,
+	) );
+
+	if ( $the_group->id === 0 || ! $the_group->user_has_access ) {
+		return new WP_Error( 'access_denied' );
+	}
+
+	$group_query = array(
+		'count_total'     => '',  // Prevents total count
+		'populate_extras' => false,
+		'type'            => 'alphabetical',
+
+		'group_id'        => $suggestion_args['group_id'],
+	);
+
+	$group_users = new BP_Group_Member_Query( $group_query );
+	if ( $group_users->results ) {
+		$query_args['include'] = wp_list_pluck( $group_users->results, 'ID' );
+	} else {
+		$query_args['include'] = array( 0 );
+	}
+
+	return $query_args;
+}
\ No newline at end of file
diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index 2fbcd17..24f5ddb 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2093,6 +2093,7 @@ function bp_live_spammer_login_error() {
  * by this particular Suggestions API extension.
  *
  * @param array $args {
+ *     @type int $group_id If set, only match users that are in this group.
  *     @type int $limit Maximum number of results to display. Default: 16.
  *     @type bool $only_friends If true, only match the current user's friends. Default: false.
  *     @type string $term The suggestion service will try to find results that contain this string.
@@ -2104,11 +2105,13 @@ function bp_live_spammer_login_error() {
  */
 function bp_members_get_suggestions( $args ) {
 	$defaults = array(
+		'group_id'     => 0,
 		'limit'        => 16,     // Sanitised upstream
 		'only_friends' => false,
 		'term'         => '',     // Sanitised upstream
 	);
 	$args                 = apply_filters( 'bp_members_get_suggestions_args', wp_parse_args( $args, $defaults ) );
+	$args['group_id']     = absint( $args['group_id'] );
 	$args['only_friends'] = (bool) $args['only_friends'];
 
 
@@ -2117,7 +2120,11 @@ function bp_members_get_suggestions( $args ) {
 		return new WP_Error( 'missing_requirement' );
 	}
 
-	$user_query = array(
+	if ( $args['group_id'] && ! bp_is_active( 'groups' ) ) {
+		// Groups component is missing.
+		return new WP_Error( 'missing_requirement' );
+	}
+
 	$user_query = apply_filters( 'bp_suggestions_members_query_args', array(
 		'count_total'     => '',  // Prevents total count
 		'populate_extras' => false,
-- 
1.9.3


From 6f32be7a364d78fcf09e813faffc88a75bfbbb91 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 17:04:19 +0100
Subject: [PATCH 10/35] Update tests for recent API changes.

---
 .../phpunit/testcases/apis/suggestions-nonauth.php | 18 ++--
 tests/phpunit/testcases/apis/suggestions.php       | 96 +++++++++-------------
 2 files changed, 49 insertions(+), 65 deletions(-)

diff --git a/tests/phpunit/testcases/apis/suggestions-nonauth.php b/tests/phpunit/testcases/apis/suggestions-nonauth.php
index 0e1be23..0c67d17 100644
--- a/tests/phpunit/testcases/apis/suggestions-nonauth.php
+++ b/tests/phpunit/testcases/apis/suggestions-nonauth.php
@@ -73,32 +73,32 @@ public function test_suggestions_with_type_members_and_only_friends() {
 		$this->assertTrue( is_wp_error( $suggestions ) );
 	}
 
-	public function test_suggestions_with_type_group_and_only_friends() {
+	public function test_suggestions_with_type_groupmembers_and_only_friends() {
 		// only_friends requires authenticated requests
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id'    => $this->group_ids['public'],
+			'group_id'     => $this->group_ids['public'],
 			'only_friends' => true,
-			'type'         => 'group-members',
+			'type'         => 'members',
 			'term'         => 'smith',
 		) );
 
 		$this->assertTrue( is_wp_error( $suggestions ) );
 	}
 
-	public function test_suggestions_with_type_group_hidden() {
+	public function test_suggestions_with_type_groupmembers_hidden() {
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['hidden'],
-			'type'      => 'group-members',
+			'group_id'  => $this->group_ids['hidden'],
+			'type'      => 'members',
 			'term'      => 'pig',
 		) );
 
 		$this->assertTrue( is_wp_error( $suggestions ) );
 	}
 
-	public function test_suggestions_with_type_group_private() {
+	public function test_suggestions_with_type_groupmembers_private() {
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['private'],
-			'type'      => 'group-members',
+			'group_id'  => $this->group_ids['private'],
+			'type'      => 'members',
 			'term'      => 'cat',
 		) );
 
diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 7fc85d7..b2d6274 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -145,9 +145,9 @@ public function test_suggestions_with_term_as_current_user() {
 
 	public function test_suggestions_with_type_groupmembers_public() {
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['public'],
-			'type'      => 'group-members',
-			'term'      => 'smith',
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
 		) );
 
 		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
@@ -155,10 +155,10 @@ public function test_suggestions_with_type_groupmembers_public() {
 
 	public function test_suggestions_with_type_groupmembers_public_and_limit() {
 		$suggestions = bp_core_get_suggestions( array(
-			'limit'     => 1,
-			'object_id' => $this->group_ids['public'],
-			'type'      => 'group-members',
-			'term'      => 'smith',
+			'limit'    => 1,
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
 		) );
 
 		$this->assertEquals( 1, count( $suggestions ) );  // one of: aardvark, smith.
@@ -166,9 +166,9 @@ public function test_suggestions_with_type_groupmembers_public_and_limit() {
 
 	public function test_suggestions_with_type_groupmembers_public_and_only_friends() {
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id'    => $this->group_ids['public'],
+			'group_id'     => $this->group_ids['public'],
 			'only_friends' => true,
-			'type'         => 'group-members',
+			'type'         => 'members',
 			'term'         => 'smith',
 		) );
 
@@ -177,9 +177,9 @@ public function test_suggestions_with_type_groupmembers_public_and_only_friends(
 
 	public function test_suggestions_with_type_groupmembers_public_and_term_as_displayname() {
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['public'],
-			'type'      => 'group-members',
-			'term'      => 'aardvark',
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'aardvark',
 		) );
 
 		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
@@ -187,9 +187,9 @@ public function test_suggestions_with_type_groupmembers_public_and_term_as_displ
 
 	public function test_suggestions_with_type_groupmembers_public_and_term_as_usernicename() {
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['public'],
-			'type'      => 'group-members',
-			'term'      => 'robert',
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'robert',
 		) );
 
 		$this->assertEquals( 1, count( $suggestions ) );  // smith.
@@ -197,9 +197,9 @@ public function test_suggestions_with_type_groupmembers_public_and_term_as_usern
 
 	public function test_suggestions_with_type_groupmembers_public_as_id() {
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['public'],
-			'type'      => 'group-members',
-			'term'      => 'smith',
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
 		) );
 
 		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
@@ -207,9 +207,9 @@ public function test_suggestions_with_type_groupmembers_public_as_id() {
 
 	public function test_suggestions_with_type_groupmembers_public_as_slug() {
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_slugs['public'],
-			'type'      => 'group-members',
-			'term'      => 'smith',
+			'group_id' => $this->group_slugs['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
 		) );
 
 		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
@@ -218,18 +218,18 @@ public function test_suggestions_with_type_groupmembers_public_as_slug() {
 	public function test_suggestions_with_type_groupmembers_hidden() {
 		// current_user isn't a member of the hidden group
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['hidden'],
-			'type'      => 'group-members',
-			'term'      => 'pig',
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
 		) );
 		$this->assertTrue( is_wp_error( $suggestions ) );
 
 		// "alpaca red" is in the hidden group
 		$this->set_current_user( $this->users['alpaca red'] );
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['hidden'],
-			'type'      => 'group-members',
-			'term'      => 'pig',
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
 		) );
 		$this->assertEquals( 1, count( $suggestions ) );  // pig
 	}
@@ -237,18 +237,18 @@ public function test_suggestions_with_type_groupmembers_hidden() {
 	public function test_suggestions_with_type_groupmembers_private() {
 		// current_user isn't a member of the private group
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['private'],
-			'type'      => 'group-members',
-			'term'      => 'cat',
+			'group_id' => $this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
 		) );
 		$this->assertTrue( is_wp_error( $suggestions ) );
 
 		// "caterpillar" is in the private group
 		$this->set_current_user( $this->users['caterpillar'] );
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => $this->group_ids['private'],
-			'type'      => 'group-members',
-			'term'      => 'cat',
+			'group_id' => $this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
 		) );
 		$this->assertEquals( 2, count( $suggestions ) );  // cat, caterpillar
 	}
@@ -343,38 +343,22 @@ public function test_suggestions_with_bad_type() {
 		$this->assertTrue( is_wp_error( $suggestions ) );
 	}
 
-	public function test_suggestions_with_type_groupmembers_and_bad_object_id() {
-		// object_id must be a positive integer.
-		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => -12,
-			'type'      => 'group-members',
-		) );
-		$this->assertTrue( is_wp_error( $suggestions ) );
-
-		// object_id can also be a group slug.
+	public function test_suggestions_with_type_groupmembers_and_bad_group_ids() {
+		// group_ids must be a positive integer.
 		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => 'fake-group-slug',
-			'type'      => 'group-members',
+			'group_id' => -12,
+			'type'     => 'members',
 		) );
 		$this->assertTrue( is_wp_error( $suggestions ) );
 
-		// object_id must be set when scope=group
+		// group_ids can also be a group slug.
 		$suggestions = bp_core_get_suggestions( array(
-			'type' => 'group-members',
+			'group_id' => 'fake-group-slug',
+			'type'     => 'members',
 		) );
 		$this->assertTrue( is_wp_error( $suggestions ) );
 	}
 
-	public function test_suggestions_with_type_members_and_object_id() {
-		// object_id cannot be set when scope=global
-		$suggestions = bp_core_get_suggestions( array(
-			'object_id' => 12,
-			'type'      => 'members',
-		) );
-
-		$this->assertTrue( is_wp_error( $suggestions ) );
-	}
-
 	public function test_suggestions_with_bad_term() {
 		// a non-empty term is mandatory
 		$suggestions = bp_core_get_suggestions( array(
-- 
1.9.3


From 75f9285389ffc6293af4520e624667c34803466a Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 20:42:19 +0100
Subject: [PATCH 11/35] Suggestions: change how we get the display name

The function now used has internal logic for bp_disable_profile_sync(),
as well as caching.
---
 src/bp-members/bp-members-functions.php | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index 24f5ddb..9b6eb64 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2148,11 +2148,7 @@ function bp_members_get_suggestions( $args ) {
 		$result        = new stdClass();
 		$result->ID    = $user->user_nicename;
 		$result->image = get_avatar( $user );
-		$result->name  = $user->display_name;
-
-		if ( bp_disable_profile_sync() ) {
-			$result->name = BP_XProfile_Field::get_fullname( $user->ID );
-		}
+		$result->name  = bp_core_get_user_displayname( $user->ID );
 
 		$results[] = $result;
 	}
-- 
1.9.3


From 6617e1c8bbb54b3f6dc296a80fbe39d0be173349 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 20:50:42 +0100
Subject: [PATCH 12/35] Add missing required parameter to some tests

type was optional in an early iteration :)
---
 tests/phpunit/testcases/apis/suggestions.php | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index b2d6274..81d228e 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -261,6 +261,7 @@ public function test_suggestions_with_type_groupmembers_private() {
 	public function test_suggestions_response_no_matches() {
 		$suggestions = bp_core_get_suggestions( array(
 			'term' => 'abcdefghijklmnopqrstuvwxyz',
+			'type' => 'members',
 		) );
 
 		$this->assertInternalType( 'array', $suggestions );
@@ -270,6 +271,7 @@ public function test_suggestions_response_no_matches() {
 	public function test_suggestions_response_single_match() {
 		$suggestion = bp_core_get_suggestions( array(
 			'term' => 'zoom',
+			'type' => 'members',
 		) );
 
 		$this->assertInternalType( 'array', $suggestion );
@@ -286,6 +288,7 @@ public function test_suggestions_response_single_match() {
 	public function test_suggestions_response_multiple_matches() {
 		$suggestions = bp_core_get_suggestions( array(
 			'term' => 'cat',
+			'type' => 'members',
 		) );
 
 		$this->assertInternalType( 'array', $suggestions );
@@ -302,11 +305,13 @@ public function test_suggestions_response_multiple_matches() {
 	public function test_suggestions_term_is_case_insensitive() {
 		$lowercase = bp_core_get_suggestions( array(
 			'term' => 'lisa',
+			'type' => 'members',
 		) );
 		$this->assertEquals( 1, count( $lowercase ) );
 
 		$uppercase = bp_core_get_suggestions( array(
 			'term' => 'LISA',
+			'type' => 'members',
 		) );
 		$this->assertEquals( 1, count( $uppercase ) );
 
@@ -317,6 +322,7 @@ public function test_suggestions_term_is_case_insensitive() {
 	public function test_suggestions_response_property_types() {
 		$suggestion = bp_core_get_suggestions( array(
 			'term' => 'zoom',
+			'type' => 'members',
 		) );
 
 		$this->assertInternalType( 'array', $suggestion );
@@ -363,6 +369,7 @@ public function test_suggestions_with_bad_term() {
 		// a non-empty term is mandatory
 		$suggestions = bp_core_get_suggestions( array(
 			'term' => '',
+			'type' => 'members',
 		) );
 
 		$this->assertTrue( is_wp_error( $suggestions ) );
-- 
1.9.3


From dce81b7dab00a12513ecc1527c4920e39edf394c Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 21:39:12 +0100
Subject: [PATCH 13/35] Suggestions tests: use $this->create_user() to create
 users
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

factory->user->create doesn’t add xprofile data which we need for these
tests.
---
 tests/phpunit/testcases/apis/suggestions-nonauth.php | 2 +-
 tests/phpunit/testcases/apis/suggestions.php         | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/tests/phpunit/testcases/apis/suggestions-nonauth.php b/tests/phpunit/testcases/apis/suggestions-nonauth.php
index 0c67d17..e21a4af 100644
--- a/tests/phpunit/testcases/apis/suggestions-nonauth.php
+++ b/tests/phpunit/testcases/apis/suggestions-nonauth.php
@@ -29,7 +29,7 @@ public function setUp() {
 
 		// Create some dummy users.
 		foreach( $users as $user ) {
-			$this->user_ids[ $user[0] ] = $this->factory->user->create( array(
+			$this->user_ids[ $user[0] ] = $this->create_user( array(
 				'display_name' => $user[1],
 				'user_login'   => $user[0],
 			) );
diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 81d228e..0a1ac10 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -16,7 +16,7 @@ public function setUp() {
 		parent::setUp();
 
 		$this->old_user_id  = get_current_user_id();
-		$this->current_user = $this->factory->user->create( array(
+		$this->current_user = $this->create_user( array(
 			'display_name' => 'Katie Parker',
 			'user_login'   => 'admin',
 		) );
@@ -38,8 +38,8 @@ public function setUp() {
 		);
 
 		// Create some dummy users.
-		foreach( $users as $user ) {
-			$this->user_ids[ $user[0] ] = $this->factory->user->create( array(
+		foreach ( $users as $user ) {
+			$this->user_ids[ $user[0] ] = $this->create_user( array(
 				'display_name' => $user[1],
 				'user_login'   => $user[0],
 			) );
-- 
1.9.3


From 5eac207f9230a48e89a939a33a4a68a01934f19a Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 21:43:54 +0100
Subject: [PATCH 14/35] Whitespace tweaks

---
 tests/phpunit/testcases/apis/suggestions-nonauth.php | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/tests/phpunit/testcases/apis/suggestions-nonauth.php b/tests/phpunit/testcases/apis/suggestions-nonauth.php
index e21a4af..b7d7aaa 100644
--- a/tests/phpunit/testcases/apis/suggestions-nonauth.php
+++ b/tests/phpunit/testcases/apis/suggestions-nonauth.php
@@ -87,9 +87,9 @@ public function test_suggestions_with_type_groupmembers_and_only_friends() {
 
 	public function test_suggestions_with_type_groupmembers_hidden() {
 		$suggestions = bp_core_get_suggestions( array(
-			'group_id'  => $this->group_ids['hidden'],
-			'type'      => 'members',
-			'term'      => 'pig',
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
 		) );
 
 		$this->assertTrue( is_wp_error( $suggestions ) );
@@ -97,9 +97,9 @@ public function test_suggestions_with_type_groupmembers_hidden() {
 
 	public function test_suggestions_with_type_groupmembers_private() {
 		$suggestions = bp_core_get_suggestions( array(
-			'group_id'  => $this->group_ids['private'],
-			'type'      => 'members',
-			'term'      => 'cat',
+			'group_id' => $this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
 		) );
 
 		$this->assertTrue( is_wp_error( $suggestions ) );
-- 
1.9.3


From affc24b90d58dae0c7d327b7ca5350fb77f64d9a Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 21:44:13 +0100
Subject: [PATCH 15/35] Suggestions: ID property is uppercase

---
 tests/phpunit/testcases/apis/suggestions.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 0a1ac10..092834a 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -139,7 +139,7 @@ public function test_suggestions_with_term_as_current_user() {
 			'term' => 'katie',
 		) );
 		$this->assertEquals( 1, count( $suggestions ) );
-		$this->assertSame( 'admin', $suggestions[0]->id );
+		$this->assertSame( 'admin', $suggestions[0]->ID );
 	}
 
 
@@ -315,8 +315,8 @@ public function test_suggestions_term_is_case_insensitive() {
 		) );
 		$this->assertEquals( 1, count( $uppercase ) );
 
-		$this->assertSame( $lowercase[0]->id, $uppercase[0]->id );
-		$this->assertSame( 'zoom', $lowercase[0]->id );
+		$this->assertSame( $lowercase[0]->ID, $uppercase[0]->ID );
+		$this->assertSame( 'zoom', $lowercase[0]->ID );
 	}
 
 	public function test_suggestions_response_property_types() {
-- 
1.9.3


From 82ef062a9cb731c7726261946f2a309a18b1cfba Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 21:48:11 +0100
Subject: [PATCH 16/35] Suggestions: uppercase more IDs

---
 tests/phpunit/testcases/apis/suggestions.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 092834a..e2f0540 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -281,7 +281,7 @@ public function test_suggestions_response_single_match() {
 
 		$this->assertInternalType( 'object', $suggestion );
 		$this->assertAttributeNotEmpty( 'image', $suggestion );
-		$this->assertAttributeNotEmpty( 'id', $suggestion );
+		$this->assertAttributeNotEmpty( 'ID', $suggestion );
 		$this->assertAttributeNotEmpty( 'name', $suggestion );
 	}
 
@@ -297,7 +297,7 @@ public function test_suggestions_response_multiple_matches() {
 		foreach ( $suggestions as $suggestion ) {
 			$this->assertInternalType( 'object', $suggestion );
 			$this->assertAttributeNotEmpty( 'image', $suggestion );
-			$this->assertAttributeNotEmpty( 'id', $suggestion );
+			$this->assertAttributeNotEmpty( 'ID', $suggestion );
 			$this->assertAttributeNotEmpty( 'name', $suggestion );
 		}
 	}
@@ -332,7 +332,7 @@ public function test_suggestions_response_property_types() {
 
 		$this->assertInternalType( 'object', $suggestion );
 		$this->assertAttributeInternalType( 'string', 'image', $suggestion );
-		$this->assertAttributeInternalType( 'string', 'id', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'ID', $suggestion );
 		$this->assertAttributeInternalType( 'string', 'name', $suggestion );
 	}
 
-- 
1.9.3


From 192cde4679ff029be90cc96acb839a8610d713c4 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 22:23:45 +0100
Subject: [PATCH 17/35] Suggestions: image needs to be a direct URL

get_avatar() returns a full `<img>` ta.g
---
 src/bp-members/bp-members-functions.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index 9b6eb64..8b9bc9a 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2147,7 +2147,7 @@ function bp_members_get_suggestions( $args ) {
 	foreach ( $user_query->results as $user ) {
 		$result        = new stdClass();
 		$result->ID    = $user->user_nicename;
-		$result->image = get_avatar( $user );
+		$result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
 		$result->name  = bp_core_get_user_displayname( $user->ID );
 
 		$results[] = $result;
-- 
1.9.3


From e4ac3acd744420cfe58cf05d5a895f9c3c64b7ef Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 22:41:40 +0100
Subject: [PATCH 18/35] phpDoc tweak

---
 src/bp-members/bp-members-functions.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index 8b9bc9a..45c9377 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2093,7 +2093,7 @@ function bp_live_spammer_login_error() {
  * by this particular Suggestions API extension.
  *
  * @param array $args {
- *     @type int $group_id If set, only match users that are in this group.
+ *     @type int $group_id If set, only match against users that are in this group.
  *     @type int $limit Maximum number of results to display. Default: 16.
  *     @type bool $only_friends If true, only match the current user's friends. Default: false.
  *     @type string $term The suggestion service will try to find results that contain this string.
-- 
1.9.3


From 8453b916780a3817fd002ef0a6a6e34105e82c78 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 22:42:05 +0100
Subject: [PATCH 19/35] Remove Suggestions test that doesn't match API

Querying groups by slug was removed.
---
 tests/phpunit/testcases/apis/suggestions.php | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index e2f0540..4bc6a68 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -205,16 +205,6 @@ public function test_suggestions_with_type_groupmembers_public_as_id() {
 		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
 	}
 
-	public function test_suggestions_with_type_groupmembers_public_as_slug() {
-		$suggestions = bp_core_get_suggestions( array(
-			'group_id' => $this->group_slugs['public'],
-			'type'     => 'members',
-			'term'     => 'smith',
-		) );
-
-		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
-	}
-
 	public function test_suggestions_with_type_groupmembers_hidden() {
 		// current_user isn't a member of the hidden group
 		$suggestions = bp_core_get_suggestions( array(
-- 
1.9.3


From 4437de02b04ce2b133abb13feea084f05c4d6233 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 23:03:38 +0100
Subject: [PATCH 20/35] Create test groups from non-current user
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Otherwise current user is the group admin and has access to the groups
which weren’t accounted for in the test.
---
 tests/phpunit/testcases/apis/suggestions-nonauth.php | 19 ++++++++++++++++---
 tests/phpunit/testcases/apis/suggestions.php         | 19 ++++++++++++++++---
 2 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/tests/phpunit/testcases/apis/suggestions-nonauth.php b/tests/phpunit/testcases/apis/suggestions-nonauth.php
index b7d7aaa..85f9d10 100644
--- a/tests/phpunit/testcases/apis/suggestions-nonauth.php
+++ b/tests/phpunit/testcases/apis/suggestions-nonauth.php
@@ -24,6 +24,7 @@ public function setUp() {
 			array( 'rabbit blue', 'Amber Hooper' ),
 			array( 'smith',       'Robert Bar' ),
 			array( 'snake',       'Eleanor Moore' ),
+			array( 'xylo',        'Silver McFadden' ),
 			array( 'zoom',        'Lisa Smithy' ),
 		);
 
@@ -40,9 +41,21 @@ public function setUp() {
 		$this->group_slugs['private'] = 'tsavo-highway';
 
 		// Create dummy groups.
-		$this->group_ids['hidden']  = $this->factory->group->create( array( 'slug' => $this->group_slugs['hidden'],  'status' => 'hidden' ) );
-		$this->group_ids['public']  = $this->factory->group->create( array( 'slug' => $this->group_slugs['public'],  'status' => 'public' ) );
-		$this->group_ids['private'] = $this->factory->group->create( array( 'slug' => $this->group_slugs['private'], 'status' => 'private' ) );
+		$this->group_ids['hidden'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['hidden'],
+			'status'     => 'hidden',
+		) );
+		$this->group_ids['public'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['public'],
+			'status'     => 'public',
+		) );
+		$this->group_ids['private'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['private'],
+			'status'     => 'private',
+		) );
 
 		// Add dummy users to dummy hidden groups.
 		groups_join_group( $this->group_ids['hidden'], $this->user_ids['pig'] );
diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 4bc6a68..494854a 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -34,6 +34,7 @@ public function setUp() {
 			array( 'rabbit blue', 'Amber Hooper' ),
 			array( 'smith',       'Robert Bar' ),
 			array( 'snake',       'Eleanor Moore' ),
+			array( 'xylo',        'Silver McFadden' ),
 			array( 'zoom',        'Lisa Smithy' ),
 		);
 
@@ -56,9 +57,21 @@ public function setUp() {
 		$this->group_slugs['private'] = 'tsavo-highway';
 
 		// Create dummy groups.
-		$this->group_ids['hidden']  = $this->factory->group->create( array( 'slug' => $this->group_slugs['hidden'],  'status' => 'hidden' ) );
-		$this->group_ids['public']  = $this->factory->group->create( array( 'slug' => $this->group_slugs['public'],  'status' => 'public' ) );
-		$this->group_ids['private'] = $this->factory->group->create( array( 'slug' => $this->group_slugs['private'], 'status' => 'private' ) );
+		$this->group_ids['hidden'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['hidden'],
+			'status'     => 'hidden',
+		) );
+		$this->group_ids['public'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['public'],
+			'status'     => 'public',
+		) );
+		$this->group_ids['private'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['private'],
+			'status'     => 'private',
+		) );
 
 		// Add dummy users to dummy hidden groups.
 		groups_join_group( $this->group_ids['hidden'], $this->user_ids['pig'] );
-- 
1.9.3


From a0508e2ff186a1eaa542f17d6a174803eae5a04f Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sat, 21 Jun 2014 23:06:57 +0100
Subject: [PATCH 21/35] Suggestions, tests: fix incorrectly named variables

---
 tests/phpunit/testcases/apis/suggestions.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 494854a..381c98e 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -228,7 +228,7 @@ public function test_suggestions_with_type_groupmembers_hidden() {
 		$this->assertTrue( is_wp_error( $suggestions ) );
 
 		// "alpaca red" is in the hidden group
-		$this->set_current_user( $this->users['alpaca red'] );
+		$this->set_current_user( $this->user_ids['alpaca red'] );
 		$suggestions = bp_core_get_suggestions( array(
 			'group_id' => $this->group_ids['hidden'],
 			'type'     => 'members',
@@ -247,7 +247,7 @@ public function test_suggestions_with_type_groupmembers_private() {
 		$this->assertTrue( is_wp_error( $suggestions ) );
 
 		// "caterpillar" is in the private group
-		$this->set_current_user( $this->users['caterpillar'] );
+		$this->set_current_user( $this->user_ids['caterpillar'] );
 		$suggestions = bp_core_get_suggestions( array(
 			'group_id' => $this->group_ids['private'],
 			'type'     => 'members',
-- 
1.9.3


From d039b95e6b429057e1e6e0f906e089b8e794f3b0 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 00:03:52 +0100
Subject: [PATCH 22/35] phpDoc tweaks

---
 src/bp-core/bp-core-functions.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/bp-core/bp-core-functions.php b/src/bp-core/bp-core-functions.php
index 837113a..e565cd0 100644
--- a/src/bp-core/bp-core-functions.php
+++ b/src/bp-core/bp-core-functions.php
@@ -1930,7 +1930,7 @@ function bp_nav_menu_get_item_url( $slug ) {
  * This is used to power BuddyPress' at-mentions suggestions, but it is flexible enough to be used
  * for similar kinds of requirements.
  *
- * If a Component or a third-party plugin wants to provide suggestions for a custom type of object,
+ * If a component or a third-party plugin wants to provide suggestions for a custom type of object,
  * use the bp_suggestions_get_types filter and return an associative array with its key as the new
  * "type", and its value as a function callback.
  *
@@ -1952,9 +1952,9 @@ function bp_nav_menu_get_item_url( $slug ) {
  */
 function bp_core_get_suggestions( $args ) {
 	$defaults = array(
-		'limit'     => 16,
-		'type'      => '',
-		'term'      => '',
+		'limit' => 16,
+		'type'  => '',
+		'term'  => '',
 	);
 	$args = wp_parse_args( $args, $defaults );
 
-- 
1.9.3


From a8b48961249f8d0eb760ccd29d9d7d84b77317c2 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 13:46:40 +0100
Subject: [PATCH 23/35] Re-implement autosuggest for Messages

---
 .../bp-legacy/buddypress-functions.php             | 78 ++++++----------------
 1 file changed, 21 insertions(+), 57 deletions(-)

diff --git a/src/bp-templates/bp-legacy/buddypress-functions.php b/src/bp-templates/bp-legacy/buddypress-functions.php
index 784b2dc..455b60e 100644
--- a/src/bp-templates/bp-legacy/buddypress-functions.php
+++ b/src/bp-templates/bp-legacy/buddypress-functions.php
@@ -1383,70 +1383,34 @@ function bp_legacy_theme_ajax_messages_delete() {
  * @return string HTML.
  */
 function bp_legacy_theme_ajax_messages_autocomplete_results() {
-
+	$limit = isset( $_GET['limit'] ) ? absint( $_GET['limit'] )          : (int) apply_filters( 'bp_autocomplete_max_results', 10 );
+	$term  = isset( $_GET['q'] )     ? sanitize_text_field( $_GET['q'] ) : '';
+	
 	// Include everyone in the autocomplete, or just friends?
-	if ( bp_is_current_component( bp_get_messages_slug() ) )
-		$autocomplete_all = buddypress()->messages->autocomplete_all;
-
-	$pag_page     = 1;
-	$limit        = (int) $_GET['limit'] ? $_GET['limit'] : apply_filters( 'bp_autocomplete_max_results', 10 );
-	$search_terms = isset( $_GET['q'] ) ? $_GET['q'] : '';
-
-	$user_query_args = array(
-		'search_terms' => $search_terms,
-		'page'         => intval( $pag_page ),
-		'per_page'     => intval( $limit ),
-	);
-
-	// If only matching against friends, get an $include param for
-	// BP_User_Query
-	if ( ! $autocomplete_all && bp_is_active( 'friends' ) ) {
-		$include = BP_Friends_Friendship::get_friend_user_ids( bp_loggedin_user_id() );
-
-		// Ensure zero matches if no friends are found
-		if ( empty( $include ) ) {
-			$include = array( 0 );
-		}
-
-		$user_query_args['include'] = $include;
+	if ( bp_is_current_component( bp_get_messages_slug() ) ) {
+		$only_friends = ( buddypress()->messages->autocomplete_all === false );
+	} else {
+		$only_friends = true;
 	}
 
-	$user_query = new BP_User_Query( $user_query_args );
-
-	// Backward compatibility - if a plugin is expecting a legacy
-	// filter, pass the IDs through the filter and requery (groan)
-	if ( has_filter( 'bp_core_autocomplete_ids' ) || has_filter( 'bp_friends_autocomplete_ids' ) ) {
-		$found_user_ids = wp_list_pluck( $user_query->results, 'ID' );
-
-		if ( $autocomplete_all ) {
-			$found_user_ids = apply_filters( 'bp_core_autocomplete_ids', $found_user_ids );
-		} else {
-			$found_user_ids = apply_filters( 'bp_friends_autocomplete_ids', $found_user_ids );
-		}
-
-		if ( empty( $found_user_ids ) ) {
-			$found_user_ids = array( 0 );
-		}
-
-		// Repopulate the $user_query variable
-		$user_query = new BP_User_Query( array(
-			'include' => $found_user_ids,
-		) );
-	}
+	$suggestions = bp_core_get_suggestions( array(
+		'limit'        => $limit,
+		'only_friends' => $only_friends,
+		'term'         => $term,
+		'type'         => 'members',
+	) );
 
-	if ( ! empty( $user_query->results ) ) {
-		foreach ( $user_query->results as $user ) {
-			if ( bp_is_username_compatibility_mode() ) {
-				// Sanitize for spaces. Use urlencode() rather
-				// than rawurlencode() because %20 breaks JS
-				$username = urlencode( $user->user_login );
-			} else {
-				$username = $user->user_nicename;
-			}
+	if ( $suggestions && ! is_wp_error( $suggestions ) ) {
+		foreach ( $suggestions as $user ) {
 
 			// Note that the final line break acts as a delimiter for the
 			// autocomplete javascript and thus should not be removed
-			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";
+			printf( '<span id="%s" href="#"></span><img src="%s" style="width: 15px"> &nbsp; %s (%s)' . "\n",
+				esc_attr( 'link-' . $user->ID ),
+				esc_url( $user->image ),
+				esc_html( $user->name ),
+				esc_html( $user->ID )
+			);
 		}
 	}
 
-- 
1.9.3


From bc1e065ec696b65e84d447acdf3ff5a0cb30ddf8 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 17:32:00 +0100
Subject: [PATCH 24/35] Fix typo in test description

---
 tests/phpunit/testcases/apis/suggestions.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 381c98e..2facd6c 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -360,7 +360,7 @@ public function test_suggestions_with_type_groupmembers_and_bad_group_ids() {
 		) );
 		$this->assertTrue( is_wp_error( $suggestions ) );
 
-		// group_ids can also be a group slug.
+		// group_ids can't be a group slug.
 		$suggestions = bp_core_get_suggestions( array(
 			'group_id' => 'fake-group-slug',
 			'type'     => 'members',
-- 
1.9.3


From 389540f1d8a2c56a22905fcd425e98afbad46a1d Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 17:44:08 +0100
Subject: [PATCH 25/35] Re-implement autosuggest for group wp-admin.

First pass. Suggestions API still needs updates to make this work as
expected.
---
 src/bp-groups/bp-groups-admin.php            | 49 +++++++++++++---------------
 tests/phpunit/testcases/apis/suggestions.php |  7 ----
 2 files changed, 23 insertions(+), 33 deletions(-)

diff --git a/src/bp-groups/bp-groups-admin.php b/src/bp-groups/bp-groups-admin.php
index 46e6a94..899b41f 100644
--- a/src/bp-groups/bp-groups-admin.php
+++ b/src/bp-groups/bp-groups-admin.php
@@ -984,41 +984,38 @@ function bp_groups_admin_get_usernames_from_ids( $user_ids = array() ) {
 function bp_groups_admin_autocomplete_handler() {
 
 	// Bail if user user shouldn't be here, or is a large network
-	if ( ! current_user_can( 'bp_moderate' ) || ( is_multisite() && wp_is_large_network( 'users' ) ) )
+	if ( ! current_user_can( 'bp_moderate' ) || ( is_multisite() && wp_is_large_network( 'users' ) ) ) {
 		wp_die( -1 );
+	}
+
+	$term     = isset( $_GET['term'] )     ? sanitize_text_field( $_GET['term'] ) : '';
+	$group_id = isset( $_GET['group_id'] ) ? absint( $_GET['group_id'] )          : 0;
 
-	$return = array();
+	if ( ! $term || ! $group_id ) {
+		wp_die( -1 );
+	}
 
-	// Exclude current group members
-	$group_id = isset( $_GET['group_id'] ) ? wp_parse_id_list( $_GET['group_id'] ) : array();
-	$group_member_query = new BP_Group_Member_Query( array(
-		'group_id'        => $group_id,
-		'per_page'        => 0, // show all
-		'group_role'      => array( 'member', 'mod', 'admin', ),
-		'populate_extras' => false,
-		'count_total'     => false,
+	$suggestions = bp_core_get_suggestions( array(
+		'group_id' => -$group_id,  // A negative value will exclude this group's members from the suggestions.
+		'limit'    => 10,
+		'term'     => $term,
+		'type'     => 'members',
 	) );
 
-	$group_members = ! empty( $group_member_query->results ) ? wp_list_pluck( $group_member_query->results, 'ID' ) : array();
+	$matches = array();
 
-	$terms = isset( $_GET['term'] ) ? $_GET['term'] : '';
-	$users = bp_core_get_users( array(
-		'type'            => 'alphabetical',
-		'search_terms'    => $terms,
-		'exclude'         => $group_members,
-		'per_page'        => 10,
-		'populate_extras' => false
-	) );
+	if ( $suggestions && ! is_wp_error( $suggestions ) ) {
+		foreach ( $suggestions as $user ) {
 
-	foreach ( (array) $users['users'] as $user ) {
-		$return[] = array(
-			/* translators: 1: user_login, 2: user_email */
-			'label' => sprintf( __( '%1$s (%2$s)', 'buddypress' ), bp_is_username_compatibility_mode() ? $user->user_login : $user->user_nicename, $user->user_email ),
-			'value' => $user->user_nicename,
-		);
+			$matches[] = array(
+				// translators: 1: user_login, 2: user_email
+				'label' => sprintf( __( '%1$s (%2$s)', 'buddypress' ), $user->name, $user->ID ),
+				'value' => $user->ID,
+			);
+		}
 	}
 
-	wp_die( json_encode( $return ) );
+	wp_die( json_encode( $matches ) );
 }
 add_action( 'wp_ajax_bp_group_admin_member_autocomplete', 'bp_groups_admin_autocomplete_handler' );
 
diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 2facd6c..c4b845c 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -353,13 +353,6 @@ public function test_suggestions_with_bad_type() {
 	}
 
 	public function test_suggestions_with_type_groupmembers_and_bad_group_ids() {
-		// group_ids must be a positive integer.
-		$suggestions = bp_core_get_suggestions( array(
-			'group_id' => -12,
-			'type'     => 'members',
-		) );
-		$this->assertTrue( is_wp_error( $suggestions ) );
-
 		// group_ids can't be a group slug.
 		$suggestions = bp_core_get_suggestions( array(
 			'group_id' => 'fake-group-slug',
-- 
1.9.3


From 8be8f55862e11f7da3d13451d390d65c622f01de Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 18:45:19 +0100
Subject: [PATCH 26/35] API tweaks to fix group wp-admin suggestions.

---
 src/bp-groups/bp-groups-functions.php   | 17 +++++++++++++----
 src/bp-members/bp-members-functions.php |  2 +-
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/src/bp-groups/bp-groups-functions.php b/src/bp-groups/bp-groups-functions.php
index a1b27df..15f78bf 100644
--- a/src/bp-groups/bp-groups-functions.php
+++ b/src/bp-groups/bp-groups-functions.php
@@ -1166,12 +1166,16 @@ function groups_remove_data_for_user( $user_id ) {
  * @since BuddyPress (2.1.0)
  */
 function bp_groups_suggestions_filter_query_args( $query_args, $suggestion_args ) {
-	if ( ! $suggestion_args['group_id'] ) {
+	if ( ! (int) $suggestion_args['group_id'] ) {
 		return $query_args;
 	}
 
+	// Positive integers will restrict the search to members in that group.
+	// Negative integers will restrict the search to members in every other group.
+	$suggestion_args['group_id'] = (int) $suggestion_args['group_id'];
+
 	$the_group = groups_get_group( array(
-		'group_id'        => $suggestion_args['group_id'],
+		'group_id'        => absint( $suggestion_args['group_id'] ),
 		'populate_extras' => true,
 	) );
 
@@ -1184,12 +1188,17 @@ function bp_groups_suggestions_filter_query_args( $query_args, $suggestion_args
 		'populate_extras' => false,
 		'type'            => 'alphabetical',
 
-		'group_id'        => $suggestion_args['group_id'],
+		'group_id'        => absint( $suggestion_args['group_id'] ),
 	);
 
 	$group_users = new BP_Group_Member_Query( $group_query );
 	if ( $group_users->results ) {
-		$query_args['include'] = wp_list_pluck( $group_users->results, 'ID' );
+		if ( $suggestion_args['group_id'] > 0 ) {
+			$query_args['include'] = wp_list_pluck( $group_users->results, 'ID' );
+		} else {
+			$query_args['exclude'] = wp_list_pluck( $group_users->results, 'ID' );
+		}
+
 	} else {
 		$query_args['include'] = array( 0 );
 	}
diff --git a/src/bp-members/bp-members-functions.php b/src/bp-members/bp-members-functions.php
index 45c9377..cccae3c 100644
--- a/src/bp-members/bp-members-functions.php
+++ b/src/bp-members/bp-members-functions.php
@@ -2111,7 +2111,7 @@ function bp_members_get_suggestions( $args ) {
 		'term'         => '',     // Sanitised upstream
 	);
 	$args                 = apply_filters( 'bp_members_get_suggestions_args', wp_parse_args( $args, $defaults ) );
-	$args['group_id']     = absint( $args['group_id'] );
+	$args['group_id']     = (int) $args['group_id'];
 	$args['only_friends'] = (bool) $args['only_friends'];
 
 
-- 
1.9.3


From 96541bd37c69b7213c001ab5d7fc55f14977075b Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 19:03:57 +0100
Subject: [PATCH 27/35] Minor whitespace tweaks

---
 tests/phpunit/testcases/apis/suggestions.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index c4b845c..651cfba 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -238,7 +238,7 @@ public function test_suggestions_with_type_groupmembers_hidden() {
 	}
 
 	public function test_suggestions_with_type_groupmembers_private() {
-		// current_user isn't a member of the private group
+		// current_user isn't a member of the private group.
 		$suggestions = bp_core_get_suggestions( array(
 			'group_id' => $this->group_ids['private'],
 			'type'     => 'members',
@@ -358,6 +358,7 @@ public function test_suggestions_with_type_groupmembers_and_bad_group_ids() {
 			'group_id' => 'fake-group-slug',
 			'type'     => 'members',
 		) );
+
 		$this->assertTrue( is_wp_error( $suggestions ) );
 	}
 
-- 
1.9.3


From 9434ddc992cb554a98d3220e1812ff72d5047a36 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 19:17:49 +0100
Subject: [PATCH 28/35] Tests for group wp-admin auto-suggest

---
 tests/phpunit/testcases/apis/suggestions.php | 78 ++++++++++++++++++++++++++++
 1 file changed, 78 insertions(+)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 651cfba..7bfc961 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -80,6 +80,8 @@ public function setUp() {
 		// Add dummy users to dummy public groups.
 		groups_join_group( $this->group_ids['public'], $this->current_user );
 		groups_join_group( $this->group_ids['public'], $this->user_ids['aardvark'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['alpaca red'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['cat'] );
 		groups_join_group( $this->group_ids['public'], $this->user_ids['smith'] );
 
 		// Add dummy users to dummy private groups.
@@ -257,6 +259,82 @@ public function test_suggestions_with_type_groupmembers_private() {
 	}
 
 
+	public function test_suggestions_with_type_groupmembers_public_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoom
+	}
+
+	public function test_suggestions_with_type_groupmembers_private_and_exclude_group_from_results() {
+		// current_user isn't a member of the private group.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+
+		$this->set_current_user( $this->user_ids['caterpillar'] );
+
+		// "cat" is in the private group, so won't show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertEmpty( $suggestions );
+
+		// "zoo" is not the private group, so will show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'zoo',
+		) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoo
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden_and_exclude_group_from_results() {
+		// current_user isn't a member of the hidden group.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+
+		$this->set_current_user( $this->user_ids['alpaca red'] );
+
+		// "alpaca red" is in the hidden group, so won't show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'alpaca red',
+		) );
+		$this->assertEmpty( $suggestions );
+
+		// "zoo" is not the hidden group, so will show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'zoo',
+		) );
+		die(var_dump('too many', $suggestions));
+		$this->assertEquals( 1, count( $suggestions ) );  // zoo
+	}
+
+
 	/**
 	 * These next tests check the format of the response from the Suggestions API.
 	 */
-- 
1.9.3


From 726128795a67253052ce28836c79ce4586ce0c63 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 19:22:30 +0100
Subject: [PATCH 29/35] Remove debug

---
 tests/phpunit/testcases/apis/suggestions.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 7bfc961..974b79a 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -330,7 +330,7 @@ public function test_suggestions_with_type_groupmembers_hidden_and_exclude_group
 			'type'     => 'members',
 			'term'     => 'zoo',
 		) );
-		die(var_dump('too many', $suggestions));
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // zoo
 	}
 
-- 
1.9.3


From 084653598ae4b600efffc65ed815e189513ed25e Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 19:23:09 +0100
Subject: [PATCH 30/35] Suggestions tests: sync group members to non-auth tests

---
 tests/phpunit/testcases/apis/suggestions-nonauth.php | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tests/phpunit/testcases/apis/suggestions-nonauth.php b/tests/phpunit/testcases/apis/suggestions-nonauth.php
index 85f9d10..4f7366b 100644
--- a/tests/phpunit/testcases/apis/suggestions-nonauth.php
+++ b/tests/phpunit/testcases/apis/suggestions-nonauth.php
@@ -62,7 +62,10 @@ public function setUp() {
 		groups_join_group( $this->group_ids['hidden'], $this->user_ids['alpaca red'] );
 
 		// Add dummy users to dummy public groups.
+		groups_join_group( $this->group_ids['public'], $this->current_user );
 		groups_join_group( $this->group_ids['public'], $this->user_ids['aardvark'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['alpaca red'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['cat'] );
 		groups_join_group( $this->group_ids['public'], $this->user_ids['smith'] );
 
 		// Add dummy users to dummy private groups.
-- 
1.9.3


From e01ab4f108f46c41569db673476dc600c2d923a2 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 19:24:52 +0100
Subject: [PATCH 31/35] Suggestions tests: count() can lie
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

count() on a WP_Error object can return 1. This is making some tests
pass accidentally. Check `is_wp_error` for each test just to be very
sure we don’t let anything similar through in future.
---
 tests/phpunit/testcases/apis/suggestions.php | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index 974b79a..dbc82fd 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -101,6 +101,7 @@ public function test_suggestions_with_type_members() {
 			'term' => 'smith',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 3, count( $suggestions ) );  // aardvark, smith, zoom.
 	}
 
@@ -111,6 +112,7 @@ public function test_suggestions_with_type_members_and_limit() {
 			'term'  => 'smith',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 2, count( $suggestions ) );  // two of: aardvark, smith, zoom.
 	}
 
@@ -120,6 +122,7 @@ public function test_suggestions_with_type_members_and_only_friends() {
 			'type'         => 'members',
 			'term'         => 'smith',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
 
 		$suggestions = bp_core_get_suggestions( array(
@@ -127,6 +130,7 @@ public function test_suggestions_with_type_members_and_only_friends() {
 			'type'         => 'members',
 			'term'         => 'cat',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 2, count( $suggestions ) );  // cat, caterpillar.
 	}
 
@@ -136,6 +140,7 @@ public function test_suggestions_with_type_members_and_term_as_displayname() {
 			'term' => 'aardvark',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
 	}
 
@@ -145,6 +150,7 @@ public function test_suggestions_with_type_members_and_term_as_usernicename() {
 			'term' => 'eleanor',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // snake.
 	}
 
@@ -153,6 +159,8 @@ public function test_suggestions_with_term_as_current_user() {
 			'type' => 'members',
 			'term' => 'katie',
 		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );
 		$this->assertSame( 'admin', $suggestions[0]->ID );
 	}
@@ -165,6 +173,7 @@ public function test_suggestions_with_type_groupmembers_public() {
 			'term'     => 'smith',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
 	}
 
@@ -176,6 +185,7 @@ public function test_suggestions_with_type_groupmembers_public_and_limit() {
 			'term'     => 'smith',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // one of: aardvark, smith.
 	}
 
@@ -187,6 +197,7 @@ public function test_suggestions_with_type_groupmembers_public_and_only_friends(
 			'term'         => 'smith',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
 	}
 
@@ -197,6 +208,7 @@ public function test_suggestions_with_type_groupmembers_public_and_term_as_displ
 			'term'     => 'aardvark',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
 	}
 
@@ -207,6 +219,7 @@ public function test_suggestions_with_type_groupmembers_public_and_term_as_usern
 			'term'     => 'robert',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // smith.
 	}
 
@@ -217,6 +230,7 @@ public function test_suggestions_with_type_groupmembers_public_as_id() {
 			'term'     => 'smith',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
 	}
 
@@ -236,6 +250,7 @@ public function test_suggestions_with_type_groupmembers_hidden() {
 			'type'     => 'members',
 			'term'     => 'pig',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // pig
 	}
 
@@ -255,6 +270,7 @@ public function test_suggestions_with_type_groupmembers_private() {
 			'type'     => 'members',
 			'term'     => 'cat',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 2, count( $suggestions ) );  // cat, caterpillar
 	}
 
@@ -265,6 +281,7 @@ public function test_suggestions_with_type_groupmembers_public_and_exclude_group
 			'type'     => 'members',
 			'term'     => 'smith',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
 
 		$suggestions = bp_core_get_suggestions( array(
@@ -272,6 +289,7 @@ public function test_suggestions_with_type_groupmembers_public_and_exclude_group
 			'type'     => 'members',
 			'term'     => 'smith',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // zoom
 	}
 
@@ -293,6 +311,7 @@ public function test_suggestions_with_type_groupmembers_private_and_exclude_grou
 			'type'     => 'members',
 			'term'     => 'cat',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEmpty( $suggestions );
 
 		// "zoo" is not the private group, so will show up here.
@@ -301,6 +320,7 @@ public function test_suggestions_with_type_groupmembers_private_and_exclude_grou
 			'type'     => 'members',
 			'term'     => 'zoo',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $suggestions ) );  // zoo
 	}
 
@@ -322,6 +342,7 @@ public function test_suggestions_with_type_groupmembers_hidden_and_exclude_group
 			'type'     => 'members',
 			'term'     => 'alpaca red',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEmpty( $suggestions );
 
 		// "zoo" is not the hidden group, so will show up here.
@@ -345,6 +366,7 @@ public function test_suggestions_response_no_matches() {
 			'type' => 'members',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertInternalType( 'array', $suggestions );
 		$this->assertEmpty( $suggestions );
 	}
@@ -355,6 +377,7 @@ public function test_suggestions_response_single_match() {
 			'type' => 'members',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertInternalType( 'array', $suggestion );
 		$this->assertNotEmpty( $suggestion );
 
@@ -372,6 +395,7 @@ public function test_suggestions_response_multiple_matches() {
 			'type' => 'members',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertInternalType( 'array', $suggestions );
 		$this->assertNotEmpty( $suggestions );
 
@@ -388,12 +412,14 @@ public function test_suggestions_term_is_case_insensitive() {
 			'term' => 'lisa',
 			'type' => 'members',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $lowercase ) );
 
 		$uppercase = bp_core_get_suggestions( array(
 			'term' => 'LISA',
 			'type' => 'members',
 		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertEquals( 1, count( $uppercase ) );
 
 		$this->assertSame( $lowercase[0]->ID, $uppercase[0]->ID );
@@ -406,6 +432,7 @@ public function test_suggestions_response_property_types() {
 			'type' => 'members',
 		) );
 
+		$this->assertFalse( is_wp_error( $suggestions ) );
 		$this->assertInternalType( 'array', $suggestion );
 		$this->assertNotEmpty( $suggestion );
 
-- 
1.9.3


From cbaf3a8a2c972bd01d2e6747f7a53ccf67d02c1d Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 19:29:17 +0100
Subject: [PATCH 32/35] Remove copypasta from 0846535

---
 tests/phpunit/testcases/apis/suggestions-nonauth.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/phpunit/testcases/apis/suggestions-nonauth.php b/tests/phpunit/testcases/apis/suggestions-nonauth.php
index 4f7366b..f953264 100644
--- a/tests/phpunit/testcases/apis/suggestions-nonauth.php
+++ b/tests/phpunit/testcases/apis/suggestions-nonauth.php
@@ -62,7 +62,6 @@ public function setUp() {
 		groups_join_group( $this->group_ids['hidden'], $this->user_ids['alpaca red'] );
 
 		// Add dummy users to dummy public groups.
-		groups_join_group( $this->group_ids['public'], $this->current_user );
 		groups_join_group( $this->group_ids['public'], $this->user_ids['aardvark'] );
 		groups_join_group( $this->group_ids['public'], $this->user_ids['alpaca red'] );
 		groups_join_group( $this->group_ids['public'], $this->user_ids['cat'] );
-- 
1.9.3


From e0699342f99508312b8612637ef9009444bca646 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 19:32:47 +0100
Subject: [PATCH 33/35] More copypasta

---
 tests/phpunit/testcases/apis/suggestions.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
index dbc82fd..c551a33 100644
--- a/tests/phpunit/testcases/apis/suggestions.php
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -377,7 +377,7 @@ public function test_suggestions_response_single_match() {
 			'type' => 'members',
 		) );
 
-		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertFalse( is_wp_error( $suggestion ) );
 		$this->assertInternalType( 'array', $suggestion );
 		$this->assertNotEmpty( $suggestion );
 
@@ -412,14 +412,14 @@ public function test_suggestions_term_is_case_insensitive() {
 			'term' => 'lisa',
 			'type' => 'members',
 		) );
-		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertFalse( is_wp_error( $lowercase ) );
 		$this->assertEquals( 1, count( $lowercase ) );
 
 		$uppercase = bp_core_get_suggestions( array(
 			'term' => 'LISA',
 			'type' => 'members',
 		) );
-		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertFalse( is_wp_error( $uppercase ) );
 		$this->assertEquals( 1, count( $uppercase ) );
 
 		$this->assertSame( $lowercase[0]->ID, $uppercase[0]->ID );
@@ -432,7 +432,7 @@ public function test_suggestions_response_property_types() {
 			'type' => 'members',
 		) );
 
-		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertFalse( is_wp_error( $suggestion ) );
 		$this->assertInternalType( 'array', $suggestion );
 		$this->assertNotEmpty( $suggestion );
 
-- 
1.9.3


From 12995d9a257a9a5fd43b65ae23bd8c725586b636 Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 19:43:17 +0100
Subject: [PATCH 34/35] Suggestions: grp members exclude test for non-auth

---
 .../phpunit/testcases/apis/suggestions-nonauth.php | 36 ++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/tests/phpunit/testcases/apis/suggestions-nonauth.php b/tests/phpunit/testcases/apis/suggestions-nonauth.php
index f953264..a16fb8e 100644
--- a/tests/phpunit/testcases/apis/suggestions-nonauth.php
+++ b/tests/phpunit/testcases/apis/suggestions-nonauth.php
@@ -119,4 +119,40 @@ public function test_suggestions_with_type_groupmembers_private() {
 
 		$this->assertTrue( is_wp_error( $suggestions ) );
 	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoom
+	}
+
+	public function test_suggestions_with_type_groupmembers_private_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );  // no access to group.
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );  // no access to group.
+	}
 }
\ No newline at end of file
-- 
1.9.3


From 7813626a7a28bcfd1208bc5968587ec26089068b Mon Sep 17 00:00:00 2001
From: Paul Gibbs <paul@byotos.com>
Date: Sun, 22 Jun 2014 20:08:04 +0100
Subject: [PATCH 35/35] Suggestions: group member queries need roles set

---
 src/bp-groups/bp-groups-functions.php | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/bp-groups/bp-groups-functions.php b/src/bp-groups/bp-groups-functions.php
index 15f78bf..a9edf9b 100644
--- a/src/bp-groups/bp-groups-functions.php
+++ b/src/bp-groups/bp-groups-functions.php
@@ -1188,7 +1188,9 @@ function bp_groups_suggestions_filter_query_args( $query_args, $suggestion_args
 		'populate_extras' => false,
 		'type'            => 'alphabetical',
 
+		'page'            => 1,
 		'group_id'        => absint( $suggestion_args['group_id'] ),
+		'group_role'      => array( 'admin', 'member', 'mod' ),
 	);
 
 	$group_users = new BP_Group_Member_Query( $group_query );
-- 
1.9.3

