From c4df60bb8daf229896f0b7c6a33744ae26bd4d81 Mon Sep 17 00:00:00 2001 From: stevando Date: Mon, 8 Aug 2022 14:03:33 +0800 Subject: [PATCH] MDL-75283 admin: Update admin user fetching to use sort helper --- lib/datalib.php | 20 ++-- lib/testing/generator/data_generator.php | 116 +++++++++++++++++++++++ lib/tests/datalib_test.php | 102 ++++++++++++++++++++ 3 files changed, 231 insertions(+), 7 deletions(-) diff --git a/lib/datalib.php b/lib/datalib.php index 641f2242fd4..133bafbca83 100644 --- a/lib/datalib.php +++ b/lib/datalib.php @@ -506,21 +506,27 @@ function get_users_listing($sort='lastaccess', $dir='ASC', $page=0, $recordsperp $params = $params + (array)$extraparams; } - if ($sort) { - $sort = " ORDER BY $sort $dir"; - } - // If a context is specified, get extra user fields that the current user // is supposed to see. $extrafields = ''; + $includedfields = ['id', 'username', 'email', 'firstname', 'lastname', 'city', 'country', + 'lastaccess', 'confirmed', 'mnethostid', 'suspended']; if ($extracontext) { - $extrafields = get_extra_user_fields_sql($extracontext, '', '', - array('id', 'username', 'email', 'firstname', 'lastname', 'city', 'country', - 'lastaccess', 'confirmed', 'mnethostid')); + $extrafields = get_extra_user_fields_sql($extracontext, '', '', $includedfields); } $namefields = get_all_user_name_fields(true); $extrafields = "$extrafields, $namefields"; + if ($sort) { + $orderbymap = trim($extrafields, ', '); + $orderbymap = array_merge(explode(',', $orderbymap), $includedfields); + $neworderbymap = ['default' => 'lastaccess']; + foreach($orderbymap as $value) { + $neworderbymap[$value] = $value; + } + $sort = get_safe_orderby($neworderbymap, $sort, $dir); + } + // warning: will return UNCONFIRMED USERS return $DB->get_records_sql("SELECT id, username, email, city, country, lastaccess, confirmed, mnethostid, suspended $extrafields FROM {user} diff --git a/lib/testing/generator/data_generator.php b/lib/testing/generator/data_generator.php index 21c2a517e3f..3503bf906cd 100644 --- a/lib/testing/generator/data_generator.php +++ b/lib/testing/generator/data_generator.php @@ -1307,4 +1307,120 @@ EOD; "data generators yet. Class {$classname} not found."); } + /** + * Create a new category for custom profile fields. + * + * @param array $data Array with 'name' and optionally 'sortorder' + * @return \stdClass New category object + */ + public function create_custom_profile_field_category(array $data): \stdClass { + global $DB; + + // Pick next sortorder if not defined. + if (!array_key_exists('sortorder', $data)) { + $data['sortorder'] = (int)$DB->get_field_sql('SELECT MAX(sortorder) FROM {user_info_category}') + 1; + } + + $category = (object)[ + 'name' => $data['name'], + 'sortorder' => $data['sortorder'] + ]; + $category->id = $DB->insert_record('user_info_category', $category); + + return $category; + } + + /** + * Creates a new custom profile field. + * + * Optional fields are: + * + * categoryid (or use 'category' to specify by name). If you don't specify + * either, it will add the field to a 'Testing' category, which will be created for you if + * necessary. + * + * sortorder (if you don't specify this, it will pick the next one in the category). + * + * all the other database fields (if you don't specify this, it will pick sensible defaults + * based on the data type). + * + * @param array $data Array with 'datatype', 'shortname', and 'name' + * @return \stdClass Database object from the user_info_field table + */ + public function create_custom_profile_field(array $data): \stdClass { + global $DB, $CFG; + require_once($CFG->dirroot . '/user/profile/lib.php'); + + // Set up category if necessary. + if (!array_key_exists('categoryid', $data)) { + if (array_key_exists('category', $data)) { + $data['categoryid'] = $DB->get_field('user_info_category', 'id', + ['name' => $data['category']], MUST_EXIST); + } else { + // Make up a 'Testing' category or use existing. + $data['categoryid'] = $DB->get_field('user_info_category', 'id', ['name' => 'Testing']); + if (!$data['categoryid']) { + $created = $this->create_custom_profile_field_category(['name' => 'Testing']); + $data['categoryid'] = $created->id; + } + } + } + + // Pick sort order if necessary. + if (!array_key_exists('sortorder', $data)) { + $data['sortorder'] = (int)$DB->get_field_sql( + 'SELECT MAX(sortorder) FROM {user_info_field} WHERE categoryid = ?', + [$data['categoryid']]) + 1; + } + + // Defaults for other values. + $defaults = [ + 'description' => '', + 'descriptionformat' => 0, + 'required' => 0, + 'locked' => 0, + 'visible' => PROFILE_VISIBLE_ALL, + 'forceunique' => 0, + 'signup' => 0, + 'defaultdata' => '', + 'defaultdataformat' => 0, + 'param1' => '', + 'param2' => '', + 'param3' => '', + 'param4' => '', + 'param5' => '' + ]; + + // Type-specific defaults for other values. + $typedefaults = [ + 'text' => [ + 'param1' => 30, + 'param2' => 2048 + ], + 'menu' => [ + 'param1' => "Yes\nNo", + 'defaultdata' => 'No' + ], + 'datetime' => [ + 'param1' => '2010', + 'param2' => '2015', + 'param3' => 1 + ], + 'checkbox' => [ + 'defaultdata' => 0 + ] + ]; + foreach ($typedefaults[$data['datatype']] ?? [] as $field => $value) { + $defaults[$field] = $value; + } + + foreach ($defaults as $field => $value) { + if (!array_key_exists($field, $data)) { + $data[$field] = $value; + } + } + + $data['id'] = $DB->insert_record('user_info_field', $data); + return (object)$data; + } } diff --git a/lib/tests/datalib_test.php b/lib/tests/datalib_test.php index aa1fa0d3878..f95f6d75154 100644 --- a/lib/tests/datalib_test.php +++ b/lib/tests/datalib_test.php @@ -684,6 +684,108 @@ class core_datalib_testcase extends advanced_testcase { } } + /** + * Tests the get_users_listing function. + */ + public function test_get_users_listing(): void { + global $DB; + + $this->resetAfterTest(); + + $generator = $this->getDataGenerator(); + + // Set up profile field. + $generator->create_custom_profile_field(['datatype' => 'text', + 'shortname' => 'specialid', 'name' => 'Special user id']); + + // Set up the show user identity option. + set_config('showuseridentity', 'department'); + + // Get all the existing user ids (we're going to remove these from test results). + $existingids = array_fill_keys($DB->get_fieldset_select('user', 'id', '1 = 1'), true); + + // Create some test user accounts. + $userids = []; + foreach (['a', 'b', 'c', 'd'] as $key) { + $record = [ + 'username' => 'user_' . $key, + 'firstname' => $key . '_first', + 'lastname' => 'last_' . $key, + 'department' => 'department_' . $key, + 'lastaccess' => ord($key) + ]; + $user = $generator->create_user($record); + $userids[] = $user->id; + } + + // Check default result with no parameters. + $results = get_users_listing(); + $results = array_diff_key($results, $existingids); + + // It should return all the results in order. + $this->assertEquals($userids, array_keys($results)); + + // Results should have some general fields and name fields, check some samples. + $this->assertEquals('user_a', $results[$userids[0]]->username); + $this->assertEquals('user_a@example.com', $results[$userids[0]]->email); + $this->assertEquals(1, $results[$userids[0]]->confirmed); + $this->assertEquals('a_first', $results[$userids[0]]->firstname); + $this->assertObjectHasAttribute('firstnamephonetic', $results[$userids[0]]); + + // Should not have department because no context specified. + $this->assertObjectNotHasAttribute('department', $results[$userids[0]]); + + // Check sorting. + $results = get_users_listing('username', 'DESC'); + $results = array_diff_key($results, $existingids); + $this->assertEquals([$userids[3], $userids[2], $userids[1], $userids[0]], array_keys($results)); + + // Check default fallback sort field works as expected. + $results = get_users_listing('blah2', 'ASC'); + $results = array_diff_key($results, $existingids); + $this->assertEquals([$userids[0], $userids[1], $userids[2], $userids[3]], array_keys($results)); + + // Check default fallback sort direction works as expected. + $results = get_users_listing('lastaccess', 'blah2'); + $results = array_diff_key($results, $existingids); + $this->assertEquals([$userids[0], $userids[1], $userids[2], $userids[3]], array_keys($results)); + + // Add the options to showuseridentity and check it returns those fields but only if you + // specify a context AND have permissions. + $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', '', '', null, + \context_system::instance()); + $this->assertObjectNotHasAttribute('department', $results[$userids[0]]); + $this->setAdminUser(); + $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', '', '', null, + \context_system::instance()); + $this->assertEquals('department_a', $results[$userids[0]]->department); + + // Check search (full name, email, username). + $results = get_users_listing('lastaccess', 'asc', 0, 0, 'b_first last_b'); + $this->assertEquals([$userids[1]], array_keys($results)); + $results = get_users_listing('lastaccess', 'asc', 0, 0, 'c@example'); + $this->assertEquals([$userids[2]], array_keys($results)); + $results = get_users_listing('lastaccess', 'asc', 0, 0, 'user_d'); + $this->assertEquals([$userids[3]], array_keys($results)); + + // Check first and last initial restriction (all the test ones have same last initial). + $results = get_users_listing('lastaccess', 'asc', 0, 0, '', 'C'); + $this->assertEquals([$userids[2]], array_keys($results)); + $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', 'L'); + $results = array_diff_key($results, $existingids); + $this->assertEquals($userids, array_keys($results)); + + // Check the extra where clause, either with the 'u.' prefix or not. + $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', '', 'id IN (:x,:y)', + ['x' => $userids[1], 'y' => $userids[3]]); + $results = array_diff_key($results, $existingids); + $this->assertEquals([$userids[1], $userids[3]], array_keys($results)); + $results = get_users_listing('lastaccess', 'asc', 0, 0, '', '', '', 'id IN (:x,:y)', + ['x' => $userids[1], 'y' => $userids[3]]); + $results = array_diff_key($results, $existingids); + $this->assertEquals([$userids[1], $userids[3]], array_keys($results)); + } + /** * Data provider for test_get_safe_orderby(). *