diff --git a/lib/datalib.php b/lib/datalib.php index abf24dfecc5..2167d665762 100644 --- a/lib/datalib.php +++ b/lib/datalib.php @@ -172,6 +172,97 @@ function search_users($courseid, $groupid, $searchtext, $sort='', array $excepti } } +/** + * 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. + * + * If the optional $search parameter is passed, then exact matches to the search + * will be sorted first. For example, suppose you have two users 'Al Zebra' and + * 'Alan Aardvark'. The default sort is Alan, then Al. If, however, you search for + * 'Al', then Al will be listed first. (With two users, this is not a big deal, + * but with thousands of users, it is essential.) + * + * The list of fields scanned for exact matches are: + * - firstname + * - lastname + * - $DB->sql_fullname + * - those returned by get_extra_user_fields + * + * If named parameters are used (which is the default, and highly recommended), + * then the parameter names are like :usersortexactN, where N is an int. + * + * The simplest possible example use is: + * list($sort, $params) = users_order_by_sql(); + * $sql = 'SELECT * FROM {users} ORDER BY ' . $sort; + * + * A more complex example, showing that this sort can be combined with other sorts: + * list($sort, $sortparams) = users_order_by_sql('u'); + * $sql = "SELECT g.id AS groupid, gg.groupingid, u.id AS userid, u.firstname, u.lastname, u.idnumber, u.username + * FROM {groups} g + * LEFT JOIN {groupings_groups} gg ON g.id = gg.groupid + * LEFT JOIN {groups_members} gm ON g.id = gm.groupid + * LEFT JOIN {user} u ON gm.userid = u.id + * WHERE g.courseid = :courseid $groupwhere $groupingwhere + * ORDER BY g.name, $sort"; + * $params += $sortparams; + * + * An example showing the use of $search: + * list($sort, $sortparams) = users_order_by_sql('u', $search, $this->get_context()); + * $order = ' ORDER BY ' . $sort; + * $params += $sortparams; + * $availableusers = $DB->get_records_sql($fields . $sql . $order, $params, $page*$perpage, $perpage); + * + * @param string $usertablealias (optional) any table prefix for the {users} table. E.g. 'u'. + * @param string $search (optional) a current search string. If given, + * any exact matches to this string will be sorted first. + * @param context $context the context we are in. Use by get_extra_user_fields. + * Defaults to $PAGE->context. + * @return array with two elements: + * string SQL fragment to use in the ORDER BY clause. For example, "firstname, lastname". + * array of parameters used in the SQL fragment. + */ +function users_order_by_sql($usertablealias = '', $search = null, context $context = null) { + global $DB, $PAGE; + + if ($usertablealias) { + $tableprefix = $usertablealias . '.'; + } else { + $tableprefix = ''; + } + + $sort = "{$tableprefix}lastname, {$tableprefix}firstname, {$tableprefix}id"; + $params = array(); + + if (!$search) { + return array($sort, $params); + } + + if (!$context) { + $context = $PAGE->context; + } + + $exactconditions = array(); + $paramkey = 'usersortexact1'; + + $exactconditions[] = $DB->sql_fullname($tableprefix . 'firstname', $tableprefix . 'lastname') . + ' = :' . $paramkey; + $params[$paramkey] = $search; + $paramkey++; + + $fieldstocheck = array_merge(array('firstname', 'lastname'), get_extra_user_fields($context)); + foreach ($fieldstocheck as $key => $field) { + $exactconditions[] = $tableprefix . $field . ' = :' . $paramkey; + $params[$paramkey] = $search; + $paramkey++; + } + + $sort = 'CASE WHEN ' . implode(' OR ', $exactconditions) . + ' THEN 0 ELSE 1 END, ' . $sort; + + return array($sort, $params); +} + /** * Returns a subset of users * @@ -189,7 +280,7 @@ function search_users($courseid, $groupid, $searchtext, $sort='', array $excepti * @param string $recordsperpage The number of records to return per page * @param string $fields A comma separated list of fields to be returned from the chosen table. * @return array|int|bool {@link $USER} records unless get is false in which case the integer count of the records found is returned. - * False is returned if an error is encountered. + * False is returned if an error is encountered. */ function get_users($get=true, $search='', $confirmed=false, array $exceptions=null, $sort='firstname ASC', $firstinitial='', $lastinitial='', $page='', $recordsperpage='', $fields='*', $extraselect='', array $extraparams=null) { diff --git a/lib/tests/datalib_test.php b/lib/tests/datalib_test.php new file mode 100644 index 00000000000..96e5ce8c710 --- /dev/null +++ b/lib/tests/datalib_test.php @@ -0,0 +1,90 @@ +. + +/** + * Test for various bits of datalib.php. + * + * @package core + * @category phpunit + * @copyright 2012 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Test for various bits of datalib.php. + * + * @package core_css + * @category css + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class datalib_testcase extends advanced_testcase { + protected function normalise_sql($sort) { + return preg_replace('~\s+~', ' ', $sort); + } + + protected function assert_same_sql($expected, $actual) { + $this->assertEquals($this->normalise_sql($expected), $this->normalise_sql($actual)); + } + + public function test_users_order_by_sql_simple() { + list($sort, $params) = users_order_by_sql(); + $this->assert_same_sql('lastname, firstname, id', $sort); + $this->assertEquals(array(), $params); + } + + public function test_users_order_by_sql_table_prefix() { + list($sort, $params) = users_order_by_sql('u'); + $this->assert_same_sql('u.lastname, u.firstname, u.id', $sort); + $this->assertEquals(array(), $params); + } + + public function test_users_order_by_sql_search_no_extra_fields() { + global $CFG, $DB; + $CFG->showuseridentity = ''; + $this->resetAfterTest(true); + + list($sort, $params) = users_order_by_sql('', 'search', context_system::instance()); + $this->assert_same_sql('CASE WHEN + ' . $DB->sql_fullname() . ' = :usersortexact1 OR + firstname = :usersortexact2 OR + lastname = :usersortexact3 + THEN 0 ELSE 1 END, lastname, firstname, id', $sort); + $this->assertEquals(array('usersortexact1' => 'search', 'usersortexact2' => 'search', + 'usersortexact3' => 'search'), $params); + } + + public function test_users_order_by_sql_search_with_extra_fields_and_prefix() { + global $CFG, $DB; + $CFG->showuseridentity = 'email,idnumber'; + $this->setAdminUser(); + $this->resetAfterTest(true); + + list($sort, $params) = users_order_by_sql('u', 'search', context_system::instance()); + $this->assert_same_sql('CASE WHEN + ' . $DB->sql_fullname('u.firstname', 'u.lastname') . ' = :usersortexact1 OR + u.firstname = :usersortexact2 OR + u.lastname = :usersortexact3 OR + u.email = :usersortexact4 OR + u.idnumber = :usersortexact5 + THEN 0 ELSE 1 END, u.lastname, u.firstname, u.id', $sort); + $this->assertEquals(array('usersortexact1' => 'search', 'usersortexact2' => 'search', + 'usersortexact3' => 'search', 'usersortexact4' => 'search', 'usersortexact5' => 'search'), $params); + } +}