diff --git a/lib/datalib.php b/lib/datalib.php index 9d257480060..c9f096af696 100644 --- a/lib/datalib.php +++ b/lib/datalib.php @@ -190,6 +190,95 @@ function search_users($courseid, $groupid, $searchtext, $sort='', array $excepti } } +/** + * Returns SQL used to search through user table to find users (in a query + * which may also join and apply other conditions). + * + * You can combine this SQL with an existing query by adding 'AND $sql' to the + * WHERE clause of your query (where $sql is the first element in the array + * returned by this function), and merging in the $params array to the parameters + * of your query (where $params is the second element). Your query should use + * named parameters such as :param, rather than the question mark style. + * + * There are examples of basic usage in the unit test for this function. + * + * @param string $search the text to search for (empty string = find all) + * @param string $u the table alias for the user table in the query being + * built. May be ''. + * @param bool $searchanywhere If true (default), searches in the middle of + * names, otherwise only searches at start + * @param array $extrafields Array of extra user fields to include in search + * @param array $exclude Array of user ids to exclude (empty = don't exclude) + * @param array $includeonly If specified, only returns users that have ids + * incldued in this array (empty = don't restrict) + * @return array an array with two elements, a fragment of SQL to go in the + * where clause the query, and an associative array containing any required + * parameters (using named placeholders). + */ +function users_search_sql($search, $u = 'u', $searchanywhere = true, array $extrafields = array(), + array $exclude = array(), array $includeonly = array()) { + global $DB, $CFG; + $params = array(); + $tests = array(); + + if ($u) { + $u .= '.'; + } + + // If we have a $search string, put a field LIKE '$search%' condition on each field. + if ($search) { + $conditions = array( + $DB->sql_fullname($u . 'firstname', $u . 'lastname'), + $conditions[] = $u . 'lastname' + ); + foreach ($extrafields as $field) { + $conditions[] = $u . $field; + } + if ($searchanywhere) { + $searchparam = '%' . $search . '%'; + } else { + $searchparam = $search . '%'; + } + $i = 0; + foreach ($conditions as $key => $condition) { + $conditions[$key] = $DB->sql_like($condition, ":con{$i}00", false, false); + $params["con{$i}00"] = $searchparam; + $i++; + } + $tests[] = '(' . implode(' OR ', $conditions) . ')'; + } + + // Add some additional sensible conditions. + $tests[] = $u . "id <> :guestid"; + $params['guestid'] = $CFG->siteguest; + $tests[] = $u . 'deleted = 0'; + $tests[] = $u . 'confirmed = 1'; + + // If we are being asked to exclude any users, do that. + if (!empty($exclude)) { + list($usertest, $userparams) = $DB->get_in_or_equal($exclude, SQL_PARAMS_NAMED, 'ex', false); + $tests[] = $u . 'id ' . $usertest; + $params = array_merge($params, $userparams); + } + + // If we are validating a set list of userids, add an id IN (...) test. + if (!empty($includeonly)) { + list($usertest, $userparams) = $DB->get_in_or_equal($includeonly, SQL_PARAMS_NAMED, 'val'); + $tests[] = $u . 'id ' . $usertest; + $params = array_merge($params, $userparams); + } + + // In case there are no tests, add one result (this makes it easier to combine + // this with an existing query as you can always add AND $sql). + if (empty($tests)) { + $tests[] = '1 = 1'; + } + + // Combing the conditions and return. + return array(implode(' AND ', $tests), $params); +} + + /** * This function generates the standard ORDER BY clause for use when generating * lists of users. If you don't have a reason to use a different order, then diff --git a/lib/tests/datalib_test.php b/lib/tests/datalib_test.php index e758c74f857..cce2090f444 100644 --- a/lib/tests/datalib_test.php +++ b/lib/tests/datalib_test.php @@ -43,6 +43,121 @@ class datalib_testcase extends advanced_testcase { $this->assertEquals($this->normalise_sql($expected), $this->normalise_sql($actual)); } + /** + * Do a test of the user search SQL with database users. + */ + public function test_users_search_sql() { + global $DB; + + // Set up test users. + $this->resetAfterTest(true); + $user1 = array( + 'username' => 'usernametest1', + 'idnumber' => 'idnumbertest1', + 'firstname' => 'First Name User Test 1', + 'lastname' => 'Last Name User Test 1', + 'email' => 'usertest1@email.com', + 'address' => '2 Test Street Perth 6000 WA', + 'phone1' => '01010101010', + 'phone2' => '02020203', + 'icq' => 'testuser1', + 'skype' => 'testuser1', + 'yahoo' => 'testuser1', + 'aim' => 'testuser1', + 'msn' => 'testuser1', + 'department' => 'Department of user 1', + 'institution' => 'Institution of user 1', + 'description' => 'This is a description for user 1', + 'descriptionformat' => FORMAT_MOODLE, + 'city' => 'Perth', + 'url' => 'http://moodle.org', + 'country' => 'au' + ); + $user1 = self::getDataGenerator()->create_user($user1); + $user2 = array( + 'username' => 'usernametest2', + 'idnumber' => 'idnumbertest2', + 'firstname' => 'First Name User Test 2', + 'lastname' => 'Last Name User Test 2', + 'email' => 'usertest2@email.com', + 'address' => '222 Test Street Perth 6000 WA', + 'phone1' => '01010101010', + 'phone2' => '02020203', + 'icq' => 'testuser1', + 'skype' => 'testuser1', + 'yahoo' => 'testuser1', + 'aim' => 'testuser1', + 'msn' => 'testuser1', + 'department' => 'Department of user 2', + 'institution' => 'Institution of user 2', + 'description' => 'This is a description for user 2', + 'descriptionformat' => FORMAT_MOODLE, + 'city' => 'Perth', + 'url' => 'http://moodle.org', + 'country' => 'au' + ); + $user2 = self::getDataGenerator()->create_user($user2); + + // Search by name (anywhere in text). + list($sql, $params) = users_search_sql('User Test 2', ''); + $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); + $this->assertFalse(array_key_exists($user1->id, $results)); + $this->assertTrue(array_key_exists($user2->id, $results)); + + // Search by (most of) full name. + list($sql, $params) = users_search_sql('First Name User Test 2 Last Name User', ''); + $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); + $this->assertFalse(array_key_exists($user1->id, $results)); + $this->assertTrue(array_key_exists($user2->id, $results)); + + // Search by name (start of text) valid or not. + list($sql, $params) = users_search_sql('User Test 2', '', false); + $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); + $this->assertEquals(0, count($results)); + list($sql, $params) = users_search_sql('First Name User Test 2', '', false); + $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); + $this->assertFalse(array_key_exists($user1->id, $results)); + $this->assertTrue(array_key_exists($user2->id, $results)); + + // Search by extra fields included or not (address). + list($sql, $params) = users_search_sql('Test Street', '', true); + $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); + $this->assertEquals(0, count($results)); + list($sql, $params) = users_search_sql('Test Street', '', true, array('address')); + $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); + $this->assertEquals(2, count($results)); + + // Exclude user. + list($sql, $params) = users_search_sql('User Test', '', true, array(), array($user1->id)); + $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); + $this->assertFalse(array_key_exists($user1->id, $results)); + $this->assertTrue(array_key_exists($user2->id, $results)); + + // Include only user. + list($sql, $params) = users_search_sql('User Test', '', true, array(), array(), array($user1->id)); + $results = $DB->get_records_sql("SELECT id FROM {user} WHERE $sql ORDER BY username", $params); + $this->assertTrue(array_key_exists($user1->id, $results)); + $this->assertFalse(array_key_exists($user2->id, $results)); + + // Join with another table and use different prefix. + set_user_preference('amphibian', 'frog', $user1); + set_user_preference('amphibian', 'salamander', $user2); + list($sql, $params) = users_search_sql('User Test 1', 'qq'); + $results = $DB->get_records_sql(" + SELECT + up.id, up.value + FROM + {user} qq + JOIN {user_preferences} up ON up.userid = qq.id + WHERE + up.name = :prefname + AND $sql", array_merge(array('prefname' => 'amphibian'), $params)); + $this->assertEquals(1, count($results)); + foreach ($results as $record) { + $this->assertEquals('frog', $record->value); + } + } + public function test_users_order_by_sql_simple() { list($sort, $params) = users_order_by_sql(); $this->assert_same_sql('lastname, firstname, id', $sort); diff --git a/user/selector/lib.php b/user/selector/lib.php index 18e9dfc5b35..1483f6f8276 100644 --- a/user/selector/lib.php +++ b/user/selector/lib.php @@ -434,63 +434,8 @@ abstract class user_selector_base { * this uses ? style placeholders. */ protected function search_sql($search, $u) { - global $DB, $CFG; - $params = array(); - $tests = array(); - - if ($u) { - $u .= '.'; - } - - // If we have a $search string, put a field LIKE '$search%' condition on each field. - if ($search) { - $conditions = array( - $DB->sql_fullname($u . 'firstname', $u . 'lastname'), - $conditions[] = $u . 'lastname' - ); - foreach ($this->extrafields as $field) { - $conditions[] = $u . $field; - } - if ($this->searchanywhere) { - $searchparam = '%' . $search . '%'; - } else { - $searchparam = $search . '%'; - } - $i = 0; - foreach ($conditions as $key=>$condition) { - $conditions[$key] = $DB->sql_like($condition, ":con{$i}00", false, false); - $params["con{$i}00"] = $searchparam; - $i++; - } - $tests[] = '(' . implode(' OR ', $conditions) . ')'; - } - - // Add some additional sensible conditions - $tests[] = $u . "id <> :guestid"; - $params['guestid'] = $CFG->siteguest; - $tests[] = $u . 'deleted = 0'; - $tests[] = $u . 'confirmed = 1'; - - // If we are being asked to exclude any users, do that. - if (!empty($this->exclude)) { - list($usertest, $userparams) = $DB->get_in_or_equal($this->exclude, SQL_PARAMS_NAMED, 'ex', false); - $tests[] = $u . 'id ' . $usertest; - $params = array_merge($params, $userparams); - } - - // If we are validating a set list of userids, add an id IN (...) test. - if (!empty($this->validatinguserids)) { - list($usertest, $userparams) = $DB->get_in_or_equal($this->validatinguserids, SQL_PARAMS_NAMED, 'val'); - $tests[] = $u . 'id ' . $usertest; - $params = array_merge($params, $userparams); - } - - if (empty($tests)) { - $tests[] = '1 = 1'; - } - - // Combing the conditions and return. - return array(implode(' AND ', $tests), $params); + return users_search_sql($search, 'u', $this->searchanywhere, $this->extrafields, + $this->exclude, $this->validatinguserids); } /**