Permalink
Browse files

MDL-26956 (1) Library: Move search SQL function to datalib

This function used to be in the user selector, but it is useful in
other areas where we want to search for users as it handles a number
of search options (including extra fields, etc.) and returns SQL
which can be included in a joined query.

A unit test has been added to check the results of these searches.
  • Loading branch information...
1 parent d8201d4 commit b2ec866fc75b2568ea64188eac285fdc8e9a95e8 @sammarshallou sammarshallou committed Mar 27, 2013
Showing with 206 additions and 57 deletions.
  1. +89 −0 lib/datalib.php
  2. +115 −0 lib/tests/datalib_test.php
  3. +2 −57 user/selector/lib.php
View
@@ -191,6 +191,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
* you should use this method to generate the order when displaying lists of users.
View
@@ -43,6 +43,121 @@ protected function assert_same_sql($expected, $actual) {
$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);
View
@@ -434,63 +434,8 @@ protected function required_fields_sql($u) {
* 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);
}
/**

0 comments on commit b2ec866

Please sign in to comment.