From 23c20dc23f6d5aec1531467b457872b3e34bdee6 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Wed, 30 Sep 2020 14:05:43 +0100 Subject: [PATCH 1/9] MDL-45242 Admin: Added lazy-loading callback to multicheckbox Currently admin_setting_configselect has lazy-loading support via a callback function (so you don't have to make pointless single-use classes for each unusual setting), but this is not present in other similar types. This commit adds identical support to admin_setting_configmulticheckbox. --- lib/adminlib.php | 30 +++++++++++++++++++++--------- lib/upgrade.txt | 2 ++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/adminlib.php b/lib/adminlib.php index d23d1b15885..c3ead083eaa 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -3066,34 +3066,46 @@ class admin_setting_configcheckbox extends admin_setting { class admin_setting_configmulticheckbox extends admin_setting { /** @var array Array of choices value=>label */ public $choices; + /** @var callable|null Loader function for choices */ + protected $choiceloader = null; /** * Constructor: uses parent::__construct * + * The $choices parameter may be either an array of $value => $label format, + * e.g. [1 => get_string('yes')], or a callback function which takes no parameters and + * returns an array in that format. + * * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. * @param string $visiblename localised * @param string $description long localised info * @param array $defaultsetting array of selected - * @param array $choices array of $value=>$label for each checkbox + * @param array|callable $choices array of $value => $label for each checkbox, or a callback */ public function __construct($name, $visiblename, $description, $defaultsetting, $choices) { - $this->choices = $choices; + if (is_array($choices)) { + $this->choices = $choices; + } + if (is_callable($choices)) { + $this->choiceloader = $choices; + } parent::__construct($name, $visiblename, $description, $defaultsetting); } /** - * This public function may be used in ancestors for lazy loading of choices + * This function may be used in ancestors for lazy loading of choices + * + * Override this method if loading of choices is expensive, such + * as when it requires multiple db requests. * - * @todo Check if this function is still required content commented out only returns true * @return bool true if loaded, false if error */ public function load_choices() { - /* - if (is_array($this->choices)) { - return true; + if ($this->choiceloader) { + if (!is_array($this->choices)) { + $this->choices = call_user_func($this->choiceloader); + } } - .... load choices here - */ return true; } diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 53ca66805d3..6fdc886ab4d 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -33,6 +33,8 @@ information provided here is intended especially for developers. * New DML driver method `$DB->sql_group_concat` for performing group concatenation of a field within a SQL query * Added new class, AMD modules and WS that allow displaying forms in modal popups or load and submit in AJAX requests. See https://docs.moodle.org/dev/Modal_and_AJAX_forms for more details. +* Admin setting admin_setting_configmulticheckbox now supports lazy-loading the options list by + supplying a callback function instead of an array of options. === 3.10 === * PHPUnit has been upgraded to 8.5. That comes with a few changes: From f21cf5bc86ccc2455e8f2cd7c3d0b58504a02a69 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Wed, 30 Sep 2020 16:21:47 +0100 Subject: [PATCH 2/9] MDL-45242 Testing: Generators for user profile fields --- lib/behat/classes/behat_core_generator.php | 12 +++ lib/testing/generator/data_generator.php | 117 +++++++++++++++++++++ lib/testing/tests/generator_test.php | 115 ++++++++++++++++++++ 3 files changed, 244 insertions(+) diff --git a/lib/behat/classes/behat_core_generator.php b/lib/behat/classes/behat_core_generator.php index 8851be97ce2..7f900ae3ade 100644 --- a/lib/behat/classes/behat_core_generator.php +++ b/lib/behat/classes/behat_core_generator.php @@ -87,6 +87,18 @@ class behat_core_generator extends behat_generator_base { 'required' => ['name', 'category', 'type', 'shortname'], 'switchids' => [], ], + 'custom profile field categories' => [ + 'singular' => 'custom profile field category', + 'datagenerator' => 'custom_profile_field_category', + 'required' => ['name'], + 'switchids' => [], + ], + 'custom profile fields' => [ + 'singular' => 'custom profile field', + 'datagenerator' => 'custom_profile_field', + 'required' => ['datatype', 'shortname', 'name'], + 'switchids' => [], + ], 'permission overrides' => [ 'singular' => 'permission override', 'datagenerator' => 'permission_override', diff --git a/lib/testing/generator/data_generator.php b/lib/testing/generator/data_generator.php index 600cc762a2c..a2344f9f2ec 100644 --- a/lib/testing/generator/data_generator.php +++ b/lib/testing/generator/data_generator.php @@ -1190,6 +1190,123 @@ EOD; return $this->get_plugin_generator('core_customfield')->create_field($data); } + /** + * 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; + } + /** * Create a new user, and enrol them in the specified course as the supplied role. * diff --git a/lib/testing/tests/generator_test.php b/lib/testing/tests/generator_test.php index 2fd59265b40..a2e82115015 100644 --- a/lib/testing/tests/generator_test.php +++ b/lib/testing/tests/generator_test.php @@ -466,4 +466,119 @@ class core_test_generator_testcase extends advanced_testcase { 'parent' => $gradecategory->id)); $this->assertEquals($gradecategory->id, $gradecategory2->parent); } + + public function test_create_custom_profile_field_category() { + global $DB; + + $this->resetAfterTest(); + $generator = $this->getDataGenerator(); + + // Insert first category without specified sortorder. + $result = $generator->create_custom_profile_field_category(['name' => 'Frogs']); + $record = $DB->get_record('user_info_category', ['name' => 'Frogs']); + $this->assertEquals(1, $record->sortorder); + + // Also check the return value. + $this->assertEquals(1, $result->sortorder); + $this->assertEquals('Frogs', $result->name); + $this->assertEquals($record->id, $result->id); + + // Insert next category without specified sortorder. + $generator->create_custom_profile_field_category(['name' => 'Zombies']); + $record = $DB->get_record('user_info_category', ['name' => 'Zombies']); + $this->assertEquals(2, $record->sortorder); + + // Insert category with specified sortorder. + $generator->create_custom_profile_field_category(['name' => 'Toads', 'sortorder' => 9]); + $record = $DB->get_record('user_info_category', ['name' => 'Toads']); + $this->assertEquals(9, $record->sortorder); + + // Insert another with unspecified sortorder. + $generator->create_custom_profile_field_category(['name' => 'Werewolves']); + $record = $DB->get_record('user_info_category', ['name' => 'Werewolves']); + $this->assertEquals(10, $record->sortorder); + } + + public function test_create_custom_profile_field() { + global $DB; + + $this->resetAfterTest(); + $generator = $this->getDataGenerator(); + + // Insert minimal field without specified category. + $field1 = $generator->create_custom_profile_field( + ['datatype' => 'text', 'shortname' => 'colour', 'name' => 'Colour']); + $record = $DB->get_record('user_info_field', ['shortname' => 'colour']); + + // Check specified values. + $this->assertEquals('Colour', $record->name); + $this->assertEquals('text', $record->datatype); + + // Check sortorder (first in category). + $this->assertEquals(1, $record->sortorder); + + // Check shared defaults for most datatypes. + $this->assertEquals('', $record->description); + $this->assertEquals(0, $record->descriptionformat); + $this->assertEquals(0, $record->required); + $this->assertEquals(0, $record->locked); + $this->assertEquals(PROFILE_VISIBLE_ALL, $record->visible); + $this->assertEquals(0, $record->forceunique); + $this->assertEquals(0, $record->signup); + $this->assertEquals('', $record->defaultdata); + $this->assertEquals(0, $record->defaultdataformat); + + // Check specific defaults for text datatype. + $this->assertEquals(30, $record->param1); + $this->assertEquals(2048, $record->param2); + + // Check the returned value matches the database data. + $this->assertEquals($record, $field1); + + // The category should relate to a new 'testing' category. + $catrecord = $DB->get_record('user_info_category', ['id' => $record->categoryid]); + $this->assertEquals('Testing', $catrecord->name); + $this->assertEquals(1, $catrecord->sortorder); + + // Create another field, this time supplying values for a few of the fields. + $generator->create_custom_profile_field( + ['datatype' => 'text', 'shortname' => 'brightness', 'name' => 'Brightness', + 'required' => 1, 'forceunique' => 1]); + $record = $DB->get_record('user_info_field', ['shortname' => 'brightness']); + + // Same testing category, next sortorder. + $this->assertEquals($catrecord->id, $record->categoryid); + $this->assertEquals(2, $record->sortorder); + + // Check modified fields. + $this->assertEquals(1, $record->required); + $this->assertEquals(1, $record->forceunique); + + // Create a field in specified category by id or name... + $category = $generator->create_custom_profile_field_category(['name' => 'Amphibians']); + $field3 = $generator->create_custom_profile_field( + ['datatype' => 'text', 'shortname' => 'frog', 'name' => 'Frog', + 'categoryid' => $category->id]); + $this->assertEquals($category->id, $field3->categoryid); + $this->assertEquals(1, $field3->sortorder); + $field4 = $generator->create_custom_profile_field( + ['datatype' => 'text', 'shortname' => 'toad', 'name' => 'Toad', + 'category' => 'Amphibians', 'sortorder' => 4]); + $this->assertEquals($category->id, $field4->categoryid); + $this->assertEquals(4, $field4->sortorder); + + // Check defaults for menu, datetime, and checkbox. + $field5 = $generator->create_custom_profile_field( + ['datatype' => 'menu', 'shortname' => 'cuisine', 'name' => 'Cuisine']); + $this->assertEquals("Yes\nNo", $field5->param1); + $this->assertEquals('No', $field5->defaultdata); + $field6 = $generator->create_custom_profile_field( + ['datatype' => 'datetime', 'shortname' => 'epoch', 'name' => 'Epoch']); + $this->assertEquals(2010, $field6->param1); + $this->assertEquals(2015, $field6->param2); + $this->assertEquals(1, $field6->param3); + $field7 = $generator->create_custom_profile_field( + ['datatype' => 'checkbox', 'shortname' => 'areyousure', 'name' => 'Are you sure?']); + $this->assertEquals(0, $field7->defaultdata); + } } From 68e576b0ed33d85af66bc20932eea34b078d9d11 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Mon, 12 Oct 2020 15:24:07 +0100 Subject: [PATCH 3/9] MDL-45242 Lib: Allow custom profile fields in showuseridentity --- admin/settings/users.php | 43 +- lang/en/admin.php | 4 +- lib/classes/user_fields.php | 644 +++++++++++++++++++++++ lib/moodlelib.php | 97 +--- lib/tests/behat/showuseridentity.feature | 70 +++ lib/tests/user_fields_test.php | 511 ++++++++++++++++++ lib/upgrade.txt | 2 + user/profile/lib.php | 30 ++ user/tests/profilelib_test.php | 44 ++ 9 files changed, 1353 insertions(+), 92 deletions(-) create mode 100644 lib/classes/user_fields.php create mode 100644 lib/tests/behat/showuseridentity.feature create mode 100644 lib/tests/user_fields_test.php diff --git a/admin/settings/users.php b/admin/settings/users.php index 6e905dc0cad..6e58d4cd89e 100644 --- a/admin/settings/users.php +++ b/admin/settings/users.php @@ -214,21 +214,38 @@ if ($hassiteconfig // with moodle/site:viewuseridentity). // Options include fields from the user table that might be helpful to // distinguish when adding or listing users ('I want to add the John - // Smith from Science faculty'). - // Custom user profile fields are not currently supported. + // Smith from Science faculty') and any custom profile fields. $temp->add(new admin_setting_configmulticheckbox('showuseridentity', new lang_string('showuseridentity', 'admin'), - new lang_string('showuseridentity_desc', 'admin'), array('email' => 1), array( - 'username' => new lang_string('username'), - 'idnumber' => new lang_string('idnumber'), - 'email' => new lang_string('email'), - 'phone1' => new lang_string('phone1'), - 'phone2' => new lang_string('phone2'), - 'department' => new lang_string('department'), - 'institution' => new lang_string('institution'), - 'city' => new lang_string('city'), - 'country' => new lang_string('country'), - ))); + new lang_string('showuseridentity_desc', 'admin'), ['email' => 1], + function() { + global $DB; + + // Basic fields available in user table. + $fields = [ + 'username' => new lang_string('username'), + 'idnumber' => new lang_string('idnumber'), + 'email' => new lang_string('email'), + 'phone1' => new lang_string('phone1'), + 'phone2' => new lang_string('phone2'), + 'department' => new lang_string('department'), + 'institution' => new lang_string('institution'), + 'city' => new lang_string('city'), + 'country' => new lang_string('country'), + ]; + + // Custom profile fields. + $profilefields = $DB->get_records('user_info_field', ['datatype' => 'text'], 'sortorder ASC'); + foreach ($profilefields as $key => $field) { + // Only reasonable-length fields can be used as identity fields. + if ($field->param2 > 255) { + continue; + } + $fields['profile_field_' . $field->shortname] = $field->name . ' *'; + } + + return $fields; + })); $setting = new admin_setting_configtext('fullnamedisplay', new lang_string('fullnamedisplay', 'admin'), new lang_string('configfullnamedisplay', 'admin'), 'language', PARAM_TEXT, 50); $setting->set_force_ltr(true); diff --git a/lang/en/admin.php b/lang/en/admin.php index a766853d503..3ecba847145 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -1205,7 +1205,9 @@ $string['setupsearchengine'] = 'Setup search engine'; $string['showcommentscount'] = 'Show comments count'; $string['showdetails'] = 'Show details'; $string['showuseridentity'] = 'Show user identity'; -$string['showuseridentity_desc'] = 'When selecting or searching for users, and when displaying lists of users, these fields may be shown in addition to their full name. The fields are only shown to users who have the moodle/site:viewuseridentity capability; by default, teachers and managers. (This option makes most sense if you choose one or two fields that are mandatory at your institution.)'; +$string['showuseridentity_desc'] = 'When selecting or searching for users, and when displaying lists of users, these fields may be shown in addition to their full name. The fields are only shown to users who have the moodle/site:viewuseridentity capability; by default, teachers and managers. (This option makes most sense if you choose one or two fields that are mandatory at your institution.) + +Fields marked * are custom user profile fields. You can select these fields, but there are currently some screens on which they will not appear.'; $string['simplexmlrequired'] = 'The SimpleXML PHP extension is now required by Moodle.'; $string['sitemenubar'] = 'Site navigation'; $string['sitemailcharset'] = 'Character set'; diff --git a/lib/classes/user_fields.php b/lib/classes/user_fields.php new file mode 100644 index 00000000000..d7fd9eb6c60 --- /dev/null +++ b/lib/classes/user_fields.php @@ -0,0 +1,644 @@ +. + +namespace core; + +/** + * Class for retrieving information about user fields that are needed for displaying user identity. + * + * @package core + */ +class user_fields { + /** @var string Prefix used to identify custom profile fields */ + const PROFILE_FIELD_PREFIX = 'profile_field_'; + /** @var string Regular expression used to match a field name against the prefix */ + const PROFILE_FIELD_REGEX = '~^' . self::PROFILE_FIELD_PREFIX . '(.*)$~'; + + /** @var int All fields required to display user's identity, based on server configuration */ + const PURPOSE_IDENTITY = 0; + /** @var int All fields required to display a user picture */ + const PURPOSE_USERPIC = 1; + /** @var int All fields required for somebody's name */ + const PURPOSE_NAME = 2; + /** @var int Field required by custom include list */ + const CUSTOM_INCLUDE = 3; + + /** @var \context|null Context in use */ + protected $context; + + /** @var bool True to allow custom user fields */ + protected $allowcustom; + + /** @var bool[] Array of purposes (from PURPOSE_xx to true/false) */ + protected $purposes; + + /** @var string[] List of extra fields to include */ + protected $include; + + /** @var string[] List of fields to exclude */ + protected $exclude; + + /** @var int Unique identifier for different queries generated in same request */ + protected static $uniqueidentifier = 1; + + /** @var array|null Associative array from field => array of purposes it was used for => true */ + protected $fields = null; + + /** + * Protected constructor - use one of the for_xx methods to create an object. + * + * @param int $purpose Initial purpose for object or -1 for none + */ + protected function __construct(int $purpose = -1) { + $this->purposes = [ + self::PURPOSE_IDENTITY => false, + self::PURPOSE_USERPIC => false, + self::PURPOSE_NAME => false, + ]; + if ($purpose != -1) { + $this->purposes[$purpose] = true; + } + $this->include = []; + $this->exclude = []; + $this->context = null; + $this->allowcustom = true; + } + + /** + * Constructs an empty user fields object to get arbitrary user fields. + * + * You can add fields to retrieve with the including() function. + * + * @return user_fields User fields object ready for use + */ + public static function empty(): user_fields { + return new user_fields(); + } + + /** + * Constructs a user fields object to get identity information for display. + * + * The function does all the required capability checks to see if the current user is allowed + * to see them in the specified context. You can pass context null to get all the fields without + * checking permissions. + * + * If the code can only handle fields in the main user table, and not custom profile fields, + * then set $allowcustom to false. + * + * Note: After constructing the object you can use the ->with_xx, ->including, and ->excluding + * functions to control the required fields in more detail. For example: + * + * $fields = user_fields::for_identity($context)->with_userpic()->excluding('email'); + * + * @param \context|null $context Context; if supplied, includes only fields the current user should see + * @param bool $allowcustom If true, custom profile fields may be included + * @return user_fields User fields object ready for use + */ + public static function for_identity(?\context $context, bool $allowcustom = true): user_fields { + $fields = new user_fields(self::PURPOSE_IDENTITY); + $fields->context = $context; + $fields->allowcustom = $allowcustom; + return $fields; + } + + /** + * Constructs a user fields object to get information required for displaying a user picture. + * + * Note: After constructing the object you can use the ->with_xx, ->including, and ->excluding + * functions to control the required fields in more detail. For example: + * + * $fields = user_fields::for_userpic()->with_name()->excluding('email'); + * + * @return user_fields User fields object ready for use + */ + public static function for_userpic(): user_fields { + return new user_fields(self::PURPOSE_USERPIC); + } + + /** + * Constructs a user fields object to get information required for displaying a user full name. + * + * Note: After constructing the object you can use the ->with_xx, ->including, and ->excluding + * functions to control the required fields in more detail. For example: + * + * $fields = user_fields::for_name()->with_userpic()->excluding('email'); + * + * @return user_fields User fields object ready for use + */ + public static function for_name(): user_fields { + return new user_fields(self::PURPOSE_NAME); + } + + /** + * On an existing user_fields object, adds the fields required for displaying user pictures. + * + * @return $this Same object for chaining function calls + */ + public function with_userpic(): user_fields { + $this->purposes[self::PURPOSE_USERPIC] = true; + return $this; + } + + /** + * On an existing user_fields object, adds the fields required for displaying user full names. + * + * @return $this Same object for chaining function calls + */ + public function with_name(): user_fields { + $this->purposes[self::PURPOSE_NAME] = true; + return $this; + } + + /** + * On an existing user_fields object, adds the fields required for displaying user identity. + * + * The function does all the required capability checks to see if the current user is allowed + * to see them in the specified context. You can pass context null to get all the fields without + * checking permissions. + * + * If the code can only handle fields in the main user table, and not custom profile fields, + * then set $allowcustom to false. + * + * @param \context|null Context; if supplied, includes only fields the current user should see + * @param bool $allowcustom If true, custom profile fields may be included + * @return $this Same object for chaining function calls + */ + public function with_identity(?\context $context, bool $allowcustom = true): user_fields { + $this->context = $context; + $this->allowcustom = $allowcustom; + $this->purposes[self::PURPOSE_IDENTITY] = true; + return $this; + } + + /** + * On an existing user_fields object, adds extra fields to be retrieved. You can specify either + * fields from the user table e.g. 'email', or profile fields e.g. 'profile_field_height'. + * + * @param string ...$include One or more fields to add + * @return $this Same object for chaining function calls + */ + public function including(string ...$include): user_fields { + $this->include = array_merge($this->include, $include); + return $this; + } + + /** + * On an existing user_fields object, excludes fields from retrieval. You can specify either + * fields from the user table e.g. 'email', or profile fields e.g. 'profile_field_height'. + * + * This is useful when constructing queries where your query already explicitly references + * certain fields, so you don't want to retrieve them twice. + * + * @param string ...$exclude One or more fields to exclude + * @return $this Same object for chaining function calls + */ + public function excluding(...$exclude): user_fields { + $this->exclude = array_merge($this->exclude, $exclude); + return $this; + } + + /** + * Gets an array of all fields that are required for the specified purposes, also taking + * into account the $includes and $excludes settings. + * + * The results may include basic field names (columns from the 'user' database table) and, + * unless turned off, custom profile field names in the format 'profile_field_myfield'. + * + * You should not rely on the order of fields, with one exception: if there is an id field + * it will be returned first. This is in case it is used with get_records calls. + * + * The $limitpurposes parameter is useful if you want to get a different set of fields than the + * purposes in the constructor. For example, if you want to get SQL for identity + user picture + * fields, but you then want to only get the identity fields as a list. (You can only specify + * purposes that were also passed to the constructor i.e. it can only be used to restrict the + * list, not add to it.) + * + * @param array $limitpurposes If specified, gets fields only for these purposes + * @return string[] Array of required fields + * @throws \coding_exception If any unknown purpose is listed + */ + public function get_required_fields(array $limitpurposes = []): array { + // The first time this is called, actually work out the list. There is no way to 'un-cache' + // it, but these objects are designed to be short-lived so it doesn't need one. + if ($this->fields === null) { + // Add all the fields as array keys so that there are no duplicates. + $this->fields = []; + if ($this->purposes[self::PURPOSE_IDENTITY]) { + foreach (self::get_identity_fields($this->context, $this->allowcustom) as $field) { + $this->fields[$field] = [self::PURPOSE_IDENTITY => true]; + } + } + if ($this->purposes[self::PURPOSE_USERPIC]) { + foreach (self::get_picture_fields() as $field) { + if (!array_key_exists($field, $this->fields)) { + $this->fields[$field] = []; + } + $this->fields[$field][self::PURPOSE_USERPIC] = true; + } + } + if ($this->purposes[self::PURPOSE_NAME]) { + foreach (self::get_name_fields() as $field) { + if (!array_key_exists($field, $this->fields)) { + $this->fields[$field] = []; + } + $this->fields[$field][self::PURPOSE_NAME] = true; + } + } + foreach ($this->include as $field) { + if ($this->allowcustom || !preg_match(self::PROFILE_FIELD_REGEX, $field)) { + if (!array_key_exists($field, $this->fields)) { + $this->fields[$field] = []; + } + $this->fields[$field][self::CUSTOM_INCLUDE] = true; + } + } + foreach ($this->exclude as $field) { + unset($this->fields[$field]); + } + + // If the id field is included, make sure it's first in the list. + if (array_key_exists('id', $this->fields)) { + $newfields = ['id' => $this->fields['id']]; + foreach ($this->fields as $field => $purposes) { + if ($field !== 'id') { + $newfields[$field] = $purposes; + } + } + $this->fields = $newfields; + } + } + + if ($limitpurposes) { + // Check the value was legitimate. + foreach ($limitpurposes as $purpose) { + if ($purpose != self::CUSTOM_INCLUDE && empty($this->purposes[$purpose])) { + throw new \coding_exception('$limitpurposes can only include purposes defined in object'); + } + } + + // Filter the fields to include only those matching the purposes. + $result = []; + foreach ($this->fields as $key => $purposes) { + foreach ($limitpurposes as $purpose) { + if (array_key_exists($purpose, $purposes)) { + $result[] = $key; + break; + } + } + } + return $result; + } else { + return array_keys($this->fields); + } + } + + /** + * Gets fields required for user pictures. + * + * The results include only basic field names (columns from the 'user' database table). + * + * @return string[] All fields required for user pictures + */ + public static function get_picture_fields(): array { + return ['id', 'picture', 'firstname', 'lastname', 'firstnamephonetic', 'lastnamephonetic', + 'middlename', 'alternatename', 'imagealt', 'email']; + } + + /** + * Gets fields required for user names. + * + * The results include only basic field names (columns from the 'user' database table). + * + * Fields are usually returned in a specific order, which the fullname() function depends on. + * If you specify 'true' to the $strangeorder flag, then the firstname and lastname fields + * are moved to the front; this is useful in a few places in existing code. New code should + * avoid requiring a particular order. + * + * @param bool $differentorder In a few places, a different order of fields is required + * @return string[] All fields used to display user names + */ + public static function get_name_fields(bool $differentorder = false): array { + $fields = ['firstnamephonetic', 'lastnamephonetic', 'middlename', 'alternatename', + 'firstname', 'lastname']; + if ($differentorder) { + return array_merge(array_slice($fields, -2), array_slice($fields, 0, -2)); + } else { + return $fields; + } + } + + /** + * Gets all fields required for user identity. These fields should be included in tables + * showing lists of users (in addition to the user's name which is included as standard). + * + * The results include basic field names (columns from the 'user' database table) and, unless + * turned off, custom profile field names in the format 'profile_field_myfield'. + * + * This function does all the required capability checks to see if the current user is allowed + * to see them in the specified context. You can pass context null to get all the fields + * without checking permissions. + * + * @param \context|null $context Context; if not supplied, all fields will be included without checks + * @param bool $allowcustom If true, custom profile fields will be included + * @return string[] Array of required fields + * @throws \coding_exception + */ + public static function get_identity_fields(?\context $context, bool $allowcustom = true): array { + global $CFG; + + // Only users with permission get the extra fields. + if ($context && !has_capability('moodle/site:viewuseridentity', $context)) { + return []; + } + + // Split showuseridentity on comma (filter needed in case the showuseridentity is empty). + $extra = array_filter(explode(',', $CFG->showuseridentity)); + + // If there are any custom fields, remove them if necessary (either if allowcustom is false, + // or if the user doesn't have access to see them). + foreach ($extra as $key => $field) { + if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) { + if ($allowcustom) { + require_once($CFG->dirroot . '/user/profile/lib.php'); + $fieldinfo = profile_get_custom_field_data_by_shortname($matches[1]); + switch ($fieldinfo['visible']) { + case PROFILE_VISIBLE_NONE: + case PROFILE_VISIBLE_PRIVATE: + $allowed = !$context || has_capability('moodle/user:viewalldetails', $context); + break; + case PROFILE_VISIBLE_ALL: + $allowed = true; + break; + } + } else { + $allowed = false; + } + if (!$allowed) { + unset($extra[$key]); + } + } + } + + // For standard user fields, access is controlled by the hiddenuserfields option and + // some different capabilities. Check and remove these if the user can't access them. + $hiddenfields = array_filter(explode(',', $CFG->hiddenuserfields)); + $hiddenidentifiers = array_intersect($extra, $hiddenfields); + + if ($hiddenidentifiers) { + if (!$context) { + $canviewhiddenuserfields = true; + } else if ($context->get_course_context(false)) { + // We are somewhere inside a course. + $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context); + } else { + // We are not inside a course. + $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context); + } + + if (!$canviewhiddenuserfields) { + // Remove hidden identifiers from the list. + $extra = array_diff($extra, $hiddenidentifiers); + } + } + + // Re-index the entries and return. + $extra = array_values($extra); + return $extra; + } + + /** + * Gets SQL that can be used in a query to get the necessary fields. + * + * The result of this function is an object with fields 'selects', 'joins', 'params', and + * 'mappings'. + * + * If not empty, the list of selects will begin with a comma and the list of joins will begin + * and end with a space. You can include the result in your existing query like this: + * + * SELECT (your existing fields) + * $selects + * FROM {user} u + * JOIN (your existing joins) + * $joins + * + * When there are no custom fields then the 'joins' result will always be an empty string, and + * 'params' will be an empty array. + * + * The $fieldmappings value is often not needed. It is an associative array from each field + * name to an SQL expression for the value of that field, e.g.: + * 'profile_field_frog' => 'uf1d_3.data' + * 'city' => 'u.city' + * This is helpful if you want to use the profile fields in a WHERE clause, becuase you can't + * refer to the aliases used in the SELECT list there. + * + * The leading comma is included because this makes it work in the pattern above even if there + * are no fields from the user_fields data (which can happen if doing identity fields and none + * are selected). If you want the result without a leading comma, set $leadingcomma to false. + * + * If the 'id' field is included then it will always be first in the list. Otherwise, you + * should not rely on the field order. + * + * For identity fields, the function does all the required capability checks to see if the + * current user is allowed to see them in the specified context. You can pass context null + * to get all the fields without checking permissions. + * + * If your code for any reason cannot cope with custom fields then you can turn them off. + * + * You can have either named or ? params. If you use named params, they are of the form + * uf1s_2; the first number increments in each call using a static variable in this class and + * the second number refers to the field being queried. A similar pattern is used to make + * join aliases unique. + * + * If your query refers to the user table by an alias e.g. 'u' then specify this in the $alias + * parameter; otherwise it will use {user} (if there are any joins for custom profile fields) + * or simply refer to the field by name only (if there aren't). + * + * If you need to use a prefix on the field names (for example in case they might coincide with + * existing result columns from your query, or if you want a convenient way to split out all + * the user data into a separate object) then you can specify one here. For example, if you + * include name fields and the prefix is 'u_' then the results will include 'u_firstname'. + * + * If you don't want to prefix all the field names but only change the id field name, use + * the $renameid parameter. (When you use this parameter, it takes precedence over any prefix; + * the id field will not be prefixed, while all others will.) + * + * @param string $alias Optional (but recommended) alias for user table in query, e.g. 'u' + * @param bool $namedparams If true, uses named :parameters instead of indexed ? parameters + * @param string $prefix Optional prefix for all field names in result, e.g. 'u_' + * @param string $renameid Renames the 'id' field if specified, e.g. 'userid' + * @param bool $leadingcomma If true the 'selects' list will start with a comma + * @return \stdClass Object with necessary SQL components + */ + public function get_sql(string $alias = '', bool $namedparams = false, string $prefix = '', + string $renameid = '', bool $leadingcomma = true): \stdClass { + global $DB; + + $fields = $this->get_required_fields(); + + $selects = ''; + $joins = ''; + $params = []; + $mappings = []; + + $unique = self::$uniqueidentifier++; + $fieldcount = 0; + + if ($alias) { + $usertable = $alias . '.'; + } else { + // If there is no alias, we still need to use {user} to identify the table when there + // are joins with other tables. When there are no customfields then there are no joins + // so we can refer to the fields by name alone. + $gotcustomfields = false; + foreach ($fields as $field) { + if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) { + $gotcustomfields = true; + break; + } + } + if ($gotcustomfields) { + $usertable = '{user}.'; + } else { + $usertable = ''; + } + } + + foreach ($fields as $field) { + if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) { + // Custom profile field. + $shortname = $matches[1]; + + $fieldcount++; + + $fieldalias = 'uf' . $unique . 'f_' . $fieldcount; + $dataalias = 'uf' . $unique . 'd_' . $fieldcount; + if ($namedparams) { + $withoutcolon = 'uf' . $unique . 's' . $fieldcount; + $placeholder = ':' . $withoutcolon; + $params[$withoutcolon] = $shortname; + } else { + $placeholder = '?'; + $params[] = $shortname; + } + $joins .= " JOIN {user_info_field} $fieldalias ON $fieldalias.shortname = $placeholder + LEFT JOIN {user_info_data} $dataalias ON $dataalias.fieldid = $fieldalias.id + AND $dataalias.userid = {$usertable}id"; + // For Oracle we need to convert the field into a usable format. + $fieldsql = $DB->sql_compare_text($dataalias . '.data', 255); + $selects .= ", $fieldsql AS $prefix$field"; + $mappings[$field] = $fieldsql; + } else { + // Standard user table field. + $selects .= ", $usertable$field"; + if ($field === 'id' && $renameid && $renameid !== 'id') { + $selects .= " AS $renameid"; + } else if ($prefix) { + $selects .= " AS $prefix$field"; + } + $mappings[$field] = "$usertable$field"; + } + } + + // Add a space to the end of the joins list; this means it can be appended directly into + // any existing query without worrying about whether the developer has remembered to add + // whitespace after it. + if ($joins) { + $joins .= ' '; + } + + // Optionally remove the leading comma. + if (!$leadingcomma) { + $selects = ltrim($selects, ' ,'); + } + + return (object)['selects' => $selects, 'joins' => $joins, 'params' => $params, + 'mappings' => $mappings]; + } + + /** + * Gets the display name of a given user field. + * + * Supports field names from the 'user' database table, and custom profile fields supplied in + * the format 'profile_field_xx'. + * + * @param string $field Field name in database + * @return string Field name for display to user + * @throws \coding_exception + */ + public static function get_display_name(string $field): string { + global $CFG; + + // Custom fields have special handling. + if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) { + require_once($CFG->dirroot . '/user/profile/lib.php'); + $fieldinfo = profile_get_custom_field_data_by_shortname($matches[1]); + // Use format_string so it can be translated with multilang filter if necessary. + return format_string($fieldinfo['name']); + } + + // Some fields have language strings which are not the same as field name. + switch ($field) { + case 'url' : { + return get_string('webpage'); + } + case 'icq' : { + return get_string('icqnumber'); + } + case 'skype' : { + return get_string('skypeid'); + } + case 'aim' : { + return get_string('aimid'); + } + case 'yahoo' : { + return get_string('yahooid'); + } + case 'msn' : { + return get_string('msnid'); + } + case 'picture' : { + return get_string('pictureofuser'); + } + } + // Otherwise just use the same lang string. + return get_string($field); + } + + /** + * Resets the unique identifier used to ensure that multiple SQL fragments generated in the + * same request will have different identifiers for parameters and table aliases. + * + * This is intended only for use in unit testing. + */ + public static function reset_unique_identifier() { + self::$uniqueidentifier = 1; + } + + /** + * Checks if a field name looks like a custom profile field i.e. it begins with profile_field_ + * (does not check if that profile field actually exists). + * + * @param string $fieldname Field name + * @return string Empty string if not a profile field, or profile field name (without profile_field_) + */ + public static function match_custom_field(string $fieldname): string { + if (preg_match(self::PROFILE_FIELD_REGEX, $fieldname, $matches)) { + return $matches[1]; + } else { + return ''; + } + } +} diff --git a/lib/moodlelib.php b/lib/moodlelib.php index bbadbf47617..1b8416693e1 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -3755,6 +3755,8 @@ function order_in_string($values, $stringformat) { /** * Checks if current user is shown any extra fields when listing users. * + * Does not include any custom profile fields. + * * @param object $context Context * @param array $already Array of fields that we're going to show anyway * so don't bother listing them @@ -3762,46 +3764,8 @@ function order_in_string($values, $stringformat) { * listed in $already */ function get_extra_user_fields($context, $already = array()) { - global $CFG; - - // Only users with permission get the extra fields. - if (!has_capability('moodle/site:viewuseridentity', $context)) { - return array(); - } - - // Split showuseridentity on comma (filter needed in case the showuseridentity is empty). - $extra = array_filter(explode(',', $CFG->showuseridentity)); - - foreach ($extra as $key => $field) { - if (in_array($field, $already)) { - unset($extra[$key]); - } - } - - // If the identity fields are also among hidden fields, make sure the user can see them. - $hiddenfields = array_filter(explode(',', $CFG->hiddenuserfields)); - $hiddenidentifiers = array_intersect($extra, $hiddenfields); - - if ($hiddenidentifiers) { - if ($context->get_course_context(false)) { - // We are somewhere inside a course. - $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context); - - } else { - // We are not inside a course. - $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context); - } - - if (!$canviewhiddenuserfields) { - // Remove hidden identifiers from the list. - $extra = array_diff($extra, $hiddenidentifiers); - } - } - - // Re-index the entries. - $extra = array_values($extra); - - return $extra; + $fields = new \core\user_fields([\core\user_fields::PURPOSE_IDENTITY], [], $already); + return $fields->get_required_fields($context, false); } /** @@ -3809,6 +3773,8 @@ function get_extra_user_fields($context, $already = array()) { * selecting users, returns a string suitable for including in an SQL select * clause to retrieve those fields. * + * Does not include any custom profile fields. + * * @param context $context Context * @param string $alias Alias of user table, e.g. 'u' (default none) * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none) @@ -3816,53 +3782,28 @@ function get_extra_user_fields($context, $already = array()) { * @return string Partial SQL select clause, beginning with comma, for example ',u.idnumber,u.department' unless it is blank */ function get_extra_user_fields_sql($context, $alias='', $prefix='', $already = array()) { - $fields = get_extra_user_fields($context, $already); - $result = ''; - // Add punctuation for alias. - if ($alias !== '') { - $alias .= '.'; + $fields = new \core\user_fields([\core\user_fields::PURPOSE_IDENTITY], [], $already); + // Note: $joins and $joinparams will always be empty because we turned off profile fields. + [$selects, $joins, $joinparams] = $fields->get_sql($context, false, false, $alias, $prefix); + + if ($alias === '') { + // The new code puts {user}. in front of the field names while the old code didn't. + $selects = str_replace('{user}.', '', $selects); } - foreach ($fields as $field) { - $result .= ', ' . $alias . $field; - if ($prefix) { - $result .= ' AS ' . $prefix . $field; - } - } - return $result; + + return $selects; } /** * Returns the display name of a field in the user table. Works for most fields that are commonly displayed to users. + * + * Also works for custom fields. + * * @param string $field Field name, e.g. 'phone1' * @return string Text description taken from language file, e.g. 'Phone number' */ function get_user_field_name($field) { - // Some fields have language strings which are not the same as field name. - switch ($field) { - case 'url' : { - return get_string('webpage'); - } - case 'icq' : { - return get_string('icqnumber'); - } - case 'skype' : { - return get_string('skypeid'); - } - case 'aim' : { - return get_string('aimid'); - } - case 'yahoo' : { - return get_string('yahooid'); - } - case 'msn' : { - return get_string('msnid'); - } - case 'picture' : { - return get_string('pictureofuser'); - } - } - // Otherwise just use the same lang string. - return get_string($field); + return \core\user_fields::get_display_name($field); } /** diff --git a/lib/tests/behat/showuseridentity.feature b/lib/tests/behat/showuseridentity.feature new file mode 100644 index 00000000000..a475ec67f19 --- /dev/null +++ b/lib/tests/behat/showuseridentity.feature @@ -0,0 +1,70 @@ +@core +Feature: Select user identity fields + In order to see who users are at my institution + As an administrator + I can configure which user fields show with lists of users + + Background: + Given the following "custom profile fields" exist: + | datatype | shortname | name | param2 | + | text | speciality | Speciality | 255 | + | checkbox | fool | Foolish | | + | text | thesis | Thesis | 100000 | + And the following "users" exist: + | username | department | profile_field_speciality | email | + | user1 | Amphibians | Frogs | email1@example.org | + | user2 | Undead | Zombies | email2@example.org | + And the following "courses" exist: + | shortname | fullname | + | C1 | Course 1 | + And the following "course enrolments" exist: + | user | course | role | + | user1 | C1 | manager | + | user2 | C1 | manager | + + Scenario: The admin settings screen should show text custom fields (and let you choose them) + When I log in as "admin" + And I navigate to "Users > Permissions > User policies" in site administration + Then I should see "Speciality" in the "#admin-showuseridentity" "css_element" + And I should not see "Foolish" in the "#admin-showuseridentity" "css_element" + And I should not see "Thesis" in the "#admin-showuseridentity" "css_element" + And I set the field "Speciality" to "1" + And I press "Save changes" + And the field "Speciality" matches value "1" + + Scenario: When you choose custom fields, these should be displayed in the 'Browse list of users' screen + Given the following config values are set as admin: + | showuseridentity | username,department,profile_field_speciality | + When I log in as "admin" + And I navigate to "Users > Accounts > Browse list of users" in site administration + Then I should see "Speciality" in the "thead" "css_element" + And I should see "Department" in the "thead" "css_element" + And I should not see "Email" in the "thead" "css_element" + Then I should see "Amphibians" in the "user1" "table_row" + And I should see "Frogs" in the "user1" "table_row" + And I should not see "email1@example.org" + And I should see "Undead" in the "user2" "table_row" + And I should see "Zombies" in the "user2" "table_row" + And I should not see "email2@example.org" + + Scenario: When you choose custom fields, these should be displayed in the 'Participants' screen + Given the following config values are set as admin: + | showuseridentity | username,department,profile_field_speciality | + When I am on the "C1" "Course" page logged in as "user1" + And I navigate to course participants + Then I should see "Frogs" in the "user1" "table_row" + And I should see "Zombies" in the "user2" "table_row" + + @javascript + Scenario: The user filtering options on the participants screen should work for custom profile fields + Given the following config values are set as admin: + | showuseridentity | username,department,profile_field_speciality | + When I am on the "C1" "Course" page logged in as "admin" + And I navigate to course participants + And I set the field "type" in the "Filter 1" "fieldset" to "Keyword" + And I set the field "Type..." in the "Filter 1" "fieldset" to "Frogs" + # You have to tab out to make it actually apply. + And I press tab + And I click on "Apply filters" "button" + Then I should see "user1" in the "participants" "table" + And I should not see "user2" in the "participants" "table" diff --git a/lib/tests/user_fields_test.php b/lib/tests/user_fields_test.php new file mode 100644 index 00000000000..c022e2a2253 --- /dev/null +++ b/lib/tests/user_fields_test.php @@ -0,0 +1,511 @@ +. + +namespace core; + +/** + * Unit tests for \core\user_fields + * + * @package core + * @copyright 2014 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_fields_testcase extends \advanced_testcase { + + /** + * Tests getting the user picture fields. + */ + public function test_get_picture_fields() { + $this->assertEquals(['id', 'picture', 'firstname', 'lastname', 'firstnamephonetic', + 'lastnamephonetic', 'middlename', 'alternatename', 'imagealt', 'email'], + user_fields::get_picture_fields()); + } + + /** + * Tests getting the user name fields. + */ + public function test_get_name_fields() { + $this->assertEquals(['firstnamephonetic', 'lastnamephonetic', 'middlename', 'alternatename', + 'firstname', 'lastname'], + user_fields::get_name_fields()); + + $this->assertEquals(['firstname', 'lastname', + 'firstnamephonetic', 'lastnamephonetic', 'middlename', 'alternatename'], + user_fields::get_name_fields(true)); + } + + /** + * Tests getting the identity fields. + */ + public function test_get_identity_fields() { + global $DB; + + $this->resetAfterTest(); + + // Create two custom profile fields, one of which is private. + $generator = self::getDataGenerator(); + $generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A']); + $generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'b', 'name' => 'B', + 'visible' => PROFILE_VISIBLE_PRIVATE]); + + // Set the extra user fields to include email, department, and both custom profile fields. + set_config('showuseridentity', 'email,department,profile_field_a,profile_field_b'); + set_config('hiddenuserfields', 'email'); + + // Create a test course and a student in the course. + $course = $generator->create_course(); + $coursecontext = \context_course::instance($course->id); + $user = $generator->create_user(); + $anotheruser = $generator->create_user(); + $usercontext = \context_user::instance($anotheruser->id); + $generator->enrol_user($user->id, $course->id, 'student'); + + // When no context is provided, it does no access checks and should return all specified. + $this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b'], + user_fields::get_identity_fields(null)); + + // If you turn off custom profile fields, you don't get those. + $this->assertEquals(['email', 'department'], user_fields::get_identity_fields(null, false)); + + // Request in context as an administator. + $this->setAdminUser(); + $this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b'], + user_fields::get_identity_fields($coursecontext)); + $this->assertEquals(['email', 'department'], + user_fields::get_identity_fields($coursecontext, false)); + + // Request in context as a student - they don't have any of the capabilities to see identity + // fields or profile fields. + $this->setUser($user); + $this->assertEquals([], user_fields::get_identity_fields($coursecontext)); + + // Give the student the basic identity fields permission. + $roleid = $DB->get_field('role', 'id', ['shortname' => 'student']); + role_change_permission($roleid, $coursecontext, 'moodle/site:viewuseridentity', CAP_ALLOW); + $this->assertEquals(['department', 'profile_field_a'], + user_fields::get_identity_fields($coursecontext)); + $this->assertEquals(['department'], + user_fields::get_identity_fields($coursecontext, false)); + + // Give them permission to view hidden user fields. + role_change_permission($roleid, $coursecontext, 'moodle/course:viewhiddenuserfields', CAP_ALLOW); + $this->assertEquals(['email', 'department', 'profile_field_a'], + user_fields::get_identity_fields($coursecontext)); + $this->assertEquals(['email', 'department'], + user_fields::get_identity_fields($coursecontext, false)); + + // Also give them permission to view all profile fields. + role_change_permission($roleid, $coursecontext, 'moodle/user:viewalldetails', CAP_ALLOW); + $this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b'], + user_fields::get_identity_fields($coursecontext)); + $this->assertEquals(['email', 'department'], + user_fields::get_identity_fields($coursecontext, false)); + + // Even if we give them student role in the user context they can't view anything... + $generator->role_assign($roleid, $user->id, $usercontext->id); + $this->assertEquals([], user_fields::get_identity_fields($usercontext)); + + // Give them basic permission. + role_change_permission($roleid, $usercontext, 'moodle/site:viewuseridentity', CAP_ALLOW); + $this->assertEquals(['department', 'profile_field_a'], + user_fields::get_identity_fields($usercontext)); + $this->assertEquals(['department'], + user_fields::get_identity_fields($usercontext, false)); + + // Give them the hidden user fields permission (it's a different one). + role_change_permission($roleid, $usercontext, 'moodle/user:viewhiddendetails', CAP_ALLOW); + $this->assertEquals(['email', 'department', 'profile_field_a'], + user_fields::get_identity_fields($usercontext)); + $this->assertEquals(['email', 'department'], + user_fields::get_identity_fields($usercontext, false)); + + // Also give them permission to view all profile fields. + role_change_permission($roleid, $usercontext, 'moodle/user:viewalldetails', CAP_ALLOW); + $this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b'], + user_fields::get_identity_fields($usercontext)); + $this->assertEquals(['email', 'department'], + user_fields::get_identity_fields($usercontext, false)); + } + + /** + * Tests the get_required_fields function. + * + * This function composes the results of get_identity/name/picture_fields, so we are not going + * to test the details of the identity permissions as that was already covered. Just how they + * are included/combined. + */ + public function test_get_required_fields() { + $this->resetAfterTest(); + + // Set up some profile fields. + $generator = self::getDataGenerator(); + $generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A']); + $generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'b', 'name' => 'B']); + set_config('showuseridentity', 'email,department,profile_field_a'); + + // What happens if you don't ask for anything? + $fields = user_fields::empty(); + $this->assertEquals([], $fields->get_required_fields()); + + // Try each invidual purpose. + $fields = user_fields::for_identity(null); + $this->assertEquals(['email', 'department', 'profile_field_a'], $fields->get_required_fields()); + $fields = user_fields::for_userpic(); + $this->assertEquals(user_fields::get_picture_fields(), $fields->get_required_fields()); + $fields = user_fields::for_name(); + $this->assertEquals(user_fields::get_name_fields(), $fields->get_required_fields()); + + // Try combining them all. There should be no duplicates (e.g. email), and the 'id' field + // should be moved to the start. + $fields = user_fields::for_identity(null)->with_name()->with_userpic(); + $this->assertEquals(['id', 'email', 'department', 'profile_field_a', 'picture', + 'firstname', 'lastname', 'firstnamephonetic', 'lastnamephonetic', 'middlename', + 'alternatename', 'imagealt'], $fields->get_required_fields()); + + // Add some specified fields to a default result. + $fields = user_fields::for_identity(null, true)->including('city', 'profile_field_b'); + $this->assertEquals(['email', 'department', 'profile_field_a', 'city', 'profile_field_b'], + $fields->get_required_fields()); + + // Remove some fields, one of which actually is in the list. + $fields = user_fields::for_identity(null, true)->excluding('email', 'city'); + $this->assertEquals(['department', 'profile_field_a'], $fields->get_required_fields()); + + // Add and remove fields. + $fields = user_fields::for_identity(null, true)->including('city', 'profile_field_b')->excluding('city', 'department'); + $this->assertEquals(['email', 'profile_field_a', 'profile_field_b'], + $fields->get_required_fields()); + + // Request the list without profile fields, check that still works with both sources. + $fields = user_fields::for_identity(null, false)->including('city', 'profile_field_b')->excluding('city', 'department'); + $this->assertEquals(['email'], $fields->get_required_fields()); + } + + /** + * Tests the get_required_fields function when you use the $limitpurposes parameter. + */ + public function test_get_required_fields_limitpurposes() { + $this->resetAfterTest(); + + // Set up some profile fields. + $generator = self::getDataGenerator(); + $generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A']); + $generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'b', 'name' => 'B']); + set_config('showuseridentity', 'email,department,profile_field_a'); + + // Create a user_fields object with all three purposes, plus included and excluded fields. + $fields = user_fields::for_identity(null, true)->with_name()->with_userpic() + ->including('city', 'profile_field_b')->excluding('firstnamephonetic', 'middlename', 'alternatename'); + + // Check the result with all purposes. + $this->assertEquals(['id', 'email', 'department', 'profile_field_a', 'picture', + 'firstname', 'lastname', 'lastnamephonetic', 'imagealt', 'city', + 'profile_field_b'], + $fields->get_required_fields([user_fields::PURPOSE_IDENTITY, user_fields::PURPOSE_NAME, + user_fields::PURPOSE_USERPIC, user_fields::CUSTOM_INCLUDE])); + + // Limit to identity and custom includes. + $this->assertEquals(['email', 'department', 'profile_field_a', 'city', 'profile_field_b'], + $fields->get_required_fields([user_fields::PURPOSE_IDENTITY, user_fields::CUSTOM_INCLUDE])); + + // Limit to name fields. + $this->assertEquals(['firstname', 'lastname', 'lastnamephonetic'], + $fields->get_required_fields([user_fields::PURPOSE_NAME])); + } + + /** + * There should be an exception if you try to 'limit' purposes to one that wasn't even included. + */ + public function test_get_required_fields_limitpurposes_not_in_constructor() { + $fields = user_fields::for_identity(null); + $this->expectExceptionMessage('$limitpurposes can only include purposes defined in object'); + $fields->get_required_fields([user_fields::PURPOSE_USERPIC]); + } + + /** + * Sets up data and a user_fields object for all the get_sql tests. + * + * @return user_fields Constructed user_fields for testing + */ + protected function init_for_sql_tests(): user_fields { + $generator = self::getDataGenerator(); + $generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A']); + $generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'b', 'name' => 'B']); + + // Create a couple of users. One doesn't have a profile field set, so we can test that. + $generator->create_user(['profile_field_a' => 'A1', 'profile_field_b' => 'B1', + 'city' => 'C1', 'department' => 'D1', 'email' => 'e1@example.org', + 'idnumber' => 'XXX1', 'username' => 'u1']); + $generator->create_user(['profile_field_a' => 'A2', + 'city' => 'C2', 'department' => 'D2', 'email' => 'e2@example.org', + 'idnumber' => 'XXX2', 'username' => 'u2']); + + // It doesn't matter how we construct it (we already tested get_required_fields which is + // where all those values are actually used) so let's just list the fields we want manually. + return user_fields::empty()->including('department', 'city', 'profile_field_a', 'profile_field_b'); + } + + /** + * Tests getting SQL (and actually using it). + */ + public function test_get_sql_variations() { + global $DB; + $this->resetAfterTest(); + + $fields = $this->init_for_sql_tests(); + user_fields::reset_unique_identifier(); + + // Basic SQL. + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams, 'mappings' => $mappings] = + (array)$fields->get_sql(); + $sql = "SELECT idnumber + $selects + FROM {user} + $joins + WHERE idnumber LIKE ? + ORDER BY idnumber"; + $records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%'])); + $this->assertCount(2, $records); + $expected1 = (object)['profile_field_a' => 'A1', 'profile_field_b' => 'B1', + 'city' => 'C1', 'department' => 'D1', 'idnumber' => 'XXX1']; + $expected2 = (object)['profile_field_a' => 'A2', 'profile_field_b' => null, + 'city' => 'C2', 'department' => 'D2', 'idnumber' => 'XXX2']; + $this->assertEquals($expected1, $records['XXX1']); + $this->assertEquals($expected2, $records['XXX2']); + + $this->assertEquals([ + 'department' => '{user}.department', + 'city' => '{user}.city', + 'profile_field_a' => $DB->sql_compare_text('uf1d_1.data', 255), + 'profile_field_b' => $DB->sql_compare_text('uf1d_2.data', 255)], $mappings); + + // SQL using named params. + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] = + (array)$fields->get_sql('', true); + $sql = "SELECT idnumber + $selects + FROM {user} + $joins + WHERE idnumber LIKE :idnum + ORDER BY idnumber"; + $records = $DB->get_records_sql($sql, array_merge($joinparams, ['idnum' => 'X%'])); + $this->assertCount(2, $records); + $this->assertEquals($expected1, $records['XXX1']); + $this->assertEquals($expected2, $records['XXX2']); + + // SQL using alias for user table. + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams, 'mappings' => $mappings] = + (array)$fields->get_sql('u'); + $sql = "SELECT idnumber + $selects + FROM {user} u + $joins + WHERE idnumber LIKE ? + ORDER BY idnumber"; + $records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%'])); + $this->assertCount(2, $records); + $this->assertEquals($expected1, $records['XXX1']); + $this->assertEquals($expected2, $records['XXX2']); + + $this->assertEquals([ + 'department' => 'u.department', + 'city' => 'u.city', + 'profile_field_a' => $DB->sql_compare_text('uf3d_1.data', 255), + 'profile_field_b' => $DB->sql_compare_text('uf3d_2.data', 255)], $mappings); + + // Returning prefixed fields. + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] = + (array)$fields->get_sql('', false, 'u_'); + $sql = "SELECT idnumber + $selects + FROM {user} + $joins + WHERE idnumber LIKE ? + ORDER BY idnumber"; + $records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%'])); + $this->assertCount(2, $records); + $expected1 = (object)['u_profile_field_a' => 'A1', 'u_profile_field_b' => 'B1', + 'u_city' => 'C1', 'u_department' => 'D1', 'idnumber' => 'XXX1']; + $this->assertEquals($expected1, $records['XXX1']); + + // Renaming the id field. We need to use a different set of fields so it actually has the + // id field. + $fields = user_fields::for_userpic(); + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] = + (array)$fields->get_sql('', false, '', 'userid'); + $sql = "SELECT idnumber + $selects + FROM {user} + $joins + WHERE idnumber LIKE ? + ORDER BY idnumber"; + $records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%'])); + $this->assertCount(2, $records); + + // User id was renamed. + $this->assertObjectNotHasAttribute('id', $records['XXX1']); + $this->assertObjectHasAttribute('userid', $records['XXX1']); + + // Other fields are normal (just try a couple). + $this->assertObjectHasAttribute('firstname', $records['XXX1']); + $this->assertObjectHasAttribute('imagealt', $records['XXX1']); + + // Check the user id is actually right. + $this->assertEquals('XXX1', + $DB->get_field('user', 'idnumber', ['id' => $records['XXX1']->userid])); + + // Rename the id field and also use a prefix. + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] = + (array)$fields->get_sql('', false, 'u_', 'userid'); + $sql = "SELECT idnumber + $selects + FROM {user} + $joins + WHERE idnumber LIKE ? + ORDER BY idnumber"; + $records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%'])); + $this->assertCount(2, $records); + + // User id was renamed. + $this->assertObjectNotHasAttribute('id', $records['XXX1']); + $this->assertObjectNotHasAttribute('u_id', $records['XXX1']); + $this->assertObjectHasAttribute('userid', $records['XXX1']); + + // Other fields are prefixed (just try a couple). + $this->assertObjectHasAttribute('u_firstname', $records['XXX1']); + $this->assertObjectHasAttribute('u_imagealt', $records['XXX1']); + + // Without a leading comma. + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] = + (array)$fields->get_sql('', false, '', '', false); + $sql = "SELECT $selects + FROM {user} + $joins + WHERE idnumber LIKE ? + ORDER BY idnumber"; + $records = $DB->get_records_sql($sql, array_merge($joinparams, ['X%'])); + $this->assertCount(2, $records); + foreach ($records as $key => $record) { + // ID should be the first field used by get_records_sql. + $this->assertEquals($key, $record->id); + // Check 2 other sample properties. + $this->assertObjectHasAttribute('firstname', $record); + $this->assertObjectHasAttribute('imagealt', $record); + } + } + + /** + * Tests what happens if you use the SQL multiple times in a query (i.e. that it correctly + * creates the different identifiers). + */ + public function test_get_sql_multiple() { + global $DB; + $this->resetAfterTest(); + + $fields = $this->init_for_sql_tests(); + + // Inner SQL. + ['selects' => $selects1, 'joins' => $joins1, 'params' => $joinparams1] = + (array)$fields->get_sql('u1', true); + // Outer SQL. + $fields2 = user_fields::empty()->including('profile_field_a', 'email'); + ['selects' => $selects2, 'joins' => $joins2, 'params' => $joinparams2] = + (array)$fields2->get_sql('u2', true); + + // Crazy combined query. + $sql = "SELECT username, details.profile_field_b AS innerb, details.city AS innerc + $selects2 + FROM {user} u2 + $joins2 + LEFT JOIN ( + SELECT u1.id + $selects1 + FROM {user} u1 + $joins1 + WHERE idnumber LIKE :idnum + ) details ON details.id = u2.id + ORDER BY username"; + $records = $DB->get_records_sql($sql, array_merge($joinparams1, $joinparams2, ['idnum' => 'X%'])); + // The left join won't match for admin. + $this->assertNull($records['admin']->innerb); + $this->assertNull($records['admin']->innerc); + // It should match for one of the test users though. + $expected1 = (object)['username' => 'u1', 'innerb' => 'B1', 'innerc' => 'C1', + 'profile_field_a' => 'A1', 'email' => 'e1@example.org']; + $this->assertEquals($expected1, $records['u1']); + } + + /** + * Tests the get_sql function when there are no fields to retrieve. + */ + public function test_get_sql_nothing() { + $fields = user_fields::empty(); + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] = (array)$fields->get_sql(); + $this->assertEquals('', $selects); + $this->assertEquals('', $joins); + $this->assertEquals([], $joinparams); + } + + /** + * Tests get_sql when there are no custom fields; in this scenario, the joins and joinparams + * are always blank. + */ + public function test_get_sql_no_custom_fields() { + $fields = user_fields::empty()->including('city', 'country'); + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams, 'mappings' => $mappings] = + (array)$fields->get_sql('u'); + $this->assertEquals(', u.city, u.country', $selects); + $this->assertEquals('', $joins); + $this->assertEquals([], $joinparams); + $this->assertEquals(['city' => 'u.city', 'country' => 'u.country'], $mappings); + } + + /** + * Tests the format of the $selects string, which is important particularly for backward + * compatibility. + */ + public function test_get_sql_selects_format() { + global $DB; + + $this->resetAfterTest(); + user_fields::reset_unique_identifier(); + + $generator = self::getDataGenerator(); + $generator->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'a', 'name' => 'A']); + + // When we list fields that include custom profile fields... + $fields = user_fields::empty()->including('id', 'profile_field_a'); + + // Supplying an alias: all fields have alias. + $selects = $fields->get_sql('u')->selects; + $this->assertEquals(', u.id, ' . $DB->sql_compare_text('uf1d_1.data', 255) . ' AS profile_field_a', $selects); + + // No alias: all files have {user} because of the joins. + $selects = $fields->get_sql()->selects; + $this->assertEquals(', {user}.id, ' . $DB->sql_compare_text('uf2d_1.data', 255) . ' AS profile_field_a', $selects); + + // When the list doesn't include custom profile fields... + $fields = user_fields::empty()->including('id', 'city'); + + // Supplying an alias: all fields have alias. + $selects = $fields->get_sql('u')->selects; + $this->assertEquals(', u.id, u.city', $selects); + + // No alias: fields do not have alias at all. + $selects = $fields->get_sql()->selects; + $this->assertEquals(', id, city', $selects); + } +} diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 6fdc886ab4d..71d2f96d8a9 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -35,6 +35,8 @@ information provided here is intended especially for developers. See https://docs.moodle.org/dev/Modal_and_AJAX_forms for more details. * Admin setting admin_setting_configmulticheckbox now supports lazy-loading the options list by supplying a callback function instead of an array of options. +* A new core API class \core\user_fields provides ways to get lists of user fields, and SQL related to + those fields. === 3.10 === * PHPUnit has been upgraded to 8.5. That comes with a few changes: diff --git a/user/profile/lib.php b/user/profile/lib.php index c82551f99c1..33dc3c85b8b 100644 --- a/user/profile/lib.php +++ b/user/profile/lib.php @@ -848,6 +848,36 @@ function profile_save_custom_fields($userid, $profilefields) { } } +/** + * Gets basic data about custom profile fields. This is minimal data that is cached within the + * current request for all fields so that it can be used quickly. + * + * @param string $shortname Shortname of custom profile field + * @return array Array with id, name, and visible fields + */ +function profile_get_custom_field_data_by_shortname(string $shortname): array { + global $DB; + + $cache = \cache::make_from_params(cache_store::MODE_REQUEST, 'core_profile', 'customfields', + [], ['simplekeys' => true, 'simpledata' => true]); + $data = $cache->get($shortname); + if (!$data) { + // If we don't have data, we get and cache it for all fields to avoid multiple DB requests. + $fields = $DB->get_records('user_info_field', null, '', 'id, shortname, name, visible'); + foreach ($fields as $field) { + $cache->set($field->shortname, (array)$field); + if ($field->shortname === $shortname) { + $data = (array)$field; + } + } + if (!$data) { + throw new \coding_exception('Unknown custom field: ' . $shortname); + } + } + + return $data; +} + /** * Trigger a user profile viewed event. * diff --git a/user/tests/profilelib_test.php b/user/tests/profilelib_test.php index 59d8b5dc603..d598a1d4b4f 100644 --- a/user/tests/profilelib_test.php +++ b/user/tests/profilelib_test.php @@ -240,4 +240,48 @@ class core_user_profilelib_testcase extends advanced_testcase { $this->assertObjectHasAttribute('house', $profilefields2); $this->assertNull($profilefields2->house); } + + /** + * Tests the profile_get_custom_field_data_by_shortname function when working normally. + */ + public function test_profile_get_custom_field_data_by_shortname_normal() { + global $DB, $CFG; + require_once($CFG->dirroot . '/user/profile/lib.php'); + + $this->resetAfterTest(); + + // Create 3 profile fields. + $generator = $this->getDataGenerator(); + $field1 = $generator->create_custom_profile_field(['datatype' => 'text', + 'shortname' => 'speciality', 'name' => 'Speciality', + 'visible' => PROFILE_VISIBLE_ALL]); + $field2 = $generator->create_custom_profile_field(['datatype' => 'menu', + 'shortname' => 'veggie', 'name' => 'Vegetarian', + 'visible' => PROFILE_VISIBLE_PRIVATE]); + + // Get the first field data and check it is correct. + $data = profile_get_custom_field_data_by_shortname('speciality'); + $this->assertEquals('Speciality', $data['name']); + $this->assertEquals(PROFILE_VISIBLE_ALL, $data['visible']); + $this->assertEquals($field1->id, $data['id']); + + // Get the second field data, checking there is no database query this time. + $before = $DB->perf_get_queries(); + $data = profile_get_custom_field_data_by_shortname('veggie'); + $this->assertEquals($before, $DB->perf_get_queries()); + $this->assertEquals('Vegetarian', $data['name']); + $this->assertEquals(PROFILE_VISIBLE_PRIVATE, $data['visible']); + $this->assertEquals($field2->id, $data['id']); + } + + /** + * Tests the profile_get_custom_field_data_by_shortname function with a field that doesn't exist. + */ + public function test_profile_get_custom_field_data_by_shortname_missing() { + global $CFG; + require_once($CFG->dirroot . '/user/profile/lib.php'); + + $this->expectExceptionMessage('Unknown custom field: speciality'); + profile_get_custom_field_data_by_shortname('speciality'); + } } From 947d8c78068dd5b4393b1e558f46a14c51ee86b4 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Mon, 12 Oct 2020 15:18:31 +0100 Subject: [PATCH 4/9] MDL-45242 Lib: Deprecate field-related library functions --- lib/deprecatedlib.php | 121 +++++++++++++++++++++++++++++++++++ lib/moodlelib.php | 112 -------------------------------- lib/outputcomponents.php | 55 +++++----------- lib/tests/moodlelib_test.php | 43 +++++++++++++ lib/upgrade.txt | 4 +- 5 files changed, 183 insertions(+), 152 deletions(-) diff --git a/lib/deprecatedlib.php b/lib/deprecatedlib.php index b02ddc0acf6..29abc58781f 100644 --- a/lib/deprecatedlib.php +++ b/lib/deprecatedlib.php @@ -3310,3 +3310,124 @@ function make_categories_options() { return core_course_category::make_categories_list('', 0, ' / '); } + +/** + * Checks if current user is shown any extra fields when listing users. + * + * Does not include any custom profile fields. + * + * @param object $context Context + * @param array $already Array of fields that we're going to show anyway + * so don't bother listing them + * @return array Array of field names from user table, not including anything + * listed in $already + * @deprecated since Moodle 3.11 MDL-45242 + * @see \core\user_fields + */ +function get_extra_user_fields($context, $already = array()) { + debugging('get_extra_user_fields() is deprecated. Please use the \core\user_fields API instead.', DEBUG_DEVELOPER); + + $fields = \core\user_fields::for_identity($context, false)->excluding(...$already); + return $fields->get_required_fields(); +} + +/** + * If the current user is to be shown extra user fields when listing or + * selecting users, returns a string suitable for including in an SQL select + * clause to retrieve those fields. + * + * Does not include any custom profile fields. + * + * @param context $context Context + * @param string $alias Alias of user table, e.g. 'u' (default none) + * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none) + * @param array $already Array of fields that we're going to include anyway so don't list them (default none) + * @return string Partial SQL select clause, beginning with comma, for example ',u.idnumber,u.department' unless it is blank + * @deprecated since Moodle 3.11 MDL-45242 + * @see \core\user_fields + */ +function get_extra_user_fields_sql($context, $alias='', $prefix='', $already = array()) { + debugging('get_extra_user_fields_sql() is deprecated. Please use the \core\user_fields API instead.', DEBUG_DEVELOPER); + + $fields = \core\user_fields::for_identity($context, false)->excluding(...$already); + // Note: There will never be any joins or join params because we turned off profile fields. + $selects = $fields->get_sql($alias, false, $prefix)->selects; + + return $selects; +} + +/** + * Returns the display name of a field in the user table. Works for most fields that are commonly displayed to users. + * + * Also works for custom fields. + * + * @param string $field Field name, e.g. 'phone1' + * @return string Text description taken from language file, e.g. 'Phone number' + * @deprecated since Moodle 3.11 MDL-45242 + * @see \core\user_fields + */ +function get_user_field_name($field) { + debugging('get_user_field_name() is deprecated. Please use \core\user_fields::get_display_name() instead', DEBUG_DEVELOPER); + + return \core\user_fields::get_display_name($field); +} + +/** + * A centralised location for the all name fields. Returns an array / sql string snippet. + * + * @param bool $returnsql True for an sql select field snippet. + * @param string $tableprefix table query prefix to use in front of each field. + * @param string $prefix prefix added to the name fields e.g. authorfirstname. + * @param string $fieldprefix sql field prefix e.g. id AS userid. + * @param bool $order moves firstname and lastname to the top of the array / start of the string. + * @return array|string All name fields. + * @deprecated since Moodle 3.11 MDL-45242 + * @see \core\user_fields + */ +function get_all_user_name_fields($returnsql = false, $tableprefix = null, $prefix = null, $fieldprefix = null, $order = false) { + debugging('get_all_user_name_fields() is deprecated. Please use the \core\user_fields API instead', DEBUG_DEVELOPER); + + // This array is provided in this order because when called by fullname() (above) if firstname is before + // firstnamephonetic str_replace() will change the wrong placeholder. + $alternatenames = []; + foreach (\core\user_fields::get_name_fields() as $field) { + $alternatenames[$field] = $field; + } + + // Let's add a prefix to the array of user name fields if provided. + if ($prefix) { + foreach ($alternatenames as $key => $altname) { + $alternatenames[$key] = $prefix . $altname; + } + } + + // If we want the end result to have firstname and lastname at the front / top of the result. + if ($order) { + // Move the last two elements (firstname, lastname) off the array and put them at the top. + for ($i = 0; $i < 2; $i++) { + // Get the last element. + $lastelement = end($alternatenames); + // Remove it from the array. + unset($alternatenames[$lastelement]); + // Put the element back on the top of the array. + $alternatenames = array_merge(array($lastelement => $lastelement), $alternatenames); + } + } + + // Create an sql field snippet if requested. + if ($returnsql) { + if ($tableprefix) { + if ($fieldprefix) { + foreach ($alternatenames as $key => $altname) { + $alternatenames[$key] = $tableprefix . '.' . $altname . ' AS ' . $fieldprefix . $altname; + } + } else { + foreach ($alternatenames as $key => $altname) { + $alternatenames[$key] = $tableprefix . '.' . $altname; + } + } + } + $alternatenames = implode(',', $alternatenames); + } + return $alternatenames; +} diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 1b8416693e1..1c474c88be0 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -3632,64 +3632,6 @@ function fullname($user, $override=false) { return $displayname; } -/** - * A centralised location for the all name fields. Returns an array / sql string snippet. - * - * @param bool $returnsql True for an sql select field snippet. - * @param string $tableprefix table query prefix to use in front of each field. - * @param string $prefix prefix added to the name fields e.g. authorfirstname. - * @param string $fieldprefix sql field prefix e.g. id AS userid. - * @param bool $order moves firstname and lastname to the top of the array / start of the string. - * @return array|string All name fields. - */ -function get_all_user_name_fields($returnsql = false, $tableprefix = null, $prefix = null, $fieldprefix = null, $order = false) { - // This array is provided in this order because when called by fullname() (above) if firstname is before - // firstnamephonetic str_replace() will change the wrong placeholder. - $alternatenames = array('firstnamephonetic' => 'firstnamephonetic', - 'lastnamephonetic' => 'lastnamephonetic', - 'middlename' => 'middlename', - 'alternatename' => 'alternatename', - 'firstname' => 'firstname', - 'lastname' => 'lastname'); - - // Let's add a prefix to the array of user name fields if provided. - if ($prefix) { - foreach ($alternatenames as $key => $altname) { - $alternatenames[$key] = $prefix . $altname; - } - } - - // If we want the end result to have firstname and lastname at the front / top of the result. - if ($order) { - // Move the last two elements (firstname, lastname) off the array and put them at the top. - for ($i = 0; $i < 2; $i++) { - // Get the last element. - $lastelement = end($alternatenames); - // Remove it from the array. - unset($alternatenames[$lastelement]); - // Put the element back on the top of the array. - $alternatenames = array_merge(array($lastelement => $lastelement), $alternatenames); - } - } - - // Create an sql field snippet if requested. - if ($returnsql) { - if ($tableprefix) { - if ($fieldprefix) { - foreach ($alternatenames as $key => $altname) { - $alternatenames[$key] = $tableprefix . '.' . $altname . ' AS ' . $fieldprefix . $altname; - } - } else { - foreach ($alternatenames as $key => $altname) { - $alternatenames[$key] = $tableprefix . '.' . $altname; - } - } - } - $alternatenames = implode(',', $alternatenames); - } - return $alternatenames; -} - /** * Reduces lines of duplicated code for getting user name fields. * @@ -3752,60 +3694,6 @@ function order_in_string($values, $stringformat) { return $valuearray; } -/** - * Checks if current user is shown any extra fields when listing users. - * - * Does not include any custom profile fields. - * - * @param object $context Context - * @param array $already Array of fields that we're going to show anyway - * so don't bother listing them - * @return array Array of field names from user table, not including anything - * listed in $already - */ -function get_extra_user_fields($context, $already = array()) { - $fields = new \core\user_fields([\core\user_fields::PURPOSE_IDENTITY], [], $already); - return $fields->get_required_fields($context, false); -} - -/** - * If the current user is to be shown extra user fields when listing or - * selecting users, returns a string suitable for including in an SQL select - * clause to retrieve those fields. - * - * Does not include any custom profile fields. - * - * @param context $context Context - * @param string $alias Alias of user table, e.g. 'u' (default none) - * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none) - * @param array $already Array of fields that we're going to include anyway so don't list them (default none) - * @return string Partial SQL select clause, beginning with comma, for example ',u.idnumber,u.department' unless it is blank - */ -function get_extra_user_fields_sql($context, $alias='', $prefix='', $already = array()) { - $fields = new \core\user_fields([\core\user_fields::PURPOSE_IDENTITY], [], $already); - // Note: $joins and $joinparams will always be empty because we turned off profile fields. - [$selects, $joins, $joinparams] = $fields->get_sql($context, false, false, $alias, $prefix); - - if ($alias === '') { - // The new code puts {user}. in front of the field names while the old code didn't. - $selects = str_replace('{user}.', '', $selects); - } - - return $selects; -} - -/** - * Returns the display name of a field in the user table. Works for most fields that are commonly displayed to users. - * - * Also works for custom fields. - * - * @param string $field Field name, e.g. 'phone1' - * @return string Text description taken from language file, e.g. 'Phone number' - */ -function get_user_field_name($field) { - return \core\user_fields::get_display_name($field); -} - /** * Returns whether a given authentication plugin exists. * diff --git a/lib/outputcomponents.php b/lib/outputcomponents.php index 8ed6e1ec986..8ca4a3f52ce 100644 --- a/lib/outputcomponents.php +++ b/lib/outputcomponents.php @@ -149,13 +149,6 @@ class file_picker implements renderable { * @category output */ class user_picture implements renderable { - /** - * @var array List of mandatory fields in user record here. (do not include - * TEXT columns because it would break SELECT DISTINCT in MSSQL and ORACLE) - */ - protected static $fields = array('id', 'picture', 'firstname', 'lastname', 'firstnamephonetic', 'lastnamephonetic', - 'middlename', 'alternatename', 'imagealt', 'email'); - /** * @var stdClass A user object with at least fields all columns specified * in $fields array constant set. @@ -227,11 +220,11 @@ class user_picture implements renderable { // only touch the DB if we are missing data and complain loudly... $needrec = false; - foreach (self::$fields as $field) { + foreach (\core\user_fields::get_picture_fields() as $field) { if (!property_exists($user, $field)) { $needrec = true; debugging('Missing '.$field.' property in $user object, this is a performance problem that needs to be fixed by a developer. ' - .'Please use user_picture::fields() to get the full list of required fields.', DEBUG_DEVELOPER); + .'Please use the \core\user_fields API to get the full list of required fields.', DEBUG_DEVELOPER); break; } } @@ -255,39 +248,23 @@ class user_picture implements renderable { * @param string $idalias alias of id field * @param string $fieldprefix prefix to add to all columns in their aliases, does not apply to 'id' * @return string + * @deprecated since Moodle 3.11 MDL-45242 + * @see \core\user_fields */ public static function fields($tableprefix = '', array $extrafields = NULL, $idalias = 'id', $fieldprefix = '') { - if (!$tableprefix and !$extrafields and !$idalias) { - return implode(',', self::$fields); - } - if ($tableprefix) { - $tableprefix .= '.'; - } - foreach (self::$fields as $field) { - if ($field === 'id' and $idalias and $idalias !== 'id') { - $fields[$field] = "$tableprefix$field AS $idalias"; - } else { - if ($fieldprefix and $field !== 'id') { - $fields[$field] = "$tableprefix$field AS $fieldprefix$field"; - } else { - $fields[$field] = "$tableprefix$field"; - } - } - } - // add extra fields if not already there + debugging('user_picture::fields() is deprecated. Please use the \core\user_fields API instead.', DEBUG_DEVELOPER); + $userfields = \core\user_fields::for_userpic(); if ($extrafields) { - foreach ($extrafields as $e) { - if ($e === 'id' or isset($fields[$e])) { - continue; - } - if ($fieldprefix) { - $fields[$e] = "$tableprefix$e AS $fieldprefix$e"; - } else { - $fields[$e] = "$tableprefix$e"; - } - } + $userfields->including(...$extrafields); } - return implode(',', $fields); + $selects = $userfields->get_sql($tableprefix, false, $fieldprefix, $idalias, false)->selects; + if ($tableprefix === '') { + // If no table alias is specified, don't add {user}. in front of fields. + $selects = str_replace('{user}.', '', $selects); + } + // Maintain legacy behaviour where the field list was done with 'implode' and no spaces. + $selects = str_replace(', ', ',', $selects); + return $selects; } /** @@ -310,7 +287,7 @@ class user_picture implements renderable { $return = new stdClass(); - foreach (self::$fields as $field) { + foreach (\core\user_fields::get_picture_fields() as $field) { if ($field === 'id') { if (property_exists($record, $idalias)) { $return->id = $record->{$idalias}; diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php index 504128ca514..a21ee933c8f 100644 --- a/lib/tests/moodlelib_test.php +++ b/lib/tests/moodlelib_test.php @@ -1505,6 +1505,8 @@ class core_moodlelib_testcase extends advanced_testcase { /** * Test essential features implementation of {@link get_extra_user_fields()} as the admin user with all capabilities. + * + * @deprecated since Moodle 3.11 MDL-45242 */ public function test_get_extra_user_fields_essentials() { global $CFG, $USER, $DB; @@ -1536,12 +1538,15 @@ class core_moodlelib_testcase extends advanced_testcase { // Two fields. $CFG->showuseridentity = 'frog,zombie'; $this->assertEquals(array('zombie'), get_extra_user_fields($context, array('frog'))); + + $this->assertDebuggingCalledCount(6); } /** * Prepare environment for couple of tests related to permission checks in {@link get_extra_user_fields()}. * * @return stdClass + * @deprecated since Moodle 3.11 MDL-45242 */ protected function environment_for_get_extra_user_fields_tests() { global $CFG, $DB; @@ -1571,6 +1576,8 @@ class core_moodlelib_testcase extends advanced_testcase { /** * No identity fields shown to student user (no permission to view identity fields). + * + * @deprecated since Moodle 3.11 MDL-45242 */ public function test_get_extra_user_fields_no_access() { @@ -1580,10 +1587,14 @@ class core_moodlelib_testcase extends advanced_testcase { $this->assertEquals(array(), get_extra_user_fields($env->coursecontext)); $this->assertEquals(array(), get_extra_user_fields(context_system::instance())); + + $this->assertDebuggingCalledCount(2); } /** * Teacher can see students' identity fields only within the course. + * + * @deprecated since Moodle 3.11 MDL-45242 */ public function test_get_extra_user_fields_course_only_access() { @@ -1593,10 +1604,14 @@ class core_moodlelib_testcase extends advanced_testcase { $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields($env->coursecontext)); $this->assertEquals(array(), get_extra_user_fields(context_system::instance())); + + $this->assertDebuggingCalledCount(2); } /** * Teacher can be prevented from seeing students' identity fields even within the course. + * + * @deprecated since Moodle 3.11 MDL-45242 */ public function test_get_extra_user_fields_course_prevented_access() { @@ -1606,10 +1621,14 @@ class core_moodlelib_testcase extends advanced_testcase { assign_capability('moodle/course:viewhiddenuserfields', CAP_PREVENT, $env->teacherrole->id, $env->coursecontext->id); $this->assertEquals(array('idnumber'), get_extra_user_fields($env->coursecontext)); + + $this->assertDebuggingCalledCount(1); } /** * Manager can see students' identity fields anywhere. + * + * @deprecated since Moodle 3.11 MDL-45242 */ public function test_get_extra_user_fields_anywhere_access() { @@ -1619,10 +1638,14 @@ class core_moodlelib_testcase extends advanced_testcase { $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields($env->coursecontext)); $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields(context_system::instance())); + + $this->assertDebuggingCalledCount(2); } /** * Manager can be prevented from seeing hidden fields outside the course. + * + * @deprecated since Moodle 3.11 MDL-45242 */ public function test_get_extra_user_fields_schismatic_access() { @@ -1635,10 +1658,14 @@ class core_moodlelib_testcase extends advanced_testcase { // Note that inside the course, the manager can still see the hidden identifiers as this is currently // controlled by a separate capability for legacy reasons. $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields($env->coursecontext)); + + $this->assertDebuggingCalledCount(2); } /** * Two capabilities must be currently set to prevent manager from seeing hidden fields. + * + * @deprecated since Moodle 3.11 MDL-45242 */ public function test_get_extra_user_fields_hard_to_prevent_access() { @@ -1651,8 +1678,15 @@ class core_moodlelib_testcase extends advanced_testcase { $this->assertEquals(array('idnumber'), get_extra_user_fields(context_system::instance())); $this->assertEquals(array('idnumber'), get_extra_user_fields($env->coursecontext)); + + $this->assertDebuggingCalledCount(2); } + /** + * Tests get_extra_user_fields_sql. + * + * @deprecated since Moodle 3.11 MDL-45242 + */ public function test_get_extra_user_fields_sql() { global $CFG, $USER, $DB; $this->resetAfterTest(); @@ -1686,6 +1720,8 @@ class core_moodlelib_testcase extends advanced_testcase { $CFG->showuseridentity = 'frog,zombie'; $this->assertEquals(', u1.zombie AS u_zombie', get_extra_user_fields_sql($context, 'u1', 'u_', array('frog'))); + + $this->assertDebuggingCalledCount(6); } /** @@ -3037,6 +3073,11 @@ class core_moodlelib_testcase extends advanced_testcase { $CFG->alternativefullnameformat = $originalcfg->alternativefullnameformat; } + /** + * Tests the get_all_user_name_fields() deprecated function. + * + * @deprecated since Moodle 3.11 MDL-45242 + */ public function test_get_all_user_name_fields() { $this->resetAfterTest(); @@ -3084,6 +3125,8 @@ class core_moodlelib_testcase extends advanced_testcase { // Returning a string. $teststring = 'firstname,lastname,firstnamephonetic,lastnamephonetic,middlename,alternatename'; $this->assertEquals($teststring, get_all_user_name_fields(true, null, null, null, true)); + + $this->assertDebuggingCalledCount(7); } public function test_order_in_string() { diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 71d2f96d8a9..44e92c53133 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -36,7 +36,9 @@ information provided here is intended especially for developers. * Admin setting admin_setting_configmulticheckbox now supports lazy-loading the options list by supplying a callback function instead of an array of options. * A new core API class \core\user_fields provides ways to get lists of user fields, and SQL related to - those fields. + those fields. This replaces existing functions get_extra_user_fields(), get_extra_user_fields_sql(), + get_user_field_name(), get_all_user_name_fields(), and user_picture::fields(), which have all been + deprecated. === 3.10 === * PHPUnit has been upgraded to 8.5. That comes with a few changes: From 5b7b1b9a06dbd9eb11da6c8b13b8c2f9b540d463 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Mon, 12 Oct 2020 17:33:17 +0100 Subject: [PATCH 5/9] MDL-45242 Lib: Replace direct references to ->showuseridentity --- admin/tool/lp/classes/external.php | 6 +-- lib/classes/user.php | 8 +--- lib/deprecatedlib.php | 5 ++- lib/myprofilelib.php | 10 ++--- mod/assign/classes/output/grading_app.php | 3 +- .../external/user_summary_exporter.php | 3 +- user/classes/table/participants_search.php | 45 +++++++++---------- 7 files changed, 36 insertions(+), 44 deletions(-) diff --git a/admin/tool/lp/classes/external.php b/admin/tool/lp/classes/external.php index ada3e887e66..597cb0abc05 100644 --- a/admin/tool/lp/classes/external.php +++ b/admin/tool/lp/classes/external.php @@ -878,10 +878,8 @@ class external extends external_api { list($filtercapsql, $filtercapparams) = api::filter_users_with_capability_on_user_context_sql($cap, $USER->id, SQL_PARAMS_NAMED); - $extrasearchfields = array(); - if (!empty($CFG->showuseridentity) && has_capability('moodle/site:viewuseridentity', $context)) { - $extrasearchfields = explode(',', $CFG->showuseridentity); - } + // TODO Does not support custom user profile fields (MDL-70456). + $extrasearchfields = \core\user_fields::get_identity_fields($context, false); $fields = \user_picture::fields('u', $extrasearchfields); list($wheresql, $whereparams) = users_search_sql($query, 'u', true, $extrasearchfields); diff --git a/lib/classes/user.php b/lib/classes/user.php index ac2430003cd..d5c00bb978e 100644 --- a/lib/classes/user.php +++ b/lib/classes/user.php @@ -251,12 +251,8 @@ class core_user { $extrasql = ''; $extraparams = []; - if (empty($CFG->showuseridentity)) { - // Explode gives wrong result with empty string. - $extra = []; - } else { - $extra = explode(',', $CFG->showuseridentity); - } + // TODO Does not support custom user profile fields (MDL-70456). + $extra = \core\user_fields::get_identity_fields(null, false); // We need the username just to skip guests. $extrafieldlist = $extra; diff --git a/lib/deprecatedlib.php b/lib/deprecatedlib.php index 29abc58781f..8cf3efa61b1 100644 --- a/lib/deprecatedlib.php +++ b/lib/deprecatedlib.php @@ -3171,9 +3171,10 @@ function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $r } $conditions[] = $idnumber; - if (!empty($CFG->showuseridentity)) { + // TODO Does not support custom user profile fields (MDL-70456). + $extrasearchfields = \core\user_fields::get_identity_fields($context, false); + if (!empty($extrasearchfields)) { // Search all user identify fields. - $extrasearchfields = explode(',', $CFG->showuseridentity); foreach ($extrasearchfields as $extrasearchfield) { if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) { // Already covered above. Search by country not supported. diff --git a/lib/myprofilelib.php b/lib/myprofilelib.php index 76a4ef0bf40..e55d95da660 100644 --- a/lib/myprofilelib.php +++ b/lib/myprofilelib.php @@ -125,12 +125,8 @@ function core_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, } else { $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields)); } - $canviewuseridentity = has_capability('moodle/site:viewuseridentity', $courseorusercontext); - if ($canviewuseridentity) { - $identityfields = array_flip(explode(',', $CFG->showuseridentity)); - } else { - $identityfields = array(); - } + // TODO Does not support custom user profile fields (MDL-70456). + $identityfields = array_flip(\core\user_fields::get_identity_fields($courseorusercontext, false)); if (is_mnet_remote_user($user)) { $sql = "SELECT h.id, h.name, h.wwwroot, @@ -156,7 +152,7 @@ function core_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, or ($user->maildisplay == core_user::MAILDISPLAY_COURSE_MEMBERS_ONLY and enrol_sharing_course($user, $USER)) or has_capability('moodle/course:useremail', $courseorusercontext) // TODO: Deprecate/remove for MDL-37479. )) - or (isset($identityfields['email']) and $canviewuseridentity) + or (isset($identityfields['email'])) ) { $maildisplay = obfuscate_mailto($user->email, ''); if ($iscurrentuser) { diff --git a/mod/assign/classes/output/grading_app.php b/mod/assign/classes/output/grading_app.php index 128a869cdee..ea5381a9c59 100644 --- a/mod/assign/classes/output/grading_app.php +++ b/mod/assign/classes/output/grading_app.php @@ -168,7 +168,8 @@ class grading_app implements templatable, renderable { $export->rarrow = $output->rarrow(); $export->larrow = $output->larrow(); // List of identity fields to display (the user info will not contain any fields the user cannot view anyway). - $export->showuseridentity = $CFG->showuseridentity; + // TODO Does not support custom user profile fields (MDL-70456). + $export->showuseridentity = implode(',', \core\user_fields::get_identity_fields(null, false)); $export->currentuserid = $USER->id; $helpicon = new \help_icon('sendstudentnotifications', 'assign'); $export->helpicon = $helpicon->export_for_template($output); diff --git a/user/classes/external/user_summary_exporter.php b/user/classes/external/user_summary_exporter.php index f8f21dc767f..1b0658166af 100644 --- a/user/classes/external/user_summary_exporter.php +++ b/user/classes/external/user_summary_exporter.php @@ -48,7 +48,8 @@ class user_summary_exporter extends \core\external\exporter { $profileurl = (new moodle_url('/user/profile.php', array('id' => $this->data->id)))->out(false); - $identityfields = array_flip(explode(',', $CFG->showuseridentity)); + // TODO Does not support custom user profile fields (MDL-70456). + $identityfields = array_flip(\core\user_fields::get_identity_fields(null, false)); $data = $this->data; foreach ($identityfields as $field => $index) { if (!empty($data->$field)) { diff --git a/user/classes/table/participants_search.php b/user/classes/table/participants_search.php index 79a7137f46c..781b86545fa 100644 --- a/user/classes/table/participants_search.php +++ b/user/classes/table/participants_search.php @@ -962,30 +962,29 @@ class participants_search { $conditions[] = $idnumber; - if (!empty($CFG->showuseridentity)) { - // Search all user identify fields. - $extrasearchfields = explode(',', $CFG->showuseridentity); - foreach ($extrasearchfields as $extrasearchfield) { - if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) { - // Already covered above. Search by country not supported. - continue; - } - $param = $searchkey3 . $extrasearchfield; - $condition = $DB->sql_like($extrasearchfield, ':' . $param, false, false); - $params[$param] = "%$keyword%"; - - if ($notjoin) { - $condition = "($extrasearchfield IS NOT NULL AND {$condition})"; - } - - if (!in_array($extrasearchfield, $this->userfields)) { - // User cannot see this field, but allow match if their own account. - $userid3 = 'userid' . $index . '3' . $extrasearchfield; - $condition = "(". $condition . " AND u.id = :$userid3)"; - $params[$userid3] = $USER->id; - } - $conditions[] = $condition; + // Search all user identify fields. + // TODO Does not support custom user profile fields (MDL-70456). + $extrasearchfields = \core\user_fields::get_identity_fields(null, false); + foreach ($extrasearchfields as $extrasearchfield) { + if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) { + // Already covered above. Search by country not supported. + continue; } + $param = $searchkey3 . $extrasearchfield; + $condition = $DB->sql_like($extrasearchfield, ':' . $param, false, false); + $params[$param] = "%$keyword%"; + + if ($notjoin) { + $condition = "($extrasearchfield IS NOT NULL AND {$condition})"; + } + + if (!in_array($extrasearchfield, $this->userfields)) { + // User cannot see this field, but allow match if their own account. + $userid3 = 'userid' . $index . '3' . $extrasearchfield; + $condition = "(". $condition . " AND u.id = :$userid3)"; + $params[$userid3] = $USER->id; + } + $conditions[] = $condition; } // Search by middlename. From 0919a043114ae7707da321e41e581cdc0d67c4c2 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Mon, 12 Oct 2020 17:51:20 +0100 Subject: [PATCH 6/9] MDL-45242 Lib: Replace calls to deprecated functions In all cases changes have been kept to a minimum while not making the code completely horrible. For example, there are many instances where it would probably be better to rewrite a query entirely, but I have not done that (in order to reduce the risk of changes). --- admin/classes/task_log_table.php | 5 +- admin/roles/assign.php | 3 +- .../output/cohort_role_assignments_table.php | 13 ++-- admin/tool/dataprivacy/classes/api.php | 3 +- admin/tool/dataprivacy/classes/external.php | 6 +- .../tool/dataprivacy/classes/local/helper.php | 3 +- .../dataprivacy/createdatarequest_form.php | 3 +- admin/tool/lp/classes/external.php | 5 +- .../classes/output/template_plans_table.php | 13 ++-- .../tool/policy/classes/acceptances_table.php | 13 ++-- admin/tool/policy/classes/api.php | 9 +-- .../policy/classes/form/accept_policy.php | 3 +- admin/tool/uploaduser/classes/process.php | 2 +- admin/user.php | 10 +-- admin/user/user_bulk_cohortadd.php | 3 +- admin/user/user_bulk_display.php | 3 +- admin/webservice/forms.php | 3 +- .../condition/profile/classes/condition.php | 2 +- .../condition/profile/classes/frontend.php | 34 +++++----- backup/moodle2/backup_stepslib.php | 2 +- badges/classes/output/external_badge.php | 3 +- badges/classes/output/issued_badge.php | 3 +- badges/criteria/award_criteria_profile.php | 4 +- badges/recipients.php | 3 +- .../block_activity_results.php | 4 +- blocks/mentees/block_mentees.php | 3 +- blocks/online_users/classes/fetcher.php | 3 +- blog/locallib.php | 4 +- blog/rsslib.php | 4 +- comment/lib.php | 3 +- comment/locallib.php | 5 +- course/classes/category.php | 3 +- course/recent_form.php | 7 +- enrol/ajax.php | 3 +- enrol/externallib.php | 6 +- enrol/locallib.php | 27 +++++--- enrol/manual/classes/enrol_users_form.php | 3 +- enrol/otherusers.php | 5 +- enrol/self/lib.php | 3 +- enrol/self/locallib.php | 4 +- grade/report/grader/ajax_callbacks.php | 3 +- grade/report/grader/lib.php | 14 ++-- grade/report/history/classes/helper.php | 9 ++- .../history/classes/output/tablelog.php | 13 ++-- grade/report/history/users_ajax.php | 3 +- group/autogroup.php | 3 +- group/index.php | 14 ++-- group/lib.php | 3 +- group/overview.php | 7 +- lib/accesslib.php | 3 +- lib/adminlib.php | 3 +- lib/authlib.php | 2 +- lib/badgeslib.php | 2 +- lib/classes/check/access/riskadmin.php | 3 +- .../check/access/riskbackup_result.php | 3 +- lib/classes/check/access/riskxss_result.php | 3 +- .../send_failed_login_notifications_task.php | 3 +- .../task/send_new_user_passwords_task.php | 3 +- lib/classes/user.php | 18 ++--- lib/completionlib.php | 9 ++- lib/datalib.php | 16 ++--- lib/deprecatedlib.php | 6 +- lib/grouplib.php | 3 +- lib/moodlelib.php | 7 +- lib/outputcomponents.php | 3 +- lib/tablelib.php | 6 +- lib/tests/moodlelib_test.php | 4 +- lib/tests/outputcomponents_test.php | 18 ++++- message/classes/api.php | 14 ++-- message/classes/helper.php | 3 +- message/lib.php | 8 ++- .../email/classes/task/send_email_task.php | 3 +- mod/assign/extensionform.php | 3 +- mod/assign/feedback/file/locallib.php | 3 +- mod/assign/gradingtable.php | 10 +-- mod/assign/lib.php | 8 ++- mod/assign/locallib.php | 15 +++-- mod/assign/override_form.php | 8 ++- mod/assign/overridedelete.php | 3 +- mod/assign/overrides.php | 5 +- mod/chat/lib.php | 10 +-- mod/choice/lib.php | 6 +- mod/choice/report.php | 9 +-- mod/data/lib.php | 5 +- mod/data/locallib.php | 5 +- mod/data/preset.php | 3 +- mod/data/view.php | 10 ++- mod/feedback/classes/responses_table.php | 8 ++- mod/feedback/lib.php | 9 ++- mod/forum/classes/form/export_form.php | 3 +- .../classes/local/vaults/discussion_list.php | 9 ++- mod/forum/classes/subscriptions.php | 3 +- mod/forum/deprecatedlib.php | 2 +- mod/forum/externallib.php | 2 +- mod/forum/lib.php | 34 ++++++---- mod/forum/renderer.php | 3 +- .../report/summary/classes/summary_table.php | 5 +- mod/forum/rsslib.php | 6 +- mod/glossary/classes/entry_query_builder.php | 4 +- mod/glossary/lib.php | 13 ++-- mod/glossary/rsslib.php | 3 +- mod/lesson/essay.php | 3 +- mod/lesson/locallib.php | 8 ++- mod/lesson/override_form.php | 8 ++- mod/lesson/overridedelete.php | 3 +- mod/lesson/overrides.php | 5 +- mod/quiz/lib.php | 3 +- mod/quiz/locallib.php | 3 +- mod/quiz/override_form.php | 8 ++- mod/quiz/overridedelete.php | 5 +- mod/quiz/overrides.php | 8 ++- mod/quiz/report/attemptsreport.php | 8 ++- mod/quiz/report/attemptsreport_table.php | 12 ++-- mod/quiz/report/grading/report.php | 3 +- mod/quiz/report/overview/report.php | 6 +- mod/scorm/report/basic/classes/report.php | 14 ++-- .../report/interactions/classes/report.php | 14 ++-- .../report/objectives/classes/report.php | 14 ++-- mod/scorm/report/userreport.php | 2 +- mod/scorm/report/userreportinteractions.php | 2 +- mod/scorm/report/userreporttracks.php | 2 +- mod/survey/lib.php | 9 ++- mod/workshop/allocation/manual/lib.php | 8 ++- mod/workshop/lib.php | 17 +++-- mod/workshop/locallib.php | 67 +++++++++++-------- mod/workshop/renderer.php | 4 +- mod/workshop/view.php | 6 +- question/classes/bank/creator_name_column.php | 2 +- .../classes/bank/modifier_name_column.php | 2 +- rating/classes/external.php | 2 +- rating/index.php | 2 +- rating/lib.php | 3 +- report/completion/index.php | 7 +- .../configlog/classes/output/report_table.php | 5 +- report/log/classes/renderable.php | 4 +- report/log/classes/table_log.php | 8 ++- report/log/locallib.php | 8 ++- report/loglive/classes/table_log.php | 4 +- report/participation/index.php | 3 +- report/progress/index.php | 7 +- report/stats/locallib.php | 3 +- search/classes/engine.php | 3 +- tag/classes/manage_table.php | 3 +- user/action_redir.php | 3 +- user/classes/search/user.php | 2 +- user/classes/table/participants.php | 5 +- user/classes/table/participants_search.php | 3 +- user/editlib.php | 13 +++- user/lib.php | 3 +- user/selector/lib.php | 5 +- userpix/index.php | 2 +- webservice/classes/token_table.php | 5 +- 152 files changed, 612 insertions(+), 394 deletions(-) diff --git a/admin/classes/task_log_table.php b/admin/classes/task_log_table.php index 673e9a18ca4..c02458d9426 100644 --- a/admin/classes/task_log_table.php +++ b/admin/classes/task_log_table.php @@ -124,8 +124,9 @@ class task_log_table extends \table_sql { $sort = "ORDER BY $sort"; } - $extrafields = get_extra_user_fields(\context_system::instance()); - $userfields = \user_picture::fields('u', $extrafields, 'userid2', 'user'); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity(\context_system::instance(), false)->with_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, 'user', 'userid2', false)->selects; $where = ''; if (!empty($this->sql->where)) { diff --git a/admin/roles/assign.php b/admin/roles/assign.php index eada1e9cc6b..17e6b65132e 100644 --- a/admin/roles/assign.php +++ b/admin/roles/assign.php @@ -271,7 +271,8 @@ if ($roleid) { foreach ($assignableroles as $roleid => $notused) { $roleusers = ''; if (0 < $assigncounts[$roleid] && $assigncounts[$roleid] <= MAX_USERS_TO_LIST_PER_ROLE) { - $userfields = 'u.id, u.username, ' . get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $userfields = 'u.id, u.username' . $userfieldsapi->get_sql('u')->selects; $roleusers = get_role_users($roleid, $context, false, $userfields); if (!empty($roleusers)) { $strroleusers = array(); diff --git a/admin/tool/cohortroles/classes/output/cohort_role_assignments_table.php b/admin/tool/cohortroles/classes/output/cohort_role_assignments_table.php index 496a4acca3d..630f8a883c7 100644 --- a/admin/tool/cohortroles/classes/output/cohort_role_assignments_table.php +++ b/admin/tool/cohortroles/classes/output/cohort_role_assignments_table.php @@ -126,7 +126,8 @@ class cohort_role_assignments_table extends table_sql { * Setup the headers for the table. */ protected function define_table_columns() { - $extrafields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->context, false); // Define headers and columns. $cols = array( @@ -170,14 +171,12 @@ class cohort_role_assignments_table extends table_sql { protected function get_sql_and_params($count = false) { $fields = 'uca.id, uca.cohortid, uca.userid, uca.roleid, '; $fields .= 'c.name as cohortname, c.idnumber as cohortidnumber, c.contextid as cohortcontextid, '; - $fields .= 'c.visible as cohortvisible, c.description as cohortdescription, c.theme as cohorttheme, '; + $fields .= 'c.visible as cohortvisible, c.description as cohortdescription, c.theme as cohorttheme'; // Add extra user fields that we need for the graded user. - $extrafields = get_extra_user_fields($this->context); - foreach ($extrafields as $field) { - $fields .= 'u.' . $field . ', '; - } - $fields .= get_all_user_name_fields(true, 'u'); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($this->context, false)->with_name(); + $fields .= $userfieldsapi->get_sql('u')->selects; if ($count) { $select = "COUNT(1)"; diff --git a/admin/tool/dataprivacy/classes/api.php b/admin/tool/dataprivacy/classes/api.php index 906dcb5efba..ceaee01a9bb 100644 --- a/admin/tool/dataprivacy/classes/api.php +++ b/admin/tool/dataprivacy/classes/api.php @@ -189,7 +189,8 @@ class api { $dpos = []; $context = context_system::instance(); foreach ($dporoles as $roleid) { - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $fields = 'u.id, u.confirmed, u.username, '. $allnames . ', ' . 'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '. 'u.country, u.picture, u.idnumber, u.department, u.institution, '. diff --git a/admin/tool/dataprivacy/classes/external.php b/admin/tool/dataprivacy/classes/external.php index 7306eb20d9d..c2ab73195dd 100644 --- a/admin/tool/dataprivacy/classes/external.php +++ b/admin/tool/dataprivacy/classes/external.php @@ -700,13 +700,15 @@ class external extends external_api { self::validate_context($context); require_capability('tool/dataprivacy:managedatarequests', $context); - $allusernames = get_all_user_name_fields(true); + $userfieldsapi = \core\user_fields::for_name(); + $allusernames = $userfieldsapi->get_sql('', false, '', '', false)->selects; // Exclude admins and guest user. $excludedusers = array_keys(get_admins()) + [guest_user()->id]; $sort = 'lastname ASC, firstname ASC'; $fields = 'id,' . $allusernames; - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($context, false); if (!empty($extrafields)) { $fields .= ',' . implode(',', $extrafields); } diff --git a/admin/tool/dataprivacy/classes/local/helper.php b/admin/tool/dataprivacy/classes/local/helper.php index 14d68f72a1b..b34cb8f11a5 100644 --- a/admin/tool/dataprivacy/classes/local/helper.php +++ b/admin/tool/dataprivacy/classes/local/helper.php @@ -187,7 +187,8 @@ class helper { global $DB; // Get users that the user has role assignments to. - $allusernames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allusernames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT u.id, $allusernames FROM {role_assignments} ra, {context} c, {user} u WHERE ra.userid = :userid diff --git a/admin/tool/dataprivacy/createdatarequest_form.php b/admin/tool/dataprivacy/createdatarequest_form.php index c708a8857ef..39c3e3c34d0 100644 --- a/admin/tool/dataprivacy/createdatarequest_form.php +++ b/admin/tool/dataprivacy/createdatarequest_form.php @@ -62,7 +62,8 @@ class tool_dataprivacy_data_request_form extends \core\form\persistent { 'valuehtmlcallback' => function($value) { global $OUTPUT; - $allusernames = get_all_user_name_fields(true); + $userfieldsapi = \core\user_fields::for_name(); + $allusernames = $userfieldsapi->get_sql('', false, '', '', false)->selects; $fields = 'id, email, ' . $allusernames; $user = \core_user::get_user($value, $fields); $useroptiondata = [ diff --git a/admin/tool/lp/classes/external.php b/admin/tool/lp/classes/external.php index 597cb0abc05..0264bd0e5cc 100644 --- a/admin/tool/lp/classes/external.php +++ b/admin/tool/lp/classes/external.php @@ -879,8 +879,9 @@ class external extends external_api { $USER->id, SQL_PARAMS_NAMED); // TODO Does not support custom user profile fields (MDL-70456). - $extrasearchfields = \core\user_fields::get_identity_fields($context, false); - $fields = \user_picture::fields('u', $extrasearchfields); + $userfieldsapi = \core\user_fields::for_identity($context, false)->with_userpic(); + $fields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $extrasearchfields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); list($wheresql, $whereparams) = users_search_sql($query, 'u', true, $extrasearchfields); list($sortsql, $sortparams) = users_order_by_sql('u', $query, $context); diff --git a/admin/tool/lp/classes/output/template_plans_table.php b/admin/tool/lp/classes/output/template_plans_table.php index 422e62af9ea..ba171163a33 100644 --- a/admin/tool/lp/classes/output/template_plans_table.php +++ b/admin/tool/lp/classes/output/template_plans_table.php @@ -92,7 +92,8 @@ class template_plans_table extends table_sql { * Setup the headers for the table. */ protected function define_table_columns() { - $extrafields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->context, false); // Define headers and columns. $cols = array( @@ -132,14 +133,12 @@ class template_plans_table extends table_sql { * @return array containing sql to use and an array of params. */ protected function get_sql_and_params($count = false) { - $fields = 'p.id, p.userid, p.name, '; + $fields = 'p.id, p.userid, p.name'; // Add extra user fields that we need for the graded user. - $extrafields = get_extra_user_fields($this->context); - foreach ($extrafields as $field) { - $fields .= 'u.' . $field . ', '; - } - $fields .= get_all_user_name_fields(true, 'u'); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($this->context, false)->with_name(); + $fields .= $userfieldsapi->get_sql('u')->selects; if ($count) { $select = "COUNT(1)"; diff --git a/admin/tool/policy/classes/acceptances_table.php b/admin/tool/policy/classes/acceptances_table.php index b12cae1fa7f..b4492bcb4b8 100644 --- a/admin/tool/policy/classes/acceptances_table.php +++ b/admin/tool/policy/classes/acceptances_table.php @@ -91,8 +91,10 @@ class acceptances_table extends \table_sql { } } - $extrafields = get_extra_user_fields(\context_system::instance()); - $userfields = \user_picture::fields('u', $extrafields); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity(\context_system::instance(), false)->with_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $extrafields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); $this->set_sql("$userfields", "{user} u", @@ -103,7 +105,7 @@ class acceptances_table extends \table_sql { } $this->add_column_header('fullname', get_string('fullnameuser', 'core')); foreach ($extrafields as $field) { - $this->add_column_header($field, get_user_field_name($field)); + $this->add_column_header($field, \core\user_fields::get_display_name($field)); } if (!$this->is_downloading() && !has_capability('tool/policy:acceptbehalf', \context_system::instance())) { @@ -168,7 +170,8 @@ class acceptances_table extends \table_sql { * Helper configuration method. */ protected function configure_for_single_version() { - $userfieldsmod = get_all_user_name_fields(true, 'm', null, 'mod'); + $userfieldsapi = \core\user_fields::for_name(); + $userfieldsmod = $userfieldsapi->get_sql('m', false, 'mod', '', false)->selects; $v = key($this->versionids); $this->sql->fields .= ", $userfieldsmod, a{$v}.status AS status{$v}, a{$v}.note, ". "a{$v}.timemodified, a{$v}.usermodified AS usermodified{$v}"; @@ -643,4 +646,4 @@ class acceptances_table extends \table_sql { } return parent::other_cols($column, $row); } -} \ No newline at end of file +} diff --git a/admin/tool/policy/classes/api.php b/admin/tool/policy/classes/api.php index 46a01c80411..5366daa5152 100644 --- a/admin/tool/policy/classes/api.php +++ b/admin/tool/policy/classes/api.php @@ -343,10 +343,10 @@ class api { global $DB; $ctxfields = context_helper::get_preload_record_columns_sql('c'); - $namefields = get_all_user_name_fields(true, 'u'); - $pixfields = user_picture::fields('u', $extrafields); + $userfieldsapi = \core\user_fields::for_name()->with_userpic()->including(...($extrafields ?? [])); + $userfields = $userfieldsapi->get_sql('u')->selects; - $sql = "SELECT $ctxfields, $namefields, $pixfields + $sql = "SELECT $ctxfields $userfields FROM {role_assignments} ra JOIN {context} c ON c.contextlevel = ".CONTEXT_USER." AND ra.contextid = c.id JOIN {user} u ON c.instanceid = u.id @@ -682,7 +682,8 @@ class api { $vsql = ' AND a.policyversionid ' . $vsql; } - $userfieldsmod = get_all_user_name_fields(true, 'm', null, 'mod'); + $userfieldsapi = \core\user_fields::for_name(); + $userfieldsmod = $userfieldsapi->get_sql('m', false, 'mod', '', false)->selects; $sql = "SELECT u.id AS mainuserid, a.policyversionid, a.status, a.lang, a.timemodified, a.usermodified, a.note, u.policyagreed, $userfieldsmod FROM {user} u diff --git a/admin/tool/policy/classes/form/accept_policy.php b/admin/tool/policy/classes/form/accept_policy.php index d385b2b33a9..6821daac8c5 100644 --- a/admin/tool/policy/classes/form/accept_policy.php +++ b/admin/tool/policy/classes/form/accept_policy.php @@ -128,7 +128,8 @@ class accept_policy extends \moodleform { $usernames = []; list($sql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); $params['usercontextlevel'] = CONTEXT_USER; - $users = $DB->get_records_sql("SELECT u.id, " . get_all_user_name_fields(true, 'u') . ", " . + $userfieldsapi = \core\user_fields::for_name(); + $users = $DB->get_records_sql("SELECT u.id" . $userfieldsapi->get_sql('u')->selects . ", " . \context_helper::get_preload_record_columns_sql('ctx') . " FROM {user} u JOIN {context} ctx ON ctx.contextlevel=:usercontextlevel AND ctx.instanceid = u.id WHERE u.id " . $sql, $params); diff --git a/admin/tool/uploaduser/classes/process.php b/admin/tool/uploaduser/classes/process.php index 5321f0e5fab..cde592919ae 100644 --- a/admin/tool/uploaduser/classes/process.php +++ b/admin/tool/uploaduser/classes/process.php @@ -155,7 +155,7 @@ class process { 'interests', ); // Include all name fields. - $this->standardfields = array_merge($this->standardfields, get_all_user_name_fields()); + $this->standardfields = array_merge($this->standardfields, \core\user_fields::get_name_fields()); } /** diff --git a/admin/user.php b/admin/user.php index a06b2edc047..0f0eec9582b 100644 --- a/admin/user.php +++ b/admin/user.php @@ -183,13 +183,15 @@ // These columns are always shown in the users list. $requiredcolumns = array('city', 'country', 'lastaccess'); // Extra columns containing the extra user fields, excluding the required columns (city and country, to be specific). - $extracolumns = get_extra_user_fields($context, $requiredcolumns); - // Get all user name fields as an array. - $allusernamefields = get_all_user_name_fields(false, null, null, null, true); + // TODO Does not support custom user profile fields (MDL-70456). + $userfields = \core\user_fields::for_identity($context, false)->excluding(...$requiredcolumns); + $extracolumns = $userfields->get_required_fields(); + // Get all user name fields as an array, but with firstname and lastname first. + $allusernamefields = \core\user_fields::get_name_fields(true); $columns = array_merge($allusernamefields, $extracolumns, $requiredcolumns); foreach ($columns as $column) { - $string[$column] = get_user_field_name($column); + $string[$column] = \core\user_fields::get_display_name($column); if ($sort != $column) { $columnicon = ""; if ($column == "lastaccess") { diff --git a/admin/user/user_bulk_cohortadd.php b/admin/user/user_bulk_cohortadd.php index f5e39475835..ab05021b6a8 100644 --- a/admin/user/user_bulk_cohortadd.php +++ b/admin/user/user_bulk_cohortadd.php @@ -64,7 +64,8 @@ if (count($cohorts) < 2) { } $countries = get_string_manager()->get_list_of_countries(true); -$namefields = get_all_user_name_fields(true); +$userfieldsapi = \core\user_fields::for_name(); +$namefields = $userfieldsapi->get_sql('', false, '', '', false)->selects; foreach ($users as $key => $id) { $user = $DB->get_record('user', array('id' => $id), 'id, ' . $namefields . ', username, email, country, lastaccess, city, deleted'); diff --git a/admin/user/user_bulk_display.php b/admin/user/user_bulk_display.php index ae6cd8a5395..146f4c7dd5d 100644 --- a/admin/user/user_bulk_display.php +++ b/admin/user/user_bulk_display.php @@ -24,7 +24,8 @@ echo $OUTPUT->header(); $countries = get_string_manager()->get_list_of_countries(true); -$namefields = get_all_user_name_fields(true); +$userfieldsapi = \core\user_fields::for_name(); +$namefields = $userfieldsapi->get_sql('', false, '', '', false)->selects; foreach ($users as $key => $id) { $user = $DB->get_record('user', array('id'=>$id), 'id, ' . $namefields . ', username, email, country, lastaccess, city'); $user->fullname = fullname($user, true); diff --git a/admin/webservice/forms.php b/admin/webservice/forms.php index 08ff31a2c3d..c958661e347 100644 --- a/admin/webservice/forms.php +++ b/admin/webservice/forms.php @@ -241,7 +241,8 @@ class web_service_token_form extends moodleform { if ($usertotal < 500) { list($sort, $params) = users_order_by_sql('u'); // User searchable selector - return users who are confirmed, not deleted, not suspended and not a guest. - $sql = 'SELECT u.id, ' . get_all_user_name_fields(true, 'u') . ' + $userfieldsapi = \core\user_fields::for_name(); + $sql = 'SELECT u.id' . $userfieldsapi->get_sql('u')->selects . ' FROM {user} u WHERE u.deleted = 0 AND u.confirmed = 1 diff --git a/availability/condition/profile/classes/condition.php b/availability/condition/profile/classes/condition.php index 106f74cac63..ce65bb0224f 100644 --- a/availability/condition/profile/classes/condition.php +++ b/availability/condition/profile/classes/condition.php @@ -197,7 +197,7 @@ class condition extends \core_availability\condition { $this->customfield); } } else { - $translatedfieldname = get_user_field_name($this->standardfield); + $translatedfieldname = \core\user_fields::get_display_name($this->standardfield); } $context = \context_course::instance($course->id); $a = new \stdClass(); diff --git a/availability/condition/profile/classes/frontend.php b/availability/condition/profile/classes/frontend.php index 373c9a1984a..e5026e57738 100644 --- a/availability/condition/profile/classes/frontend.php +++ b/availability/condition/profile/classes/frontend.php @@ -44,23 +44,23 @@ class frontend extends \core_availability\frontend { \section_info $section = null) { // Standard user fields. $standardfields = array( - 'firstname' => get_user_field_name('firstname'), - 'lastname' => get_user_field_name('lastname'), - 'email' => get_user_field_name('email'), - 'city' => get_user_field_name('city'), - 'country' => get_user_field_name('country'), - 'url' => get_user_field_name('url'), - 'icq' => get_user_field_name('icq'), - 'skype' => get_user_field_name('skype'), - 'aim' => get_user_field_name('aim'), - 'yahoo' => get_user_field_name('yahoo'), - 'msn' => get_user_field_name('msn'), - 'idnumber' => get_user_field_name('idnumber'), - 'institution' => get_user_field_name('institution'), - 'department' => get_user_field_name('department'), - 'phone1' => get_user_field_name('phone1'), - 'phone2' => get_user_field_name('phone2'), - 'address' => get_user_field_name('address') + 'firstname' => \core\user_fields::get_display_name('firstname'), + 'lastname' => \core\user_fields::get_display_name('lastname'), + 'email' => \core\user_fields::get_display_name('email'), + 'city' => \core\user_fields::get_display_name('city'), + 'country' => \core\user_fields::get_display_name('country'), + 'url' => \core\user_fields::get_display_name('url'), + 'icq' => \core\user_fields::get_display_name('icq'), + 'skype' => \core\user_fields::get_display_name('skype'), + 'aim' => \core\user_fields::get_display_name('aim'), + 'yahoo' => \core\user_fields::get_display_name('yahoo'), + 'msn' => \core\user_fields::get_display_name('msn'), + 'idnumber' => \core\user_fields::get_display_name('idnumber'), + 'institution' => \core\user_fields::get_display_name('institution'), + 'department' => \core\user_fields::get_display_name('department'), + 'phone1' => \core\user_fields::get_display_name('phone1'), + 'phone2' => \core\user_fields::get_display_name('phone2'), + 'address' => \core\user_fields::get_display_name('address') ); \core_collator::asort($standardfields); diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index 963f55c9021..3c5395150f4 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -1369,7 +1369,7 @@ class backup_users_structure_step extends backup_structure_step { 'phone2', 'institution', 'department', 'address', 'city', 'country', 'lastip', 'picture', 'url', 'description', 'descriptionformat', 'imagealt', 'auth'); - $anonfields = array_merge($anonfields, get_all_user_name_fields()); + $anonfields = array_merge($anonfields, \core\user_fields::get_name_fields()); // Add anonymized fields to $userfields with custom final element foreach ($anonfields as $field) { diff --git a/badges/classes/output/external_badge.php b/badges/classes/output/external_badge.php index e992d9dbf72..f56bde0f870 100644 --- a/badges/classes/output/external_badge.php +++ b/badges/classes/output/external_badge.php @@ -58,7 +58,8 @@ class external_badge implements renderable { global $DB; // At this point a user has connected a backpack. So, we are going to get // their backpack email rather than their account email. - $namefields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $user = $DB->get_record_sql("SELECT {$namefields}, b.email FROM {user} u INNER JOIN {badge_backpack} b ON u.id = b.userid WHERE b.userid = :userid", array('userid' => $recipient), IGNORE_MISSING); diff --git a/badges/classes/output/issued_badge.php b/badges/classes/output/issued_badge.php index bc70d3bbbf9..c3ce9a9d3c3 100644 --- a/badges/classes/output/issued_badge.php +++ b/badges/classes/output/issued_badge.php @@ -76,7 +76,8 @@ class issued_badge implements renderable { array('hash' => $hash), IGNORE_MISSING); if ($rec) { // Get a recipient from database. - $namefields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $user = $DB->get_record_sql("SELECT u.id, $namefields, u.deleted, u.email FROM {user} u WHERE u.id = :userid", array('userid' => $rec->userid)); $this->recipient = $user; diff --git a/badges/criteria/award_criteria_profile.php b/badges/criteria/award_criteria_profile.php index da76bc87474..bd887db96d1 100644 --- a/badges/criteria/award_criteria_profile.php +++ b/badges/criteria/award_criteria_profile.php @@ -88,7 +88,7 @@ class award_criteria_profile extends award_criteria { if (in_array($field, $existing)) { $checked = true; } - $this->config_options($mform, array('id' => $field, 'checked' => $checked, 'name' => get_user_field_name($field), 'error' => false)); + $this->config_options($mform, array('id' => $field, 'checked' => $checked, 'name' => \core\user_fields::get_display_name($field), 'error' => false)); $none = false; } } @@ -138,7 +138,7 @@ class award_criteria_profile extends award_criteria { if (is_numeric($p['field'])) { $str = $DB->get_field('user_info_field', 'name', array('id' => $p['field'])); } else { - $str = get_user_field_name($p['field']); + $str = \core\user_fields::get_display_name($p['field']); } if (!$str) { $output[] = $OUTPUT->error_text(get_string('error:nosuchfield', 'badges')); diff --git a/badges/recipients.php b/badges/recipients.php index 0dce38df78c..7f1e75f5c31 100644 --- a/badges/recipients.php +++ b/badges/recipients.php @@ -87,7 +87,8 @@ if ($badge->has_manual_award_criteria() && has_capability('moodle/badges:awardba echo $OUTPUT->box($OUTPUT->single_button($url, get_string('award', 'badges')), 'clearfix mdl-align'); } -$namefields = get_all_user_name_fields(true, 'u'); +$userfieldsapi = \core\user_fields::for_name(); +$namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT b.userid, b.dateissued, b.uniquehash, $namefields FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id diff --git a/blocks/activity_results/block_activity_results.php b/blocks/activity_results/block_activity_results.php index fd33925156d..7c19e4ca4d8 100644 --- a/blocks/activity_results/block_activity_results.php +++ b/blocks/activity_results/block_activity_results.php @@ -508,12 +508,12 @@ class block_activity_results extends block_base { // Now grab all the users from the database. $userids = array_merge(array_keys($best), array_keys($worst)); - $fields = array_merge(array('id', 'idnumber'), get_all_user_name_fields()); + $fields = array_merge(array('id', 'idnumber'), \core\user_fields::get_name_fields()); $fields = implode(',', $fields); $users = $DB->get_records_list('user', 'id', $userids, '', $fields); // If configured to view user idnumber, ensure current user can see it. - $extrafields = get_extra_user_fields($this->context); + $extrafields = \core\user_fields::for_identity($this->context)->get_required_fields(); $canviewidnumber = (array_search('idnumber', $extrafields) !== false); // Ready for output! diff --git a/blocks/mentees/block_mentees.php b/blocks/mentees/block_mentees.php index ff326308ef4..9ad847ce23c 100644 --- a/blocks/mentees/block_mentees.php +++ b/blocks/mentees/block_mentees.php @@ -50,7 +50,8 @@ class block_mentees extends block_base { $this->content = new stdClass(); // get all the mentees, i.e. users you have a direct assignment to - $allusernames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allusernames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; if ($usercontexts = $DB->get_records_sql("SELECT c.instanceid, c.instanceid, $allusernames FROM {role_assignments} ra, {context} c, {user} u WHERE ra.userid = ? diff --git a/blocks/online_users/classes/fetcher.php b/blocks/online_users/classes/fetcher.php index a63e0ec73dc..c6777d2d850 100644 --- a/blocks/online_users/classes/fetcher.php +++ b/blocks/online_users/classes/fetcher.php @@ -86,7 +86,8 @@ class fetcher { } $params = array(); - $userfields = \user_picture::fields('u', array('username', 'deleted')); + $userfieldsapi = \core\user_fields::for_userpic()->including('username', 'deleted'); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; // Add this to the SQL to show only group users. if ($currentgroup !== null) { diff --git a/blog/locallib.php b/blog/locallib.php index 40af88d431e..c6d111cea76 100644 --- a/blog/locallib.php +++ b/blog/locallib.php @@ -646,8 +646,8 @@ class blog_listing { if (!$userid) { $userid = $USER->id; } - - $allnamefields = \user_picture::fields('u', null, 'useridalias'); + $userfieldsapi = \core\user_fields::for_userpic(); + $allnamefields = $userfieldsapi->get_sql('u', false, '', 'useridalias', false)->selects; // The query used to locate blog entries is complicated. It will be built from the following components: $requiredfields = "p.*, $allnamefields"; // The SELECT clause. $tables = array('p' => 'post', 'u' => 'user'); // Components of the FROM clause (table_id => table_name). diff --git a/blog/rsslib.php b/blog/rsslib.php index 37d1968e702..878f7a71cbf 100644 --- a/blog/rsslib.php +++ b/blog/rsslib.php @@ -234,7 +234,9 @@ function blog_rss_get_feed($context, $args) { switch ($type) { case 'user': - $info = fullname($DB->get_record('user', array('id' => $id), get_all_user_name_fields(true))); + $userfieldsapi = \core\user_fields::for_name(); + $info = fullname($DB->get_record('user', array('id' => $id), + $userfieldsapi->get_sql('', false, '', '', false)->selects)); break; case 'course': $info = $DB->get_field('course', 'fullname', array('id' => $id)); diff --git a/comment/lib.php b/comment/lib.php index 65d81e30cbd..f9d5cffbb72 100644 --- a/comment/lib.php +++ b/comment/lib.php @@ -551,7 +551,8 @@ class comment { $params = array(); $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15; $start = $page * $perpage; - $ufields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; list($componentwhere, $component) = $this->get_component_select_sql('c'); if ($component) { diff --git a/comment/locallib.php b/comment/locallib.php index 512ff49e80c..0fb00f95a39 100644 --- a/comment/locallib.php +++ b/comment/locallib.php @@ -61,7 +61,8 @@ class comment_manager { } $comments = array(); - $usernamefields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT c.id, c.contextid, c.itemid, c.component, c.commentarea, c.userid, c.content, $usernamefields, c.timecreated FROM {comments} c JOIN {user} u @@ -75,7 +76,7 @@ class comment_manager { $item->time = userdate($item->timecreated); $item->content = format_text($item->content, FORMAT_MOODLE, $formatoptions); // Unset fields not related to the comment - foreach (get_all_user_name_fields() as $namefield) { + foreach (\core\user_fields::get_name_fields() as $namefield) { unset($item->$namefield); } unset($item->timecreated); diff --git a/course/classes/category.php b/course/classes/category.php index 187f85123ed..e538368e02e 100644 --- a/course/classes/category.php +++ b/course/classes/category.php @@ -1033,7 +1033,8 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr list($sql2, $params2) = $DB->get_in_or_equal($managerroles, SQL_PARAMS_NAMED, 'rid'); list($sort, $sortparams) = users_order_by_sql('u'); $notdeleted = array('notdeleted' => 0); - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT ra.contextid, ra.id AS raid, r.id AS roleid, r.name AS rolename, r.shortname AS roleshortname, rn.name AS rolecoursealias, u.id, u.username, $allnames diff --git a/course/recent_form.php b/course/recent_form.php index d3056ea9fa5..b4c870dbb65 100644 --- a/course/recent_form.php +++ b/course/recent_form.php @@ -77,9 +77,12 @@ class recent_form extends moodleform { $options[0] = get_string('allparticipants'); $options[$CFG->siteguest] = get_string('guestuser'); + $userfieldsapi = \core\user_fields::for_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + if (isset($groupoptions[0])) { // can see all enrolled users - if ($enrolled = get_enrolled_users($context, null, 0, user_picture::fields('u'))) { + if ($enrolled = get_enrolled_users($context, null, 0, $ufields)) { foreach ($enrolled as $euser) { $options[$euser->id] = fullname($euser, $viewfullnames); } @@ -87,7 +90,7 @@ class recent_form extends moodleform { } else { // can see users from some groups only foreach ($groupoptions as $groupid=>$unused) { - if ($enrolled = get_enrolled_users($context, null, $groupid, user_picture::fields('u'))) { + if ($enrolled = get_enrolled_users($context, null, $groupid, $ufields)) { foreach ($enrolled as $euser) { if (!array_key_exists($euser->id, $options)) { $options[$euser->id] = fullname($euser, $viewfullnames); diff --git a/enrol/ajax.php b/enrol/ajax.php index 4134c397280..1bd9cc4465c 100644 --- a/enrol/ajax.php +++ b/enrol/ajax.php @@ -94,7 +94,8 @@ switch ($action) { $search = optional_param('search', '', PARAM_RAW); $page = optional_param('page', 0, PARAM_INT); $outcome->response = $manager->search_other_users($search, $searchanywhere, $page); - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($context, false); $useroptions = array(); // User is not enrolled, either link to site profile or do not link at all. if (has_capability('moodle/user:viewdetails', context_system::instance())) { diff --git a/enrol/externallib.php b/enrol/externallib.php index 8281f3cc6aa..e2cfb216587 100644 --- a/enrol/externallib.php +++ b/enrol/externallib.php @@ -560,7 +560,8 @@ class core_enrol_external extends external_api { // Add also extra user fields. $requiredfields = array_merge( ['id', 'fullname', 'profileimageurl', 'profileimageurlsmall'], - get_extra_user_fields($context) + // TODO Does not support custom user profile fields (MDL-70456). + \core\user_fields::get_identity_fields($context, false) ); foreach ($users['users'] as $id => $user) { // Note: We pass the course here to validate that the current user can at least view user details in this course. @@ -652,7 +653,8 @@ class core_enrol_external extends external_api { // Add also extra user fields. $requiredfields = array_merge( ['id', 'fullname', 'profileimageurl', 'profileimageurlsmall'], - get_extra_user_fields($context) + // TODO Does not support custom user profile fields (MDL-70456). + \core\user_fields::get_identity_fields($context, false) ); foreach ($users['users'] as $user) { if ($userdetails = user_get_user_details($user, $course, $requiredfields)) { diff --git a/enrol/locallib.php b/enrol/locallib.php index 0a46f0f02ef..cd6f9fd8788 100644 --- a/enrol/locallib.php +++ b/enrol/locallib.php @@ -238,7 +238,8 @@ class course_enrolment_manager { list($instancessql, $params, $filter) = $this->get_instance_sql(); list($filtersql, $moreparams) = $this->get_filter_sql(); $params += $moreparams; - $extrafields = get_extra_user_fields($this->get_context()); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->get_context(), false); $extrafields[] = 'lastaccess'; $ufields = user_picture::fields('u', $extrafields); $sql = "SELECT DISTINCT $ufields, COALESCE(ul.timeaccess, 0) AS lastcourseaccess @@ -268,7 +269,8 @@ class course_enrolment_manager { global $DB; // Search condition. - $extrafields = get_extra_user_fields($this->get_context()); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->get_context(), false); list($sql, $params) = users_search_sql($this->searchfilter, 'u', true, $extrafields); // Role condition. @@ -341,7 +343,8 @@ class course_enrolment_manager { list($ctxcondition, $params) = $DB->get_in_or_equal($this->context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'ctx'); $params['courseid'] = $this->course->id; $params['cid'] = $this->course->id; - $extrafields = get_extra_user_fields($this->get_context()); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->get_context(), false); $ufields = user_picture::fields('u', $extrafields); $sql = "SELECT ra.id as raid, ra.contextid, ra.component, ctx.contextlevel, ra.roleid, $ufields, coalesce(u.lastaccess,0) AS lastaccess @@ -379,8 +382,9 @@ class course_enrolment_manager { $tests = array("u.id <> :guestid", 'u.deleted = 0', 'u.confirmed = 1'); $params = array('guestid' => $CFG->siteguest); if (!empty($search)) { - $conditions = get_extra_user_fields($this->get_context()); - foreach (get_all_user_name_fields() as $field) { + // TODO Does not support custom user profile fields (MDL-70456). + $conditions = \core\user_fields::get_identity_fields($this->get_context(), false); + foreach (\core\user_fields::get_name_fields() as $field) { $conditions[] = 'u.'.$field; } $conditions[] = $DB->sql_fullname('u.firstname', 'u.lastname'); @@ -399,7 +403,9 @@ class course_enrolment_manager { } $wherecondition = implode(' AND ', $tests); - $extrafields = get_extra_user_fields($this->get_context(), array('username', 'lastaccess')); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($this->get_context(), false)->excluding('username', 'lastaccess'); + $extrafields = $userfieldsapi->get_required_fields(); $extrafields[] = 'username'; $extrafields[] = 'lastaccess'; $extrafields[] = 'maildisplay'; @@ -1046,7 +1052,8 @@ class course_enrolment_manager { $context = $this->get_context(); $now = time(); - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($context, false); $users = array(); foreach ($userroles as $userrole) { @@ -1124,7 +1131,8 @@ class course_enrolment_manager { $canmanagegroups = has_capability('moodle/course:managegroups', $context); $url = new moodle_url($pageurl, $this->get_url_params()); - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($context, false); $enabledplugins = $this->get_enrolment_plugins(true); @@ -1301,7 +1309,8 @@ class course_enrolment_manager { list($instancesql, $instanceparams) = $DB->get_in_or_equal(array_keys($instances), SQL_PARAMS_NAMED, 'instanceid0000'); } - $userfields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; list($idsql, $idparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'userid0000'); list($sort, $sortparams) = users_order_by_sql('u'); diff --git a/enrol/manual/classes/enrol_users_form.php b/enrol/manual/classes/enrol_users_form.php index 257a5c2a995..8fde2485872 100644 --- a/enrol/manual/classes/enrol_users_form.php +++ b/enrol/manual/classes/enrol_users_form.php @@ -93,7 +93,8 @@ class enrol_manual_enrol_users_form extends moodleform { 'courseid' => $course->id, 'enrolid' => $instance->id, 'perpage' => $CFG->maxusersperpage, - 'userfields' => implode(',', get_extra_user_fields($context)) + // TODO Does not support custom user profile fields (MDL-70456). + 'userfields' => implode(',', \core\user_fields::get_identity_fields($context, false)) ); $mform->addElement('autocomplete', 'userlist', get_string('selectusers', 'enrol_manual'), array(), $options); diff --git a/enrol/otherusers.php b/enrol/otherusers.php index ede4a35613a..82846e03dda 100644 --- a/enrol/otherusers.php +++ b/enrol/otherusers.php @@ -54,9 +54,10 @@ $userdetails = array ( 'firstname' => get_string('firstname'), 'lastname' => get_string('lastname'), ); -$extrafields = get_extra_user_fields($context); +// TODO Does not support custom user profile fields (MDL-70456). +$extrafields = \core\user_fields::get_identity_fields($context, false); foreach ($extrafields as $field) { - $userdetails[$field] = get_user_field_name($field); + $userdetails[$field] = \core\user_fields::get_display_name($field); } $fields = array( diff --git a/enrol/self/lib.php b/enrol/self/lib.php index 4807f49e0a7..9131f408d85 100644 --- a/enrol/self/lib.php +++ b/enrol/self/lib.php @@ -1021,7 +1021,8 @@ class enrol_self_plugin extends enrol_plugin { // We only use the first user. $i = 0; do { - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $rusers = get_role_users($croles[$i], $context, true, 'u.id, u.confirmed, u.username, '. $allnames . ', u.email, r.sortorder, ra.id', 'r.sortorder, ra.id ASC, ' . $sort, null, '', '', '', '', $sortparams); $i++; diff --git a/enrol/self/locallib.php b/enrol/self/locallib.php index 850555e2990..e360616f792 100644 --- a/enrol/self/locallib.php +++ b/enrol/self/locallib.php @@ -82,7 +82,9 @@ class enrol_self_enrol_form extends moodleform { $mform->addElement('password', 'enrolpassword', get_string('password', 'enrol_self'), array('id' => 'enrolpassword_'.$instance->id)); $context = context_course::instance($this->instance->courseid); - $keyholders = get_users_by_capability($context, 'enrol/self:holdkey', user_picture::fields('u')); + $userfieldsapi = \core\user_fields::for_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $keyholders = get_users_by_capability($context, 'enrol/self:holdkey', $ufields); $keyholdercount = 0; foreach ($keyholders as $keyholder) { $keyholdercount++; diff --git a/grade/report/grader/ajax_callbacks.php b/grade/report/grader/ajax_callbacks.php index 8260320ed94..a7e7ee81216 100644 --- a/grade/report/grader/ajax_callbacks.php +++ b/grade/report/grader/ajax_callbacks.php @@ -91,7 +91,8 @@ switch ($action) { } if ($errorstr) { - $user = $DB->get_record('user', array('id' => $userid), 'id, ' . get_all_user_name_fields(true)); + $userfieldsapi = \core\user_fields::for_name(); + $user = $DB->get_record('user', array('id' => $userid), 'id' . $userfieldsapi->get_sql()->selects); $gradestr = new stdClass(); $gradestr->username = fullname($user); $gradestr->itemname = $grade_item->get_name(); diff --git a/grade/report/grader/lib.php b/grade/report/grader/lib.php index 86f968b099b..4409d500963 100644 --- a/grade/report/grader/lib.php +++ b/grade/report/grader/lib.php @@ -293,7 +293,8 @@ class grade_report_grader extends grade_report { } if ($errorstr) { - $userfields = 'id, ' . get_all_user_name_fields(true); + $userfieldsapi = \core\user_fields::for_name(); + $userfields = 'id, ' . $userfieldsapi->get_sql('', false, '', '', false)->selects; $user = $DB->get_record('user', array('id' => $userid), $userfields); $gradestr = new stdClass(); $gradestr->username = fullname($user, $viewfullnames); @@ -437,7 +438,9 @@ class grade_report_grader extends grade_report { list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->context, '', 0, $showonlyactiveenrol); // Fields we need from the user table. - $userfields = user_picture::fields('u', get_extra_user_fields($this->context)); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($this->context, false)->with_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; // We want to query both the current context and parent contexts. list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($this->context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx'); @@ -657,7 +660,8 @@ class grade_report_grader extends grade_report { $strfeedback = $this->get_lang_string("feedback"); $strgrade = $this->get_lang_string('grade'); - $extrafields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->context, false); $arrows = $this->get_sort_arrows($extrafields); @@ -1942,7 +1946,7 @@ class grade_report_grader extends grade_report { } $arrows['studentname'] = ''; - $requirednames = order_in_string(get_all_user_name_fields(), $nameformat); + $requirednames = order_in_string(\core\user_fields::get_name_fields(), $nameformat); if (!empty($requirednames)) { foreach ($requirednames as $name) { $arrows['studentname'] .= html_writer::link( @@ -1959,7 +1963,7 @@ class grade_report_grader extends grade_report { foreach ($extrafields as $field) { $fieldlink = html_writer::link(new moodle_url($this->baseurl, - array('sortitemid'=>$field)), get_user_field_name($field)); + array('sortitemid'=>$field)), \core\user_fields::get_display_name($field)); $arrows[$field] = $fieldlink; if ($field == $this->sortitemid) { diff --git a/grade/report/history/classes/helper.php b/grade/report/history/classes/helper.php index 00f4c6d429f..050e6c4fa21 100644 --- a/grade/report/history/classes/helper.php +++ b/grade/report/history/classes/helper.php @@ -131,7 +131,8 @@ class helper { global $DB, $USER; // Fields we need from the user table. - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($context, false); $params = array(); if (!empty($search)) { list($filtersql, $params) = users_search_sql($search, 'u', true, $extrafields); @@ -140,7 +141,8 @@ class helper { $filtersql = ''; } - $ufields = \user_picture::fields('u', $extrafields).',u.username'; + $userfieldsapi = \core\user_fields::for_userpic()->including(...(array_merge($extrafields, ['username']))); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; if ($count) { $select = "SELECT COUNT(DISTINCT u.id) "; $orderby = ""; @@ -201,7 +203,8 @@ class helper { $groupwheresql = " AND gm.groupid $insql "; } - $ufields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT u.id, $ufields FROM {user} u JOIN {grade_grades_history} ggh ON ggh.usermodified = u.id diff --git a/grade/report/history/classes/output/tablelog.php b/grade/report/history/classes/output/tablelog.php index 5cc89df88f1..5cd19fbe9ee 100644 --- a/grade/report/history/classes/output/tablelog.php +++ b/grade/report/history/classes/output/tablelog.php @@ -140,7 +140,8 @@ class tablelog extends \table_sql implements \renderable { * Setup the headers for the html table. */ protected function define_table_columns() { - $extrafields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->context, false); // Define headers and columns. $cols = array( @@ -394,17 +395,19 @@ class tablelog extends \table_sql implements \renderable { gi.itemtype, gi.itemmodule, gi.iteminstance, gi.itemnumber, '; // Add extra user fields that we need for the graded user. - $extrafields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->context, false); foreach ($extrafields as $field) { $fields .= 'u.' . $field . ', '; } - $gradeduserfields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $gradeduserfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $fields .= $gradeduserfields . ', '; $groupby = $fields; // Add extra user fields that we need for the grader user. - $fields .= get_all_user_name_fields(true, 'ug', '', 'grader'); - $groupby .= get_all_user_name_fields(true, 'ug'); + $fields .= $userfieldsapi->get_sql('ug', false, 'grader', '', false)->selects; + $groupby .= $userfieldsapi->get_sql('ug', false, '', '', false)->selects; // Filtering on revised grades only. $revisedonly = !empty($this->filters->revisedonly); diff --git a/grade/report/history/users_ajax.php b/grade/report/history/users_ajax.php index 387a1aab186..af43ee0a3d2 100644 --- a/grade/report/history/users_ajax.php +++ b/grade/report/history/users_ajax.php @@ -51,7 +51,8 @@ $users = \gradereport_history\helper::get_users($context, $search, $page, 25); $outcome->response = array('users' => array()); $outcome->response['totalusers'] = \gradereport_history\helper::get_users_count($context, $search);; -$extrafields = get_extra_user_fields($context); +// TODO Does not support custom user profile fields (MDL-70456). +$extrafields = \core\user_fields::get_identity_fields($context, false); $useroptions = array('link' => false, 'visibletoscreenreaders' => false); // Format the user record. diff --git a/group/autogroup.php b/group/autogroup.php index 9d69597b264..87df71ecdaf 100644 --- a/group/autogroup.php +++ b/group/autogroup.php @@ -99,7 +99,8 @@ if ($editform->is_cancelled()) { // Display only active users if the option was selected or they do not have the capability to view suspended users. $onlyactive = !empty($data->includeonlyactiveenrol) || !has_capability('moodle/course:viewsuspendedusers', $context); - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($context, false); $users = groups_get_potential_members($data->courseid, $data->roleid, $source, $orderby, !empty($data->notingroup), $onlyactive, $extrafields); $usercnt = count($users); diff --git a/group/index.php b/group/index.php index e2aa22b35e7..b61e87d07a0 100644 --- a/group/index.php +++ b/group/index.php @@ -81,9 +81,12 @@ switch ($action) { case 'ajax_getmembersingroup': $roles = array(); - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($context, false)->with_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $extrafields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); if ($groupmemberroles = groups_get_members_by_role($groupids[0], $courseid, - 'u.id, ' . user_picture::fields('u', $extrafields))) { + 'u.id, ' . $userfields)) { $viewfullnames = has_capability('moodle/site:viewfullnames', $context); @@ -202,9 +205,12 @@ if ($groups) { // Get list of group members to render if there is a single selected group. $members = array(); if ($singlegroup) { - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($context, false)->with_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $extrafields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); if ($groupmemberroles = groups_get_members_by_role(reset($groupids), $courseid, - 'u.id, ' . user_picture::fields('u', $extrafields))) { + 'u.id, ' . $userfields)) { $viewfullnames = has_capability('moodle/site:viewfullnames', $context); diff --git a/group/lib.php b/group/lib.php index 98330166fe5..ebeab85b3b6 100644 --- a/group/lib.php +++ b/group/lib.php @@ -848,7 +848,8 @@ function groups_get_potential_members($courseid, $roleid = null, $source = null, } } - $allusernamefields = user_picture::fields('u', $extrafields); + $userfieldsapi = \core\user_fields::for_userpic()->including(...$extrafields); + $allusernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT DISTINCT u.id, u.username, $allusernamefields, u.idnumber FROM {user} u JOIN ($esql) e ON e.id = u.id diff --git a/group/overview.php b/group/overview.php index fcbf697da24..ff73970c4c7 100644 --- a/group/overview.php +++ b/group/overview.php @@ -110,8 +110,11 @@ if ($groupingid) { list($sort, $sortparams) = users_order_by_sql('u'); -$extrafields = get_extra_user_fields($context); -$allnames = 'u.id, ' . user_picture::fields('u', $extrafields); +// TODO Does not support custom user profile fields (MDL-70456). +$userfieldsapi = \core\user_fields::for_identity($context, false)->with_userpic(); +$userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; +$extrafields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); +$allnames = 'u.id, ' . $userfields; $sql = "SELECT g.id AS groupid, gg.groupingid, u.id AS userid, $allnames, u.idnumber, u.username FROM {groups} g diff --git a/lib/accesslib.php b/lib/accesslib.php index 39f110e2553..7699a691e7b 100644 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -3923,7 +3923,8 @@ function get_role_users($roleid, context $context, $parent = false, $fields = '' global $DB; if (empty($fields)) { - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $fields = 'u.id, u.confirmed, u.username, '. $allnames . ', ' . 'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '. 'u.country, u.picture, u.idnumber, u.department, u.institution, '. diff --git a/lib/adminlib.php b/lib/adminlib.php index c3ead083eaa..923f9daab3a 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -4311,7 +4311,8 @@ class admin_setting_users_with_capability extends admin_setting_configmultiselec 'This is unexpected, and a problem because there is no way to pass these ' . 'parameters to get_users_by_capability. See MDL-34657.'); } - $userfields = 'u.id, u.username, ' . get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $userfields = 'u.id, u.username, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects; $users = get_users_by_capability(context_system::instance(), $this->capability, $userfields, $sort); $this->choices = array( '$@NONE@$' => get_string('nobody'), diff --git a/lib/authlib.php b/lib/authlib.php index d1604e6a18a..26099d3f807 100644 --- a/lib/authlib.php +++ b/lib/authlib.php @@ -1102,7 +1102,7 @@ function signup_setup_new_user($user) { $user->secret = random_string(15); $user->auth = $CFG->registerauth; // Initialize alternate name fields to empty strings. - $namefields = array_diff(get_all_user_name_fields(), useredit_get_required_name_fields()); + $namefields = array_diff(\core\user_fields::get_name_fields(), useredit_get_required_name_fields()); foreach ($namefields as $namefield) { $user->$namefield = ''; } diff --git a/lib/badgeslib.php b/lib/badgeslib.php index d67c1deeeb6..3923aa9d1b1 100644 --- a/lib/badgeslib.php +++ b/lib/badgeslib.php @@ -145,7 +145,7 @@ function badges_notify_badge_award(badge $badge, $userid, $issued, $filepathhash $userfrom = new stdClass(); $userfrom->id = $admin->id; $userfrom->email = !empty($CFG->badges_defaultissuercontact) ? $CFG->badges_defaultissuercontact : $admin->email; - foreach (get_all_user_name_fields() as $addname) { + foreach (\core\user_fields::get_name_fields() as $addname) { $userfrom->$addname = !empty($CFG->badges_defaultissuername) ? '' : $admin->$addname; } $userfrom->firstname = !empty($CFG->badges_defaultissuername) ? $CFG->badges_defaultissuername : $admin->firstname; diff --git a/lib/classes/check/access/riskadmin.php b/lib/classes/check/access/riskadmin.php index 7c0729cfaad..77bdec273d6 100644 --- a/lib/classes/check/access/riskadmin.php +++ b/lib/classes/check/access/riskadmin.php @@ -66,7 +66,8 @@ class riskadmin extends check { */ public function get_result(): result { global $DB, $CFG; - $userfields = \user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT $userfields FROM {user} u WHERE u.id IN ($CFG->siteadmins)"; diff --git a/lib/classes/check/access/riskbackup_result.php b/lib/classes/check/access/riskbackup_result.php index a234072c8d9..ee4e75bbf80 100644 --- a/lib/classes/check/access/riskbackup_result.php +++ b/lib/classes/check/access/riskbackup_result.php @@ -165,7 +165,8 @@ class riskbackup_result extends \core\check\result { 'context1' => CONTEXT_COURSE, 'context2' => CONTEXT_COURSE, ]; - $userfields = \user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $rs = $DB->get_recordset_sql(" SELECT DISTINCT $userfields, ra.contextid, diff --git a/lib/classes/check/access/riskxss_result.php b/lib/classes/check/access/riskxss_result.php index 7097db6de21..2041825cab0 100644 --- a/lib/classes/check/access/riskxss_result.php +++ b/lib/classes/check/access/riskxss_result.php @@ -90,7 +90,8 @@ class riskxss_result extends \core\check\result { global $CFG, $DB; - $userfields = \user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $users = $DB->get_records_sql("SELECT DISTINCT $userfields $this->sqlfrom", $this->params); foreach ($users as $uid => $user) { $url = "$CFG->wwwroot/user/view.php?id=$user->id"; diff --git a/lib/classes/task/send_failed_login_notifications_task.php b/lib/classes/task/send_failed_login_notifications_task.php index 7af752e995b..019e0ba625f 100644 --- a/lib/classes/task/send_failed_login_notifications_task.php +++ b/lib/classes/task/send_failed_login_notifications_task.php @@ -115,7 +115,8 @@ class send_failed_login_notifications_task extends scheduled_task { // Now, select all the login error logged records belonging to the ips and infos // since lastnotifyfailure, that we have stored in the cache_flags table. - $namefields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT * FROM ( SELECT l.*, u.username, $namefields FROM {" . $logtable . "} l diff --git a/lib/classes/task/send_new_user_passwords_task.php b/lib/classes/task/send_new_user_passwords_task.php index bdf55be6781..ded1fb2caa9 100644 --- a/lib/classes/task/send_new_user_passwords_task.php +++ b/lib/classes/task/send_new_user_passwords_task.php @@ -47,7 +47,8 @@ class send_new_user_passwords_task extends scheduled_task { // Generate new password emails for users - ppl expect these generated asap. if ($DB->count_records('user_preferences', array('name' => 'create_password', 'value' => '1'))) { mtrace('Creating passwords for new users...'); - $usernamefields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $newusers = $DB->get_recordset_sql("SELECT u.id as id, u.email, u.auth, u.deleted, u.suspended, u.emailstop, u.mnethostid, u.mailformat, $usernamefields, u.username, u.lang, diff --git a/lib/classes/user.php b/lib/classes/user.php index d5c00bb978e..eb15369318c 100644 --- a/lib/classes/user.php +++ b/lib/classes/user.php @@ -252,20 +252,10 @@ class core_user { $extraparams = []; // TODO Does not support custom user profile fields (MDL-70456). - $extra = \core\user_fields::get_identity_fields(null, false); - - // We need the username just to skip guests. - $extrafieldlist = $extra; - if (!in_array('username', $extra)) { - $extrafieldlist[] = 'username'; - } - // The deleted flag will always be false because users_search_sql excludes deleted users, - // but it must be present or it causes PHP warnings in some functions below. - if (!in_array('deleted', $extra)) { - $extrafieldlist[] = 'deleted'; - } - $selectfields = \user_picture::fields('u', - array_merge(get_all_user_name_fields(), $extrafieldlist)); + $userfieldsapi = \core\user_fields::for_identity(null, false)->with_userpic()->with_name() + ->including('username', 'deleted'); + $selectfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $extra = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); $index = 1; foreach ($extra as $fieldname) { diff --git a/lib/completionlib.php b/lib/completionlib.php index 2258259f865..61fce2f2f46 100644 --- a/lib/completionlib.php +++ b/lib/completionlib.php @@ -1190,11 +1190,10 @@ class completion_info { context_course::instance($this->course->id), 'moodle/course:isincompletionreports', $groupid, true); - $allusernames = get_all_user_name_fields(true, 'u'); - $sql = 'SELECT u.id, u.idnumber, ' . $allusernames; - if ($extracontext) { - $sql .= get_extra_user_fields_sql($extracontext, 'u', '', array('idnumber')); - } + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($extracontext, false)->with_name(); + $allusernames = $userfieldsapi->get_sql('u')->selects; + $sql = 'SELECT u.id, u.idnumber ' . $allusernames; $sql .= ' FROM (' . $enrolledsql . ') eu JOIN {user} u ON u.id = eu.id'; if ($where) { diff --git a/lib/datalib.php b/lib/datalib.php index 613f82f68e7..68d89f89f8a 100644 --- a/lib/datalib.php +++ b/lib/datalib.php @@ -368,7 +368,8 @@ function users_order_by_sql($usertablealias = '', $search = null, context $conte $params[$paramkey] = $search; $paramkey++; - $fieldstocheck = array_merge(array('firstname', 'lastname'), get_extra_user_fields($context)); + // TODO Does not support custom user profile fields (MDL-70456). + $fieldstocheck = array_merge(array('firstname', 'lastname'), \core\user_fields::get_identity_fields($context, false)); foreach ($fieldstocheck as $key => $field) { $exactconditions[] = 'LOWER(' . $tableprefix . $field . ') = LOWER(:' . $paramkey . ')'; $params[$paramkey] = $search; @@ -512,14 +513,11 @@ function get_users_listing($sort='lastaccess', $dir='ASC', $page=0, $recordsperp // If a context is specified, get extra user fields that the current user // is supposed to see. - $extrafields = ''; - if ($extracontext) { - $extrafields = get_extra_user_fields_sql($extracontext, '', '', - array('id', 'username', 'email', 'firstname', 'lastname', 'city', 'country', - 'lastaccess', 'confirmed', 'mnethostid')); - } - $namefields = get_all_user_name_fields(true); - $extrafields = "$extrafields, $namefields"; + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($extracontext, false)->with_name() + ->excluding('id', 'username', 'email', 'firstname', 'lastname', 'city', 'country', + 'lastaccess', 'confirmed', 'mnethostid'); + $extrafields = $userfields->get_sql()->selects; // warning: will return UNCONFIRMED USERS return $DB->get_records_sql("SELECT id, username, email, city, country, lastaccess, confirmed, mnethostid, suspended $extrafields diff --git a/lib/deprecatedlib.php b/lib/deprecatedlib.php index 8cf3efa61b1..8492e23b4fd 100644 --- a/lib/deprecatedlib.php +++ b/lib/deprecatedlib.php @@ -3083,8 +3083,10 @@ function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $r $joins = array('FROM {user} u'); $wheres = array(); - $userfields = get_extra_user_fields($context); - $userfieldssql = user_picture::fields('u', $userfields); + // TODO Does not support custom user profile fields (MDL-70456). + $userfields = \core\user_fields::get_identity_fields($context, false); + $userfieldsapi = \core\user_fields::for_userpic()->including(...$userfields); + $userfieldssql = $userfieldsapi->get_sql('u', false, '', '', false)->selects; if ($isfrontpage) { $select = "SELECT $userfieldssql, u.lastaccess"; diff --git a/lib/grouplib.php b/lib/grouplib.php index 6aa89065f46..8b7d8533fff 100644 --- a/lib/grouplib.php +++ b/lib/grouplib.php @@ -1321,7 +1321,8 @@ function groups_user_groups_visible($course, $userid, $cm = null) { function groups_get_groups_members($groupsids, $extrafields=null, $sort='lastname ASC') { global $DB; - $userfields = user_picture::fields('u', $extrafields); + $userfieldsapi = \core\user_fields::for_userpic()->including(...($extrafields ?? [])); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; list($insql, $params) = $DB->get_in_or_equal($groupsids); return $DB->get_records_sql("SELECT $userfields diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 1c474c88be0..f6fea8ee107 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -3540,7 +3540,7 @@ function fullname($user, $override=false) { } // Get all of the name fields. - $allnames = get_all_user_name_fields(); + $allnames = \core\user_fields::get_name_fields(); if ($CFG->debugdeveloper) { foreach ($allnames as $allname) { if (!property_exists($user, $allname)) { @@ -3645,7 +3645,10 @@ function fullname($user, $override=false) { * @return object User name fields. */ function username_load_fields_from_object($addtoobject, $secondobject, $prefix = null, $additionalfields = null) { - $fields = get_all_user_name_fields(false, null, $prefix); + $fields = \core\user_fields::get_name_fields(); + foreach ($fields as &$field) { + $field = $prefix . $field; + } if ($additionalfields) { // Additional fields can specify their own 'alias' such as 'id' => 'userid'. This checks to see if // the key is a number and then sets the key to the array value. diff --git a/lib/outputcomponents.php b/lib/outputcomponents.php index 8ca4a3f52ce..a3d3b1f3c86 100644 --- a/lib/outputcomponents.php +++ b/lib/outputcomponents.php @@ -230,7 +230,8 @@ class user_picture implements renderable { } if ($needrec) { - $this->user = $DB->get_record('user', array('id'=>$user->id), self::fields(), MUST_EXIST); + $this->user = $DB->get_record('user', array('id'=>$user->id), + implode(',', \core\user_fields::get_picture_fields()), MUST_EXIST); } else { $this->user = clone($user); } diff --git a/lib/tablelib.php b/lib/tablelib.php index be904c2d766..37b8ee5538c 100644 --- a/lib/tablelib.php +++ b/lib/tablelib.php @@ -608,7 +608,7 @@ class flexible_table { if (isset($this->columns[$column])) { continue; // This column is OK. } - if (in_array($column, get_all_user_name_fields()) && + if (in_array($column, \core\user_fields::get_name_fields()) && isset($this->columns['fullname'])) { continue; // This column is OK. } @@ -1235,7 +1235,7 @@ class flexible_table { $nameformat = get_string('fullnamedisplay'); } - $requirednames = order_in_string(get_all_user_name_fields(), $nameformat); + $requirednames = order_in_string(\core\user_fields::get_name_fields(), $nameformat); if (!empty($requirednames)) { if ($this->is_sortable($column)) { @@ -1315,7 +1315,7 @@ class flexible_table { $sortdata = array_merge([$sortby => $sortorder], $sortdata); } - $usernamefields = get_all_user_name_fields(); + $usernamefields = \core\user_fields::get_name_fields(); $sortdata = array_filter($sortdata, function($sortby) use ($usernamefields) { $isvalidsort = $sortby && $this->is_sortable($sortby); $isvalidsort = $isvalidsort && empty($this->prefs['collapse'][$sortby]); diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php index a21ee933c8f..89751b40f79 100644 --- a/lib/tests/moodlelib_test.php +++ b/lib/tests/moodlelib_test.php @@ -3788,7 +3788,7 @@ class core_moodlelib_testcase extends advanced_testcase { // User information for showing a picture. $user = new stdClass(); - $additionalfields = explode(',', user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $user = username_load_fields_from_object($user, $userinfo, null, $additionalfields); $user->id = $userinfo->userid; $expectedarray = new stdClass(); @@ -3817,7 +3817,7 @@ class core_moodlelib_testcase extends advanced_testcase { // Return an object with user picture information. $user = new stdClass(); - $additionalfields = explode(',', user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $user = username_load_fields_from_object($user, $userinfo, 'author', $additionalfields); $user->id = $userinfo->userid; $expectedarray = new stdClass(); diff --git a/lib/tests/outputcomponents_test.php b/lib/tests/outputcomponents_test.php index 0ff1c55c392..00ecbe9a559 100644 --- a/lib/tests/outputcomponents_test.php +++ b/lib/tests/outputcomponents_test.php @@ -33,6 +33,11 @@ require_once($CFG->libdir . '/outputcomponents.php'); */ class core_outputcomponents_testcase extends advanced_testcase { + /** + * Tests user_picture::fields. + * + * @deprecated since Moodle 3.11 MDL-45242 + */ public function test_fields_aliasing() { $fields = user_picture::fields(); $fields = array_map('trim', explode(',', $fields)); @@ -60,10 +65,16 @@ class core_outputcomponents_testcase extends advanced_testcase { $this->assertContains($expected, $returned, "Expected pattern '$expected' not returned"); } $this->assertContains("custom1 AS prefixcustom1", $returned, "Expected pattern 'custom1 AS prefixcustom1' not returned"); + + // Deprecation warnings for user_picture::fields. + $this->assertDebuggingCalledCount(2); } + /** + * Tests user_picture::unalias. + */ public function test_fields_unaliasing() { - $fields = user_picture::fields(); + $fields = implode(',', \core\user_fields::get_picture_fields()); $fields = array_map('trim', explode(',', $fields)); $fakerecord = new stdClass(); @@ -86,8 +97,11 @@ class core_outputcomponents_testcase extends advanced_testcase { $this->assertSame('Value of custom1', $returned->custom1); } + /** + * Tests user_picture::unalias with null values. + */ public function test_fields_unaliasing_null() { - $fields = user_picture::fields(); + $fields = implode(',', \core\user_fields::get_picture_fields()); $fields = array_map('trim', explode(',', $fields)); $fakerecord = new stdClass(); diff --git a/message/classes/api.php b/message/classes/api.php index af3a7ec63e8..dcc69c05f25 100644 --- a/message/classes/api.php +++ b/message/classes/api.php @@ -111,8 +111,9 @@ class api { global $DB; // Get the user fields we want. - $ufields = \user_picture::fields('u', array('lastaccess'), 'userfrom_id', 'userfrom_'); - $ufields2 = \user_picture::fields('u2', array('lastaccess'), 'userto_id', 'userto_'); + $userfieldsapi = \core\user_fields::for_userpic()->including('lastaccess'); + $ufields = $userfieldsapi->get_sql('u', false, 'userfrom_', '', false)->selects; + $ufields2 = $userfieldsapi->get_sql('u2', false, 'userto_', '', false)->selects; // Add the uniqueid column to make each row unique and avoid SQL errors. $uniqueidsql = $DB->sql_concat('m.id', "'_'", 'm.useridfrom', "'_'", 'mcm.userid'); @@ -1022,7 +1023,8 @@ class api { debugging('\core_message\api::get_contacts_with_unread_message_count is deprecated and no longer used', DEBUG_DEVELOPER); - $userfields = \user_picture::fields('u', array('lastaccess')); + $userfieldsapi = \core\user_fields::for_userpic()->including('lastaccess'); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $unreadcountssql = "SELECT $userfields, count(m.id) as messagecount FROM {message_contacts} mc INNER JOIN {user} u @@ -1063,7 +1065,8 @@ class api { debugging('\core_message\api::get_non_contacts_with_unread_message_count is deprecated and no longer used', DEBUG_DEVELOPER); - $userfields = \user_picture::fields('u', array('lastaccess')); + $userfieldsapi = \core\user_fields::for_userpic()->including('lastaccess'); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $unreadcountssql = "SELECT $userfields, count(m.id) as messagecount FROM {user} u INNER JOIN {messages} m @@ -1885,7 +1888,8 @@ class api { public static function get_blocked_users($userid) { global $DB; - $userfields = \user_picture::fields('u', array('lastaccess')); + $userfieldsapi = \core\user_fields::for_userpic()->including('lastaccess'); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $blockeduserssql = "SELECT $userfields FROM {message_users_blocked} mub INNER JOIN {user} u diff --git a/message/classes/helper.php b/message/classes/helper.php index 5074c0e9472..4517185bf9b 100644 --- a/message/classes/helper.php +++ b/message/classes/helper.php @@ -429,7 +429,8 @@ class helper { } list($useridsql, $usersparams) = $DB->get_in_or_equal($userids); - $userfields = \user_picture::fields('u', array('lastaccess')); + $userfieldsapi = \core\user_fields::for_userpic()->including('lastaccess'); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $userssql = "SELECT $userfields, u.deleted, mc.id AS contactid, mub.id AS blockedid FROM {user} u LEFT JOIN {message_contacts} mc diff --git a/message/lib.php b/message/lib.php index 03a7a09680c..3333f1874a3 100644 --- a/message/lib.php +++ b/message/lib.php @@ -183,7 +183,8 @@ function message_search_users($courseids, $searchtext, $sort='', $exceptions='') } $fullname = $DB->sql_fullname(); - $ufields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; if (!empty($sort)) { $order = ' ORDER BY '. $sort; @@ -549,11 +550,12 @@ function message_get_messages($useridto, $useridfrom = 0, $notifications = -1, $ global $DB; // If the 'useridto' value is empty then we are going to retrieve messages sent by the useridfrom to any user. + $userfieldsapi = \core\user_fields::for_name(); if (empty($useridto)) { - $userfields = get_all_user_name_fields(true, 'u', '', 'userto'); + $userfields = $userfieldsapi->get_sql('u', false, 'userto', '', false)->selects; $messageuseridtosql = 'u.id as useridto'; } else { - $userfields = get_all_user_name_fields(true, 'u', '', 'userfrom'); + $userfields = $userfieldsapi->get_sql('u', false, 'userfrom', '', false)->selects; $messageuseridtosql = "$useridto as useridto"; } diff --git a/message/output/email/classes/task/send_email_task.php b/message/output/email/classes/task/send_email_task.php index d21f3155da4..07ff2648dca 100644 --- a/message/output/email/classes/task/send_email_task.php +++ b/message/output/email/classes/task/send_email_task.php @@ -161,7 +161,8 @@ class send_email_task extends scheduled_task { protected function get_users_messages_for_conversation(int $conversationid, int $userid) : moodle_recordset { global $DB; - $usernamefields = \user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT $usernamefields, m.* FROM {messages} m JOIN {user} u diff --git a/mod/assign/extensionform.php b/mod/assign/extensionform.php index ca8e196a979..5f94b8cde06 100644 --- a/mod/assign/extensionform.php +++ b/mod/assign/extensionform.php @@ -58,7 +58,8 @@ class mod_assign_extension_form extends moodleform { $usercount = 0; $usershtml = ''; - $extrauserfields = get_extra_user_fields($assign->get_context()); + // TODO Does not support custom user profile fields (MDL-70456). + $extrauserfields = \core\user_fields::get_identity_fields($assign->get_context(), false); foreach ($userlist as $userid) { if ($usercount >= 5) { $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5); diff --git a/mod/assign/feedback/file/locallib.php b/mod/assign/feedback/file/locallib.php index e0afc11f136..f42198935b0 100644 --- a/mod/assign/feedback/file/locallib.php +++ b/mod/assign/feedback/file/locallib.php @@ -474,7 +474,8 @@ class assign_feedback_file extends assign_feedback_plugin { $this->assignment->get_course_context()), $this->assignment->is_blind_marking(), $this->assignment->get_uniqueid_for_user($user->id), - get_extra_user_fields($this->assignment->get_context())); + // TODO Does not support custom user profile fields (MDL-70456). + \core\user_fields::get_identity_fields($this->assignment->get_context(), false)); $usershtml .= $this->assignment->get_renderer()->render($usersummary); $usercount += 1; } diff --git a/mod/assign/gradingtable.php b/mod/assign/gradingtable.php index 82e2f2fdb0e..77260037e40 100644 --- a/mod/assign/gradingtable.php +++ b/mod/assign/gradingtable.php @@ -134,9 +134,11 @@ class assign_grading_table extends table_sql implements renderable { $params['assignmentid3'] = (int)$this->assignment->get_instance()->id; $params['newstatus'] = ASSIGN_SUBMISSION_STATUS_NEW; - $extrauserfields = get_extra_user_fields($this->assignment->get_context()); - - $fields = user_picture::fields('u', $extrauserfields) . ', '; + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($this->assignment->get_context(), false)->with_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $extrauserfields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); + $fields = $userfields . ', '; $fields .= 'u.id as userid, '; $fields .= 's.status as status, '; $fields .= 's.id as submissionid, '; @@ -406,7 +408,7 @@ class assign_grading_table extends table_sql implements renderable { foreach ($extrauserfields as $extrafield) { $columns[] = $extrafield; - $headers[] = get_user_field_name($extrafield); + $headers[] = \core\user_fields::get_display_name($extrafield); } } else { // Record ID. diff --git a/mod/assign/lib.php b/mod/assign/lib.php index c3d172e2819..7dd73acfa93 100644 --- a/mod/assign/lib.php +++ b/mod/assign/lib.php @@ -589,7 +589,8 @@ function assign_print_recent_activity($course, $viewfullnames, $timestart) { // Do not use log table if possible, it may be huge. $dbparams = array($timestart, $course->id, 'assign', ASSIGN_SUBMISSION_STATUS_SUBMITTED); - $namefields = user_picture::fields('u', null, 'userid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $namefields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;; if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, um.id as recordid, $namefields FROM {assign_submission} asb @@ -746,7 +747,8 @@ function assign_get_recent_mod_activity(&$activities, $params['timestart'] = $timestart; $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED; - $userfields = user_picture::fields('u', null, 'userid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects; if (!$submissions = $DB->get_records_sql('SELECT asb.id, asb.timemodified, ' . $userfields . @@ -833,7 +835,7 @@ function assign_get_recent_mod_activity(&$activities, $activity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade; } - $userfields = explode(',', user_picture::fields()); + $userfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); foreach ($userfields as $userfield) { if ($userfield == 'id') { // Aliased in SQL above. diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 3fea27b7747..de1b93e216a 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -2065,9 +2065,9 @@ class assign { */ private function get_grading_sort_sql() { $usersort = flexible_table::get_sort_for_table('mod_assign_grading'); - $extrauserfields = get_extra_user_fields($this->get_context()); - - $userfields = explode(',', user_picture::fields('', $extrauserfields)); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($this->context, false)->with_userpic(); + $userfields = $userfieldsapi->get_required_fields(); $orderfields = explode(',', $usersort); $validlist = []; @@ -4148,7 +4148,8 @@ class assign { $viewfullnames, $this->is_blind_marking(), $this->get_uniqueid_for_user($user->id), - get_extra_user_fields($this->get_context()), + // TODO Does not support custom user profile fields (MDL-70456). + \core\user_fields::get_identity_fields($this->get_context(), false), !$this->is_active_user($userid)); $o .= $this->get_renderer()->render($usersummary); } @@ -4997,7 +4998,8 @@ class assign { $usershtml = ''; $usercount = 0; - $extrauserfields = get_extra_user_fields($this->get_context()); + // TODO Does not support custom user profile fields (MDL-70456). + $extrauserfields = \core\user_fields::get_identity_fields($this->get_context(), false); $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_context()); foreach ($userlist as $userid) { if ($usercount >= 5) { @@ -5061,7 +5063,8 @@ class assign { $usershtml = ''; $usercount = 0; - $extrauserfields = get_extra_user_fields($this->get_context()); + // TODO Does not support custom user profile fields (MDL-70456). + $extrauserfields = \core\user_fields::get_identity_fields($this->get_context(), false); $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_context()); foreach ($userlist as $userid) { if ($usercount >= 5) { diff --git a/mod/assign/override_form.php b/mod/assign/override_form.php index a317285c2f3..3a4d4cbada0 100644 --- a/mod/assign/override_form.php +++ b/mod/assign/override_form.php @@ -156,12 +156,13 @@ class assign_override_form extends moodleform { list($sort) = users_order_by_sql('u'); // Get the list of appropriate users, depending on whether and how groups are used. + $userfieldsapi = \core\user_fields::for_name(); if ($accessallgroups) { $users = get_enrolled_users($this->context, '', 0, - 'u.id, u.email, ' . get_all_user_name_fields(true, 'u'), $sort); + 'u.id, u.email, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects, $sort); } else if ($groups = groups_get_activity_allowed_groups($cm)) { $enrolledjoin = get_enrolled_join($this->context, 'u.id'); - $userfields = 'u.id, u.email, ' . get_all_user_name_fields(true, 'u'); + $userfields = 'u.id, u.email, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects; list($ingroupsql, $ingroupparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED); $params = $enrolledjoin->params + $ingroupparams; $sql = "SELECT $userfields @@ -185,7 +186,8 @@ class assign_override_form extends moodleform { } $userchoices = array(); - $canviewemail = in_array('email', get_extra_user_fields($this->context)); + // TODO Does not support custom user profile fields (MDL-70456). + $canviewemail = in_array('email', \core\user_fields::get_identity_fields($this->context, false)); foreach ($users as $id => $user) { if (empty($invalidusers[$id]) || (!empty($override) && $id == $override->userid)) { diff --git a/mod/assign/overridedelete.php b/mod/assign/overridedelete.php index b96999a9be9..60630337920 100644 --- a/mod/assign/overridedelete.php +++ b/mod/assign/overridedelete.php @@ -90,7 +90,8 @@ if ($override->groupid) { $group = $DB->get_record('groups', array('id' => $override->groupid), 'id, name'); $confirmstr = get_string("overridedeletegroupsure", "assign", $group->name); } else { - $namefields = get_all_user_name_fields(true); + $userfieldsapi = \core\user_fields::for_name(); + $namefields = $userfieldsapi->get_sql('', false, '', '', false)->selects; $user = $DB->get_record('user', array('id' => $override->userid), 'id, ' . $namefields); $confirmstr = get_string("overridedeleteusersure", "assign", fullname($user)); diff --git a/mod/assign/overrides.php b/mod/assign/overrides.php index effc9df7e19..aa07023ec7f 100644 --- a/mod/assign/overrides.php +++ b/mod/assign/overrides.php @@ -121,8 +121,9 @@ if ($groupmode) { list($sort, $params) = users_order_by_sql('u'); $params['assignid'] = $assign->id; + $userfieldsapi = \core\user_fields::for_name(); if ($accessallgroups) { - $sql = 'SELECT o.*, ' . get_all_user_name_fields(true, 'u') . ' + $sql = 'SELECT o.*, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects . ' FROM {assign_overrides} o JOIN {user} u ON o.userid = u.id WHERE o.assignid = :assignid @@ -133,7 +134,7 @@ if ($groupmode) { list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED); $params += $inparams; - $sql = 'SELECT o.*, ' . get_all_user_name_fields(true, 'u') . ' + $sql = 'SELECT o.*, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects . ' FROM {assign_overrides} o JOIN {user} u ON o.userid = u.id JOIN {groups_members} gm ON u.id = gm.userid diff --git a/mod/chat/lib.php b/mod/chat/lib.php index 478fe8be7b5..9a2b1bf0793 100644 --- a/mod/chat/lib.php +++ b/mod/chat/lib.php @@ -372,7 +372,8 @@ function chat_print_recent_activity($course, $viewfullnames, $timestart) { $groupselect = ""; } - $userfields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; if (!$users = $DB->get_records_sql("SELECT $userfields FROM {course_modules} cm JOIN {chat} ch ON ch.id = cm.instance @@ -513,7 +514,8 @@ function chat_get_users($chatid, $groupid=0, $groupingid=0) { $groupingjoin = ''; } - $ufields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; return $DB->get_records_sql("SELECT DISTINCT $ufields, c.lastmessageping, c.firstping FROM {chat_users} c JOIN {user} u ON u.id = c.userid $groupingjoin @@ -905,7 +907,7 @@ function chat_format_message($message, $courseid, $currentuser, $chatlastrow=nul if (isset($users[$message->userid])) { $user = $users[$message->userid]; - } else if ($user = $DB->get_record('user', array('id' => $message->userid), user_picture::fields())) { + } else if ($user = $DB->get_record('user', array('id' => $message->userid), implode(',', \core\user_fields::get_picture_fields()))) { $users[$message->userid] = $user; } else { return null; @@ -936,7 +938,7 @@ function chat_format_message_theme ($message, $chatuser, $currentuser, $grouping if (isset($users[$message->userid])) { $sender = $users[$message->userid]; - } else if ($sender = $DB->get_record('user', array('id' => $message->userid), user_picture::fields())) { + } else if ($sender = $DB->get_record('user', array('id' => $message->userid), implode(',', \core\user_fields::get_picture_fields()))) { $users[$message->userid] = $sender; } else { return null; diff --git a/mod/choice/lib.php b/mod/choice/lib.php index 4096c8b62a4..f4b9585547f 100644 --- a/mod/choice/lib.php +++ b/mod/choice/lib.php @@ -798,9 +798,11 @@ function choice_get_response_data($choice, $cm, $groupmode, $onlyactive) { /// First get all the users who have access here /// To start with we assume they are all "unanswered" then move them later - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($context, false)->with_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $allresponses[0] = get_enrolled_users($context, 'mod/choice:choose', $currentgroup, - user_picture::fields('u', $extrafields), null, 0, 0, $onlyactive); + $userfields, null, 0, 0, $onlyactive); /// Get all the recorded responses for this choice $rawresponses = $DB->get_records('choice_answers', array('choiceid' => $choice->id)); diff --git a/mod/choice/report.php b/mod/choice/report.php index 9c9ea4c55c9..b78a93d2c04 100644 --- a/mod/choice/report.php +++ b/mod/choice/report.php @@ -95,7 +95,8 @@ $users = choice_get_response_data($choice, $cm, $groupmode, $onlyactive); - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($context, false); if ($download == "ods" && has_capability('mod/choice:downloadresponses', $context)) { require_once("$CFG->libdir/odslib.class.php"); @@ -118,7 +119,7 @@ // Add headers for extra user fields. foreach ($extrafields as $field) { - $myxls->write_string(0, $i++, get_user_field_name($field)); + $myxls->write_string(0, $i++, \core\user_fields::get_display_name($field)); } $myxls->write_string(0, $i++, get_string("group")); @@ -179,7 +180,7 @@ // Add headers for extra user fields. foreach ($extrafields as $field) { - $myxls->write_string(0, $i++, get_user_field_name($field)); + $myxls->write_string(0, $i++, \core\user_fields::get_display_name($field)); } $myxls->write_string(0, $i++, get_string("group")); @@ -235,7 +236,7 @@ // Add headers for extra user fields. foreach ($extrafields as $field) { - echo get_user_field_name($field) . "\t"; + echo \core\user_fields::get_display_name($field) . "\t"; } echo get_string("group"). "\t"; diff --git a/mod/data/lib.php b/mod/data/lib.php index b26423a97a8..18c706399b5 100644 --- a/mod/data/lib.php +++ b/mod/data/lib.php @@ -4118,9 +4118,8 @@ function data_get_recordids($alias, $searcharray, $dataid, $recordids) { function data_get_advanced_search_sql($sort, $data, $recordids, $selectdata, $sortorder) { global $DB; - $namefields = user_picture::fields('u'); - // Remove the id from the string. This already exists in the sql statement. - $namefields = str_replace('u.id,', '', $namefields); + $userfieldsapi = \core\user_fields::for_userpic()->excluding('id'); + $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; if ($sort == 0) { $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ' diff --git a/mod/data/locallib.php b/mod/data/locallib.php index c91540845d6..9e99e42c953 100644 --- a/mod/data/locallib.php +++ b/mod/data/locallib.php @@ -1112,9 +1112,8 @@ function data_search_entries($data, $cm, $context, $mode, $currentgroup, $search $advparams = array(); // This is used for the initial reduction of advanced search results with required entries. $entrysql = ''; - $namefields = user_picture::fields('u'); - // Remove the id from the string. This already exists in the sql statement. - $namefields = str_replace('u.id,', '', $namefields); + $userfieldsapi = \core\user_fields::for_userpic()->excluding('id'); + $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; // Find the field we are sorting on. if ($sort <= 0 or !$sortfield = data_get_field_from_id($sort, $data)) { diff --git a/mod/data/preset.php b/mod/data/preset.php index d76becd15ad..df23f319bcf 100644 --- a/mod/data/preset.php +++ b/mod/data/preset.php @@ -62,7 +62,8 @@ $presets = data_get_available_presets($context); $strdelete = get_string('deleted', 'data'); foreach ($presets as &$preset) { if (!empty($preset->userid)) { - $namefields = get_all_user_name_fields(true); + $userfieldsapi = \core\user_fields::for_name(); + $namefields = $userfieldsapi->get_sql('', false, '', '', false)->selects; $presetuser = $DB->get_record('user', array('id' => $preset->userid), 'id, ' . $namefields, MUST_EXIST); $preset->description = $preset->name.' ('.fullname($presetuser, true).')'; } else { diff --git a/mod/data/view.php b/mod/data/view.php index 5f7800e1cef..bde4875c1f3 100644 --- a/mod/data/view.php +++ b/mod/data/view.php @@ -294,9 +294,8 @@ echo $OUTPUT->notification(get_string('recorddeleted','data'), 'notifysuccess'); } } else { // Print a confirmation page - $allnamefields = user_picture::fields('u'); - // Remove the id from the string. This already exists in the sql statement. - $allnamefields = str_replace('u.id,', '', $allnamefields); + $userfieldsapi = \core\user_fields::for_userpic()->excluding('id'); + $allnamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $dbparams = array($delete); if ($deleterecord = $DB->get_record_sql("SELECT dr.*, $allnamefields FROM {data_records} dr @@ -332,9 +331,8 @@ $validrecords = array(); $recordids = array(); foreach ($multidelete as $value) { - $allnamefields = user_picture::fields('u'); - // Remove the id from the string. This already exists in the sql statement. - $allnamefields = str_replace('u.id,', '', $allnamefields); + $userfieldsapi = \core\user_fields::for_userpic()->excluding('id'); + $allnamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $dbparams = array('id' => $value); if ($deleterecord = $DB->get_record_sql("SELECT dr.*, $allnamefields FROM {data_records} dr diff --git a/mod/feedback/classes/responses_table.php b/mod/feedback/classes/responses_table.php index 84d477b5ac9..66ab1083141 100644 --- a/mod/feedback/classes/responses_table.php +++ b/mod/feedback/classes/responses_table.php @@ -120,8 +120,10 @@ class mod_feedback_responses_table extends table_sql { get_string('groups') ); - $extrafields = get_extra_user_fields($this->get_context()); - $ufields = user_picture::fields('u', $extrafields, $this->useridfield); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($this->get_context(), false)->with_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', $this->useridfield, false)->selects; + $extrafields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); $fields = 'c.id, c.timemodified as completed_timemodified, c.courseid, '.$ufields; $from = '{feedback_completed} c ' . 'JOIN {user} u ON u.id = c.userid AND u.deleted = :notdeleted'; @@ -141,7 +143,7 @@ class mod_feedback_responses_table extends table_sql { foreach ($extrafields as $field) { $fields .= ", u.{$field}"; $tablecolumns[] = $field; - $tableheaders[] = get_user_field_name($field); + $tableheaders[] = \core\user_fields::get_display_name($field); } } diff --git a/mod/feedback/lib.php b/mod/feedback/lib.php index 621bb385b43..2d0d52c7f33 100644 --- a/mod/feedback/lib.php +++ b/mod/feedback/lib.php @@ -388,7 +388,8 @@ function feedback_get_recent_mod_activity(&$activities, &$index, $sqlargs = array(); - $userfields = user_picture::fields('u', null, 'useridagain'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', 'useridagain', false)->selects; $sql = " SELECT fk . * , fc . * , $userfields FROM {feedback_completed} fc JOIN {feedback} fk ON fk.id = fc.feedback @@ -985,7 +986,8 @@ function feedback_get_incomplete_users(cm_info $cm, //first get all user who can complete this feedback $cap = 'mod/feedback:complete'; - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $fields = 'u.id, ' . $allnames . ', u.picture, u.email, u.imagealt'; if (!$allusers = get_users_by_capability($context, $cap, @@ -1122,7 +1124,8 @@ function feedback_get_complete_users($cm, $sortsql = ''; } - $ufields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = 'SELECT DISTINCT '.$ufields.', c.timemodified as completed_timemodified FROM {user} u, {feedback_completed} c '.$fromgroup.' WHERE '.$where.' anonymous_response = :anon diff --git a/mod/forum/classes/form/export_form.php b/mod/forum/classes/form/export_form.php index fb571f94263..93647c699d6 100644 --- a/mod/forum/classes/form/export_form.php +++ b/mod/forum/classes/form/export_form.php @@ -57,7 +57,8 @@ class export_form extends \moodleform { 'valuehtmlcallback' => function($value) { global $OUTPUT; - $allusernames = get_all_user_name_fields(true); + $userfieldsapi = \core\user_fields::for_name(); + $allusernames = $userfieldsapi->get_sql('', false, '', '', false)->selects; $fields = 'id, ' . $allusernames; $user = \core_user::get_user($value, $fields); $useroptiondata = [ diff --git a/mod/forum/classes/local/vaults/discussion_list.php b/mod/forum/classes/local/vaults/discussion_list.php index 07bc0dbfb85..ac8cd0307e5 100644 --- a/mod/forum/classes/local/vaults/discussion_list.php +++ b/mod/forum/classes/local/vaults/discussion_list.php @@ -131,8 +131,11 @@ class discussion_list extends db_table_vault { // - Most recent editor. $thistable = new dml_table(self::TABLE, $alias, $alias); $posttable = new dml_table('forum_posts', 'fp', 'p_'); - $firstauthorfields = \user_picture::fields('fa', ['deleted'], self::FIRST_AUTHOR_ID_ALIAS, self::FIRST_AUTHOR_ALIAS); - $latestuserfields = \user_picture::fields('la', ['deleted'], self::LATEST_AUTHOR_ID_ALIAS, self::LATEST_AUTHOR_ALIAS); + $userfieldsapi = \core\user_fields::for_userpic()->including('deleted'); + $firstauthorfields = $userfieldsapi->get_sql('fa', false, + self::FIRST_AUTHOR_ALIAS, self::FIRST_AUTHOR_ID_ALIAS, false)->selects; + $latestuserfields = $userfieldsapi->get_sql('la', false, + self::LATEST_AUTHOR_ALIAS, self::LATEST_AUTHOR_ID_ALIAS, false)->selects; $fields = implode(', ', [ $thistable->get_field_select(), @@ -271,7 +274,7 @@ class discussion_list extends db_table_vault { $nameformat = get_string('fullnamedisplay', '', (object)['firstname' => 'firstname', 'lastname' => 'lastname']); } // Fetch all the available user name fields. - $availablefields = order_in_string(get_all_user_name_fields(), $nameformat); + $availablefields = order_in_string(\core\user_fields::get_name_fields(), $nameformat); // We'll default to the first name if there's no available name field. $returnfield = 'firstname'; if (!empty($availablefields)) { diff --git a/mod/forum/classes/subscriptions.php b/mod/forum/classes/subscriptions.php index af3f57e1e6f..7982357ef7e 100644 --- a/mod/forum/classes/subscriptions.php +++ b/mod/forum/classes/subscriptions.php @@ -393,7 +393,8 @@ class subscriptions { global $CFG, $DB; if (empty($fields)) { - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $fields ="u.id, u.username, $allnames, diff --git a/mod/forum/deprecatedlib.php b/mod/forum/deprecatedlib.php index 330f05bbf25..5a4e6aad02a 100644 --- a/mod/forum/deprecatedlib.php +++ b/mod/forum/deprecatedlib.php @@ -839,7 +839,7 @@ function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=fa // Build an object that represents the posting user $postuser = new stdClass; - $postuserfields = explode(',', user_picture::fields()); + $postuserfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields); $postuser->id = $post->userid; $postuser->fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']); diff --git a/mod/forum/externallib.php b/mod/forum/externallib.php index 1a6e1a319e2..0df7c0f8ae4 100644 --- a/mod/forum/externallib.php +++ b/mod/forum/externallib.php @@ -674,7 +674,7 @@ class mod_forum_external extends external_api { $discussion->usermodifiedfullname = null; $discussion->usermodifiedpictureurl = null; } else { - $picturefields = explode(',', user_picture::fields()); + $picturefields = explode(',', implode(',', \core\user_fields::get_picture_fields())); // Load user objects from the results of the query. $user = new stdclass(); diff --git a/mod/forum/lib.php b/mod/forum/lib.php index 579c9414cb2..7384a3b5b04 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -644,7 +644,8 @@ function forum_print_recent_activity($course, $viewfullnames, $timestart) { // do not use log table if possible, it may be huge and is expensive to join with other tables - $allnamefields = user_picture::fields('u', null, 'duserid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $allnamefields = $userfieldsapi->get_sql('u', false, '', 'duserid', false)->selects; if (!$posts = $DB->get_records_sql("SELECT p.*, f.course, f.type AS forumtype, f.name AS forumname, f.intro, f.introformat, f.duedate, f.cutoffdate, f.assessed AS forumassessed, f.assesstimestart, f.assesstimefinish, @@ -971,7 +972,8 @@ function forum_scale_used_anywhere(int $scaleid): bool { function forum_get_post_full($postid) { global $CFG, $DB; - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt FROM {forum_posts} p JOIN {forum_discussions} d ON p.discussion = d.id @@ -1000,7 +1002,8 @@ function forum_get_all_discussion_posts($discussionid, $sort, $tracking = false) $params[] = $USER->id; } - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $params[] = $discussionid; if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel FROM {forum_posts} p @@ -1298,7 +1301,8 @@ function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=5 FROM $fromsql WHERE $selectsql"; - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $searchsql = "SELECT p.*, d.forum, $allnames, @@ -1338,7 +1342,8 @@ function forum_get_user_posts($forumid, $userid) { } } - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt FROM {forum} f JOIN {forum_discussions} d ON d.forum = f.id @@ -1429,7 +1434,8 @@ function forum_count_user_posts($forumid, $userid) { function forum_get_post_from_log($log) { global $CFG, $DB; - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; if ($log->action == "add post") { return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture @@ -1757,11 +1763,13 @@ function forum_get_discussions($cm, $forumsort="", $fullpost=true, $unused=-1, $ $postdata = "p.*"; } + $userfieldsapi = \core\user_fields::for_name(); + if (empty($userlastmodified)) { // We don't need to know this $umfields = ""; $umtable = ""; } else { - $umfields = ', ' . get_all_user_name_fields(true, 'um', null, 'um') . ', um.email AS umemail, um.picture AS umpicture, + $umfields = $userfieldsapi->get_sql('um', false, 'um')->selects . ', um.email AS umemail, um.picture AS umpicture, um.imagealt AS umimagealt'; $umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)"; } @@ -1775,7 +1783,7 @@ function forum_get_discussions($cm, $forumsort="", $fullpost=true, $unused=-1, $ $discussionfields = "d.id as discussionid, d.course, d.forum, d.name, d.firstpost, d.groupid, d.assessed," . " d.timemodified, d.usermodified, d.timestart, d.timeend, d.pinned, d.timelocked"; - $allnames = get_all_user_name_fields(true, 'u'); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT $postdata, $discussionfields, $allnames, u.email, u.picture, u.imagealt $umfields FROM {forum_discussions} d @@ -2431,7 +2439,7 @@ function forum_print_discussion_header(&$post, $forum, $group = -1, $datestring // Picture $postuser = new stdClass(); - $postuserfields = explode(',', user_picture::fields()); + $postuserfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields); $postuser->id = $post->userid; echo ''; @@ -4106,7 +4114,8 @@ function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $cours $groupselect = ""; } - $allnames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, d.timestart, d.timeend, d.userid AS duserid, $allnames, u.email, u.picture, u.imagealt, u.email @@ -4178,7 +4187,7 @@ function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $cours $tmpactivity->user = new stdClass(); $additionalfields = array('id' => 'userid', 'picture', 'imagealt', 'email'); - $additionalfields = explode(',', user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $tmpactivity->user = username_load_fields_from_object($tmpactivity->user, $post, null, $additionalfields); $tmpactivity->user->id = $post->userid; @@ -6015,7 +6024,8 @@ function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, // Prepare SQL to both count and search. // We alias user.id to useridx because we forum_posts already has a userid field and not aliasing this would break // oracle and mssql. - $userfields = user_picture::fields('u', null, 'useridx'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', 'useridx', false)->selects; $countsql = 'SELECT COUNT(*) '; $selectsql = 'SELECT p.*, d.forum, d.name AS discussionname, '.$userfields.' '; $wheresql = implode(" OR ", $forumsearchwhere); diff --git a/mod/forum/renderer.php b/mod/forum/renderer.php index 733b558f8ec..c3cc7b9db90 100644 --- a/mod/forum/renderer.php +++ b/mod/forum/renderer.php @@ -125,7 +125,8 @@ class mod_forum_renderer extends plugin_renderer_base { $output .= $this->output->heading(get_string("invalidmodule", "error")); } else { $cm = $modinfo->instances['forum'][$forum->id]; - $canviewemail = in_array('email', get_extra_user_fields(context_module::instance($cm->id))); + // TODO Does not support custom user profile fields (MDL-70456). + $canviewemail = in_array('email', \core\user_fields::get_identity_fields(context_module::instance($cm->id), false)); $strparams = new stdclass(); $strparams->name = format_string($forum->name); $strparams->count = count($users); diff --git a/mod/forum/report/summary/classes/summary_table.php b/mod/forum/report/summary/classes/summary_table.php index 2e5e7a68a88..2333c594777 100644 --- a/mod/forum/report/summary/classes/summary_table.php +++ b/mod/forum/report/summary/classes/summary_table.php @@ -543,8 +543,9 @@ class summary_table extends table_sql { protected function define_base_sql(): void { global $USER; - $userfields = get_extra_user_fields($this->userfieldscontext); - $userfieldssql = \user_picture::fields('u', $userfields); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($this->userfieldscontext, false)->with_userpic(); + $userfieldssql = $userfieldsapi->get_sql('u', false, '', '', false)->selects; // Define base SQL query format. $this->sql->basefields = ' ue.userid AS userid, diff --git a/mod/forum/rsslib.php b/mod/forum/rsslib.php index e93acdea1f1..2c8e4af28f4 100644 --- a/mod/forum/rsslib.php +++ b/mod/forum/rsslib.php @@ -182,7 +182,8 @@ function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) { $forumsort = "d.timemodified DESC"; $postdata = "p.id AS postid, p.subject, p.created as postcreated, p.modified, p.discussion, p.userid, p.message as postmessage, p.messageformat AS postformat, p.messagetrust AS posttrust"; - $userpicturefields = user_picture::fields('u', null, 'userid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userpicturefields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects; $sql = "SELECT $postdata, d.id as discussionid, d.name as discussionname, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $userpicturefields @@ -235,7 +236,8 @@ function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) { $privatewhere = ''; } - $usernamefields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT p.id AS postid, d.id AS discussionid, d.name AS discussionname, diff --git a/mod/glossary/classes/entry_query_builder.php b/mod/glossary/classes/entry_query_builder.php index 2b74234d522..b1870649660 100644 --- a/mod/glossary/classes/entry_query_builder.php +++ b/mod/glossary/classes/entry_query_builder.php @@ -117,7 +117,9 @@ class mod_glossary_entry_query_builder { * @return void */ public function add_user_fields() { - $this->fields[] = user_picture::fields('u', null, 'userdataid', 'userdata'); + $userfieldsapi = \core\user_fields::for_userpic(); + $fields = $userfieldsapi->get_sql('u', false, 'userdata', '', false)->selects; + $this->fields[] = $fields; } /** diff --git a/mod/glossary/lib.php b/mod/glossary/lib.php index 58f5c365519..7979d492c8e 100644 --- a/mod/glossary/lib.php +++ b/mod/glossary/lib.php @@ -403,7 +403,8 @@ function glossary_get_recent_mod_activity(&$activities, &$index, $timestart, $co $params['timestart'] = $timestart; $params['glossaryid'] = $cm->instance; - $ufields = user_picture::fields('u', null, 'userid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects; $entries = $DB->get_records_sql(" SELECT ge.id AS entryid, ge.glossaryid, ge.concept, ge.definition, ge.approved, ge.timemodified, $ufields @@ -564,8 +565,10 @@ function glossary_print_recent_activity($course, $viewfullnames, $timestart) { if (count($approvals) == 0) { return false; } + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects; $selectsql = 'SELECT ge.id, ge.concept, ge.approved, ge.timemodified, ge.glossaryid, - '.user_picture::fields('u',null,'userid'); + ' . $userfields; $countsql = 'SELECT COUNT(*)'; $joins = array(' FROM {glossary_entries} ge '); @@ -3671,7 +3674,8 @@ function glossary_get_authors($glossary, $context, $limit, $from, $options = arr global $DB, $USER; $params = array(); - $userfields = user_picture::fields('u', null); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $approvedsql = '(ge.approved <> 0 OR ge.userid = :myid)'; $params['myid'] = $USER->id; @@ -3824,7 +3828,8 @@ function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch list($searchcond, $params) = glossary_get_search_terms_sql($terms, $fullsearch, $glossary->id); - $userfields = user_picture::fields('u', null, 'userdataid', 'userdata'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, 'userdata', 'userdataid', false)->selects; // Need one inner view here to avoid distinct + text. $sqlwrapheader = 'SELECT ge.*, ge.concept AS glossarypivot, ' . $userfields . ' diff --git a/mod/glossary/rsslib.php b/mod/glossary/rsslib.php index f322b8b412a..1d27b2f5e09 100644 --- a/mod/glossary/rsslib.php +++ b/mod/glossary/rsslib.php @@ -145,7 +145,8 @@ } if ($glossary->rsstype == 1) {//With author - $allnamefields = get_all_user_name_fields(true,'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allnamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT e.id AS entryid, e.concept AS entryconcept, e.definition AS entrydefinition, diff --git a/mod/lesson/essay.php b/mod/lesson/essay.php index df2ce8c8336..9d0c799442d 100644 --- a/mod/lesson/essay.php +++ b/mod/lesson/essay.php @@ -325,7 +325,8 @@ switch ($mode) { JOIN ($esql) ue ON a.userid = ue.id WHERE pageid $usql"; if ($essayattempts = $DB->get_records_sql($sql, $parameters)) { - $ufields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; // Get all the users who have taken this lesson. list($sort, $sortparams) = users_order_by_sql('u'); diff --git a/mod/lesson/locallib.php b/mod/lesson/locallib.php index 784678db4aa..f4b0ad56edf 100644 --- a/mod/lesson/locallib.php +++ b/mod/lesson/locallib.php @@ -705,12 +705,14 @@ function lesson_get_overview_report_table_and_data(lesson $lesson, $currentgroup list($esql, $params) = get_enrolled_sql($context, '', $currentgroup, true); list($sort, $sortparams) = users_order_by_sql('u'); - $extrafields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($context, false)->with_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $extrafields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); $params['a1lessonid'] = $lesson->id; $params['b1lessonid'] = $lesson->id; $params['c1lessonid'] = $lesson->id; - $ufields = user_picture::fields('u', $extrafields); $sql = "SELECT DISTINCT $ufields FROM {user} u JOIN ( @@ -901,7 +903,7 @@ function lesson_get_overview_report_table_and_data(lesson $lesson, $currentgroup $headers = [get_string('name')]; foreach ($extrafields as $field) { - $headers[] = get_user_field_name($field); + $headers[] = \core\user_fields::get_display_name($field); } $caneditlesson = has_capability('mod/lesson:edit', $context); diff --git a/mod/lesson/override_form.php b/mod/lesson/override_form.php index ae52f8b35b3..7e7e9a2bcda 100644 --- a/mod/lesson/override_form.php +++ b/mod/lesson/override_form.php @@ -140,12 +140,13 @@ class lesson_override_form extends moodleform { list($sort) = users_order_by_sql('u'); // Get the list of appropriate users, depending on whether and how groups are used. + $userfieldsapi = \core\user_fields::for_name(); if ($accessallgroups) { $users = get_enrolled_users($this->context, '', 0, - 'u.id, u.email, ' . get_all_user_name_fields(true, 'u'), $sort); + 'u.id, u.email, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects, $sort); } else if ($groups = groups_get_activity_allowed_groups($cm)) { $enrolledjoin = get_enrolled_join($this->context, 'u.id'); - $userfields = 'u.id, u.email, ' . get_all_user_name_fields(true, 'u'); + $userfields = 'u.id, u.email, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects; list($ingroupsql, $ingroupparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED); $params = $enrolledjoin->params + $ingroupparams; $sql = "SELECT $userfields @@ -169,7 +170,8 @@ class lesson_override_form extends moodleform { } $userchoices = array(); - $canviewemail = in_array('email', get_extra_user_fields($this->context)); + // TODO Does not support custom user profile fields (MDL-70456). + $canviewemail = in_array('email', \core\user_fields::get_identity_fields($this->context, false)); foreach ($users as $id => $user) { if (empty($invalidusers[$id]) || (!empty($override) && $id == $override->userid)) { diff --git a/mod/lesson/overridedelete.php b/mod/lesson/overridedelete.php index bd213e00a73..3dacfd8f06d 100644 --- a/mod/lesson/overridedelete.php +++ b/mod/lesson/overridedelete.php @@ -93,7 +93,8 @@ if ($override->groupid) { $group = $DB->get_record('groups', array('id' => $override->groupid), 'id, name'); $confirmstr = get_string("overridedeletegroupsure", "lesson", $group->name); } else { - $namefields = get_all_user_name_fields(true); + $userfieldsapi = \core\user_fields::for_name(); + $namefields = $userfieldsapi->get_sql('', false, '', '', false)->selects; $user = $DB->get_record('user', array('id' => $override->userid), 'id, ' . $namefields); $confirmstr = get_string("overridedeleteusersure", "lesson", fullname($user)); diff --git a/mod/lesson/overrides.php b/mod/lesson/overrides.php index 9ff3cddc598..3a988169fbb 100644 --- a/mod/lesson/overrides.php +++ b/mod/lesson/overrides.php @@ -106,8 +106,9 @@ if ($groupmode) { list($sort, $params) = users_order_by_sql('u'); $params['lessonid'] = $lesson->id; + $userfieldsapi = \core\user_fields::for_name(); if ($accessallgroups) { - $sql = 'SELECT o.*, ' . get_all_user_name_fields(true, 'u') . ' + $sql = 'SELECT o.*, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects . ' FROM {lesson_overrides} o JOIN {user} u ON o.userid = u.id WHERE o.lessonid = :lessonid @@ -118,7 +119,7 @@ if ($groupmode) { list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED); $params += $inparams; - $sql = 'SELECT o.*, ' . get_all_user_name_fields(true, 'u') . ' + $sql = 'SELECT o.*, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects . ' FROM {lesson_overrides} o JOIN {user} u ON o.userid = u.id JOIN {groups_members} gm ON u.id = gm.userid diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index ba56c86b4ab..0b8b996ad15 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -921,7 +921,8 @@ function quiz_get_recent_mod_activity(&$activities, &$index, $timestart, $params['timestart'] = $timestart; $params['quizid'] = $quiz->id; - $ufields = user_picture::fields('u', null, 'useridagain'); + $userfieldsapi = \core\user_fields::for_userpic(); + $ufields = $userfieldsapi->get_sql('u', false, '', 'useridagain', false)->selects; if (!$attempts = $DB->get_records_sql(" SELECT qa.*, {$ufields} diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index ac304014898..2327332b245 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -1699,7 +1699,8 @@ function quiz_send_notification_messages($course, $quiz, $attempt, $context, $cm // Check for notifications required. $notifyfields = 'u.id, u.username, u.idnumber, u.email, u.emailstop, u.lang, u.timezone, u.mailformat, u.maildisplay, u.auth, u.suspended, u.deleted, '; - $notifyfields .= get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $notifyfields .= $userfieldsapi->get_sql('u', false, '', '', false)->selects; $groups = groups_get_all_groups($course->id, $submitter->id, $cm->groupingid); if (is_array($groups) && count($groups) > 0) { $groups = array_keys($groups); diff --git a/mod/quiz/override_form.php b/mod/quiz/override_form.php index 2036cae81a9..7ddecba5b68 100644 --- a/mod/quiz/override_form.php +++ b/mod/quiz/override_form.php @@ -123,7 +123,9 @@ class quiz_override_form extends moodleform { } } else { // User override. - $extrauserfields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($this->context, false)->with_userpic()->with_name(); + $extrauserfields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); if ($this->userid) { // There is already a userid, so freeze the selector. $user = $DB->get_record('user', ['id' => $this->userid]); @@ -143,7 +145,7 @@ class quiz_override_form extends moodleform { } // Get the list of appropriate users, depending on whether and how groups are used. - $userfields = user_picture::fields('u', $extrauserfields, 'userid'); + $userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects; if ($accessallgroups) { $users = get_users_by_capability($this->context, 'mod/quiz:attempt', $userfields, $sort); @@ -226,7 +228,7 @@ class quiz_override_form extends moodleform { * Get a user's name and identity ready to display. * * @param stdClass $user a user object. - * @param array $extrauserfields from get_extra_user_fields. + * @param array $extrauserfields (identity fields in user table only from the user_fields API) * @return string User's name, with extra info, for display. */ protected function display_user_name(stdClass $user, array $extrauserfields) { diff --git a/mod/quiz/overridedelete.php b/mod/quiz/overridedelete.php index 9da9c8ebcb2..a9d9ab94ed0 100644 --- a/mod/quiz/overridedelete.php +++ b/mod/quiz/overridedelete.php @@ -95,12 +95,13 @@ if ($override->groupid) { $group = $DB->get_record('groups', ['id' => $override->groupid], 'id, name'); $confirmstr = get_string("overridedeletegroupsure", "quiz", $group->name); } else { - $namefields = get_all_user_name_fields(true); $user = $DB->get_record('user', ['id' => $override->userid]); $username = fullname($user); $namefields = []; - foreach (get_extra_user_fields($context) as $field) { + + // TODO Does not support custom user profile fields (MDL-70456). + foreach (\core\user_fields::for_identity($context, false)->get_required_fields() as $field) { if (isset($user->$field) && $user->$field !== '') { $namefields[] = $user->$field; } diff --git a/mod/quiz/overrides.php b/mod/quiz/overrides.php index 78b354d1eaf..172daeb2709 100644 --- a/mod/quiz/overrides.php +++ b/mod/quiz/overrides.php @@ -108,15 +108,17 @@ if ($groupmode) { // User overrides. $colclasses[] = 'colname'; $headers[] = get_string('user'); - $extrauserfields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $userfieldsapi = \core\user_fields::for_identity($context, false)->with_name()->with_userpic(); + $extrauserfields = $userfieldsapi->get_required_fields([\core\user_fields::PURPOSE_IDENTITY]); foreach ($extrauserfields as $field) { $colclasses[] = 'col' . $field; - $headers[] = get_user_field_name($field); + $headers[] = \core\user_fields::get_display_name($field); } list($sort, $params) = users_order_by_sql('u'); $params['quizid'] = $quiz->id; - $userfields = user_picture::fields('u', $extrauserfields, 'userid'); + $userfields = $userfieldsapi->get_sql('u', true, '', 'userid', false)->selects; if ($showallgroups) { $groupsjoin = ''; diff --git a/mod/quiz/report/attemptsreport.php b/mod/quiz/report/attemptsreport.php index 58163af3926..c4d7e4ecc9a 100644 --- a/mod/quiz/report/attemptsreport.php +++ b/mod/quiz/report/attemptsreport.php @@ -203,10 +203,11 @@ abstract class quiz_attempts_report extends quiz_default_report { $headers[] = get_string('firstname'); } - $extrafields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->context, false); foreach ($extrafields as $field) { $columns[] = $field; - $headers[] = get_user_field_name($field); + $headers[] = \core\user_fields::get_display_name($field); } } @@ -217,7 +218,8 @@ abstract class quiz_attempts_report extends quiz_default_report { protected function configure_user_columns($table) { $table->column_suppress('picture'); $table->column_suppress('fullname'); - $extrafields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->context, false); foreach ($extrafields as $field) { $table->column_suppress($field); } diff --git a/mod/quiz/report/attemptsreport_table.php b/mod/quiz/report/attemptsreport_table.php index e4ae349f5e0..9f681b718e8 100644 --- a/mod/quiz/report/attemptsreport_table.php +++ b/mod/quiz/report/attemptsreport_table.php @@ -135,7 +135,7 @@ abstract class quiz_attempts_report_table extends table_sql { public function col_picture($attempt) { global $OUTPUT; $user = new stdClass(); - $additionalfields = explode(',', user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $user = username_load_fields_from_object($user, $attempt, null, $additionalfields); $user->id = $attempt->userid; return $OUTPUT->user_picture($user); @@ -413,15 +413,15 @@ abstract class quiz_attempts_report_table extends table_sql { $fields .= "\n(CASE WHEN $this->qmsubselect THEN 1 ELSE 0 END) AS gradedattempt,"; } - $extrafields = get_extra_user_fields_sql($this->context, 'u', '', - array('id', 'idnumber', 'firstname', 'lastname', 'picture', - 'imagealt', 'institution', 'department', 'email')); - $allnames = get_all_user_name_fields(true, 'u'); + // TODO Does not support custom user profile fields (MDL-70456). + $userfields = \core\user_fields::for_identity($this->context, false)->with_name() + ->excluding('id', 'idnumber', 'picture', 'imagealt', 'institution', 'department', 'email'); + $extrafields = $userfields->get_sql('u')->selects; $fields .= ' quiza.uniqueid AS usageid, quiza.id AS attempt, u.id AS userid, - u.idnumber, ' . $allnames . ', + u.idnumber, u.picture, u.imagealt, u.institution, diff --git a/mod/quiz/report/grading/report.php b/mod/quiz/report/grading/report.php index 28990e63068..6f66a8e678c 100644 --- a/mod/quiz/report/grading/report.php +++ b/mod/quiz/report/grading/report.php @@ -224,7 +224,8 @@ class quiz_grading_report extends quiz_default_report { $params[] = $this->quiz->id; $fields = 'quiza.*, u.idnumber, '; - $fields .= get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $fields .= $userfieldsapi->get_sql('u', false, '', '', false)->selects; $attemptsbyid = $DB->get_records_sql(" SELECT $fields FROM {quiz_attempts} quiza diff --git a/mod/quiz/report/overview/report.php b/mod/quiz/report/overview/report.php index 0c97eca6d9d..360d5c0d109 100644 --- a/mod/quiz/report/overview/report.php +++ b/mod/quiz/report/overview/report.php @@ -393,7 +393,8 @@ class quiz_overview_report extends quiz_attempts_report { global $DB; $this->unlock_session(); - $sql = "SELECT quiza.*, " . get_all_user_name_fields(true, 'u') . " + $userfieldsapi = \core\user_fields::for_name(); + $sql = "SELECT quiza.*, " . $userfieldsapi->get_sql('u', false, '', '', false)->selects . " FROM {quiz_attempts} quiza JOIN {user} u ON u.id = quiza.userid"; $where = "quiz = :qid AND preview = 0"; @@ -460,8 +461,9 @@ class quiz_overview_report extends quiz_attempts_report { } list($uniqueidcondition, $params) = $DB->get_in_or_equal(array_keys($attemptquestions)); + $userfieldsapi = \core\user_fields::for_name(); $attempts = $DB->get_records_sql(" - SELECT quiza.*, " . get_all_user_name_fields(true, 'u') . " + SELECT quiza.*, " . $userfieldsapi->get_sql('u', false, '', '', false)->selects . " FROM {quiz_attempts} quiza JOIN {user} u ON u.id = quiza.userid WHERE quiza.uniqueid $uniqueidcondition diff --git a/mod/scorm/report/basic/classes/report.php b/mod/scorm/report/basic/classes/report.php index d5f1285c095..b7f09a65f7b 100644 --- a/mod/scorm/report/basic/classes/report.php +++ b/mod/scorm/report/basic/classes/report.php @@ -117,10 +117,11 @@ class report extends \mod_scorm\report { } $columns[] = 'fullname'; $headers[] = get_string('name'); - $extrafields = get_extra_user_fields($coursecontext); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($coursecontext, false); foreach ($extrafields as $field) { $columns[] = $field; - $headers[] = get_user_field_name($field); + $headers[] = \core\user_fields::get_display_name($field); } $columns[] = 'attempt'; @@ -267,9 +268,10 @@ class report extends \mod_scorm\report { } // Construct the SQL. $select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').' AS uniqueid, '; - $select .= 'st.scormid AS scormid, st.attempt AS attempt, ' . - \user_picture::fields('u', array('idnumber'), 'userid') . - get_extra_user_fields_sql($coursecontext, 'u', '', array('email', 'idnumber')) . ' '; + // TODO Does not support custom user profile fields (MDL-70456). + $userfields = \core\user_fields::for_identity($coursecontext, false)->with_userpic()->including('idnumber'); + $selectfields = $userfields->get_sql('u', false, '', 'userid')->selects; + $select .= 'st.scormid AS scormid, st.attempt AS attempt ' . $selectfields . ' '; // This part is the same for all cases - join users and scorm_scoes_track tables. $from = 'FROM {user} u '; @@ -377,7 +379,7 @@ class report extends \mod_scorm\report { } if (in_array('picture', $columns)) { $user = new \stdClass(); - $additionalfields = explode(',', \user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $user = username_load_fields_from_object($user, $scouser, null, $additionalfields); $user->id = $scouser->userid; $row[] = $OUTPUT->user_picture($user, array('courseid' => $course->id)); diff --git a/mod/scorm/report/interactions/classes/report.php b/mod/scorm/report/interactions/classes/report.php index 1bc5afbda20..61644dea9fc 100644 --- a/mod/scorm/report/interactions/classes/report.php +++ b/mod/scorm/report/interactions/classes/report.php @@ -133,10 +133,11 @@ class report extends \mod_scorm\report { $columns[] = 'fullname'; $headers[] = get_string('name'); - $extrafields = get_extra_user_fields($coursecontext); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($coursecontext, false); foreach ($extrafields as $field) { $columns[] = $field; - $headers[] = get_user_field_name($field); + $headers[] = \core\user_fields::get_display_name($field); } $columns[] = 'attempt'; $headers[] = get_string('attempt', 'scorm'); @@ -156,9 +157,10 @@ class report extends \mod_scorm\report { // Construct the SQL. $select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').' AS uniqueid, '; - $select .= 'st.scormid AS scormid, st.attempt AS attempt, ' . - \user_picture::fields('u', array('idnumber'), 'userid') . - get_extra_user_fields_sql($coursecontext, 'u', '', array('email', 'idnumber')) . ' '; + // TODO Does not support custom user profile fields (MDL-70456). + $userfields = \core\user_fields::for_identity($coursecontext, false)->with_userpic()->including('idnumber'); + $selectfields = $userfields->get_sql('u', false, '', 'userid')->selects; + $select .= 'st.scormid AS scormid, st.attempt AS attempt ' . $selectfields . ' '; // This part is the same for all cases - join users and scorm_scoes_track tables. $from = 'FROM {user} u '; @@ -422,7 +424,7 @@ class report extends \mod_scorm\report { } if (in_array('picture', $columns)) { $user = new \stdClass(); - $additionalfields = explode(',', \user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $user = username_load_fields_from_object($user, $scouser, null, $additionalfields); $user->id = $scouser->userid; $row[] = $OUTPUT->user_picture($user, array('courseid' => $course->id)); diff --git a/mod/scorm/report/objectives/classes/report.php b/mod/scorm/report/objectives/classes/report.php index 4e2ad624ed6..f8af595852c 100644 --- a/mod/scorm/report/objectives/classes/report.php +++ b/mod/scorm/report/objectives/classes/report.php @@ -127,10 +127,11 @@ class report extends \mod_scorm\report { $columns[] = 'fullname'; $headers[] = get_string('name'); - $extrafields = get_extra_user_fields($coursecontext); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($coursecontext, false); foreach ($extrafields as $field) { $columns[] = $field; - $headers[] = get_user_field_name($field); + $headers[] = \core\user_fields::get_display_name($field); } $columns[] = 'attempt'; $headers[] = get_string('attempt', 'scorm'); @@ -150,9 +151,10 @@ class report extends \mod_scorm\report { // Construct the SQL. $select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').' AS uniqueid, '; - $select .= 'st.scormid AS scormid, st.attempt AS attempt, ' . - \user_picture::fields('u', array('idnumber'), 'userid') . - get_extra_user_fields_sql($coursecontext, 'u', '', array('email', 'idnumber')) . ' '; + // TODO Does not support custom user profile fields (MDL-70456). + $userfields = \core\user_fields::for_identity($coursecontext, false)->with_userpic()->including('idnumber'); + $selectfields = $userfields->get_sql('u', false, '', 'userid')->selects; + $select .= 'st.scormid AS scormid, st.attempt AS attempt ' . $selectfields . ' '; // This part is the same for all cases - join users and scorm_scoes_track tables. $from = 'FROM {user} u '; @@ -412,7 +414,7 @@ class report extends \mod_scorm\report { } if (in_array('picture', $columns)) { $user = new \stdClass(); - $additionalfields = explode(',', \user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $user = username_load_fields_from_object($user, $scouser, null, $additionalfields); $user->id = $scouser->userid; $row[] = $OUTPUT->user_picture($user, array('courseid' => $course->id)); diff --git a/mod/scorm/report/userreport.php b/mod/scorm/report/userreport.php index b5a2dc88c91..32f0ca9e689 100644 --- a/mod/scorm/report/userreport.php +++ b/mod/scorm/report/userreport.php @@ -39,7 +39,7 @@ $tracksurl = new moodle_url('/mod/scorm/report/userreporttracks.php', array('id' $cm = get_coursemodule_from_id('scorm', $id, 0, false, MUST_EXIST); $course = get_course($cm->course); $scorm = $DB->get_record('scorm', array('id' => $cm->instance), '*', MUST_EXIST); -$user = $DB->get_record('user', array('id' => $userid), user_picture::fields(), MUST_EXIST); +$user = $DB->get_record('user', array('id' => $userid), implode(',', \core\user_fields::get_picture_fields()), MUST_EXIST); // Get list of attempts this user has made. $attemptids = scorm_get_all_attempts($scorm->id, $userid); diff --git a/mod/scorm/report/userreportinteractions.php b/mod/scorm/report/userreportinteractions.php index 73d1271e933..92983c13ea0 100644 --- a/mod/scorm/report/userreportinteractions.php +++ b/mod/scorm/report/userreportinteractions.php @@ -40,7 +40,7 @@ $url = new moodle_url('/mod/scorm/report/userreportinteractions.php', array('id' $cm = get_coursemodule_from_id('scorm', $id, 0, false, MUST_EXIST); $course = get_course($cm->course); $scorm = $DB->get_record('scorm', array('id' => $cm->instance), '*', MUST_EXIST); -$user = $DB->get_record('user', array('id' => $userid), user_picture::fields(), MUST_EXIST); +$user = $DB->get_record('user', array('id' => $userid), implode(',', \core\user_fields::get_picture_fields()), MUST_EXIST); // Get list of attempts this user has made. $attemptids = scorm_get_all_attempts($scorm->id, $userid); diff --git a/mod/scorm/report/userreporttracks.php b/mod/scorm/report/userreporttracks.php index e73813836c5..a0e702cc145 100644 --- a/mod/scorm/report/userreporttracks.php +++ b/mod/scorm/report/userreporttracks.php @@ -40,7 +40,7 @@ $url = new moodle_url('/mod/scorm/report/userreporttracks.php', array('id' => $i $cm = get_coursemodule_from_id('scorm', $id, 0, false, MUST_EXIST); $course = get_course($cm->course); $scorm = $DB->get_record('scorm', array('id' => $cm->instance), '*', MUST_EXIST); -$user = $DB->get_record('user', array('id' => $userid), user_picture::fields(), MUST_EXIST); +$user = $DB->get_record('user', array('id' => $userid), implode(',', \core\user_fields::get_picture_fields()), MUST_EXIST); $selsco = $DB->get_record('scorm_scoes', array('id' => $scoid), '*', MUST_EXIST); $PAGE->set_url($url); diff --git a/mod/survey/lib.php b/mod/survey/lib.php index 82820ad1f6a..c5b4f1972c5 100644 --- a/mod/survey/lib.php +++ b/mod/survey/lib.php @@ -243,7 +243,8 @@ function survey_print_recent_activity($course, $viewfullnames, $timestart) { $slist = implode(',', $ids); // there should not be hundreds of glossaries in one course, right? - $allusernames = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $allusernames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $rs = $DB->get_recordset_sql("SELECT sa.userid, sa.survey, MAX(sa.time) AS time, $allusernames FROM {survey_answers} sa @@ -315,7 +316,8 @@ function survey_get_responses($surveyid, $groupid, $groupingid) { $groupsjoin = ""; } - $userfields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; return $DB->get_records_sql("SELECT $userfields, MAX(a.time) as time FROM {survey_answers} a JOIN {user} u ON a.userid = u.id @@ -375,7 +377,8 @@ function survey_get_user_answers($surveyid, $questionid, $groupid, $sort="sa.ans $groupsql = ''; } - $userfields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; return $DB->get_records_sql("SELECT sa.*, $userfields FROM {survey_answers} sa, {user} u $groupfrom WHERE sa.survey = :surveyid diff --git a/mod/workshop/allocation/manual/lib.php b/mod/workshop/allocation/manual/lib.php index a1fd31cf736..ba73dd51dab 100644 --- a/mod/workshop/allocation/manual/lib.php +++ b/mod/workshop/allocation/manual/lib.php @@ -224,7 +224,7 @@ class workshop_manual_allocator implements workshop_allocator { // load the participants' submissions $submissions = $this->workshop->get_submissions(array_keys($participants)); - $allnames = get_all_user_name_fields(); + $allnames = \core\user_fields::get_name_fields(); foreach ($submissions as $submission) { if (!isset($userinfo[$submission->authorid])) { $userinfo[$submission->authorid] = new stdclass(); @@ -243,7 +243,8 @@ class workshop_manual_allocator implements workshop_allocator { $reviewers = array(); if ($submissions) { list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED); - $picturefields = user_picture::fields('r', array(), 'reviewerid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $picturefields = $userfieldsapi->get_sql('r', false, '', 'reviewerid', false)->selects; $sql = "SELECT a.id AS assessmentid, a.submissionid, $picturefields, s.id AS submissionid, s.authorid FROM {workshop_assessments} a @@ -269,7 +270,8 @@ class workshop_manual_allocator implements workshop_allocator { $reviewees = array(); if ($participants) { list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED); - $namefields = get_all_user_name_fields(true, 'e'); + $userfieldsapi = \core\user_fields::for_name(); + $namefields = $userfieldsapi->get_sql('e', false, '', '', false)->selects; $params['workshopid'] = $this->workshop->id; $sql = "SELECT a.id AS assessmentid, a.submissionid, u.id AS reviewerid, diff --git a/mod/workshop/lib.php b/mod/workshop/lib.php index 951b63174a0..cba46ce7071 100644 --- a/mod/workshop/lib.php +++ b/mod/workshop/lib.php @@ -523,8 +523,9 @@ function workshop_user_complete($course, $user, $mod, $workshop) { function workshop_print_recent_activity($course, $viewfullnames, $timestart) { global $CFG, $USER, $DB, $OUTPUT; - $authoramefields = get_all_user_name_fields(true, 'author', null, 'author'); - $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer'); + $userfieldsapi = \core\user_fields::for_name(); + $authoramefields = $userfieldsapi->get_sql('author', false, 'author', '', false)->selects; + $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', '', false)->selects; $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified, author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified, @@ -768,8 +769,9 @@ function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $co $params['submissionmodified'] = $timestart; $params['assessmentmodified'] = $timestart; - $authornamefields = get_all_user_name_fields(true, 'author', null, 'author'); - $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer'); + $userfieldsapi = \core\user_fields::for_name(); + $authornamefields = $userfieldsapi->get_sql('author', false, 'author', '', false)->selects; + $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', '', false)->selects; $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified, author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt, @@ -806,13 +808,13 @@ function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $co // remember all user names we can use later if (empty($users[$activity->authorid])) { $u = new stdclass(); - $additionalfields = explode(',', user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields); $users[$activity->authorid] = $u; } if ($activity->reviewerid and empty($users[$activity->reviewerid])) { $u = new stdclass(); - $additionalfields = explode(',', user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields); $users[$activity->reviewerid] = $u; } @@ -1490,7 +1492,8 @@ function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filea } else { - $userfields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT s.id, $userfields FROM {workshop_submissions} s JOIN {user} u ON (s.authorid = u.id) diff --git a/mod/workshop/locallib.php b/mod/workshop/locallib.php index 7bc6f081b16..5cbe1f4cab7 100644 --- a/mod/workshop/locallib.php +++ b/mod/workshop/locallib.php @@ -861,8 +861,9 @@ class workshop { public function get_submissions($authorid='all', $groupid=0, $limitfrom=0, $limitnum=0) { global $DB; - $authorfields = user_picture::fields('u', null, 'authoridx', 'author'); - $gradeoverbyfields = user_picture::fields('t', null, 'gradeoverbyx', 'over'); + $userfieldsapi = \core\user_fields::for_userpic(); + $authorfields = $userfieldsapi->get_sql('u', false, 'author', 'authoridx', false)->selects; + $gradeoverbyfields = $userfieldsapi->get_sql('t', false, 'over', 'gradeoverbyx', false)->selects; $params = array('workshopid' => $this->id); $sql = "SELECT s.id, s.workshopid, s.example, s.authorid, s.timecreated, s.timemodified, s.title, s.grade, s.gradeover, s.gradeoverby, s.published, @@ -966,8 +967,9 @@ class workshop { // we intentionally check the workshopid here, too, so the workshop can't touch submissions // from other instances - $authorfields = user_picture::fields('u', null, 'authoridx', 'author'); - $gradeoverbyfields = user_picture::fields('g', null, 'gradeoverbyx', 'gradeoverby'); + $userfieldsapi = \core\user_fields::for_userpic(); + $authorfields = $userfieldsapi->get_sql('u', false, 'author', 'authoridx', false)->selects; + $gradeoverbyfields = $userfieldsapi->get_sql('g', false, 'gradeoverby', 'gradeoverbyx', false)->selects; $sql = "SELECT s.*, $authorfields, $gradeoverbyfields FROM {workshop_submissions} s INNER JOIN {user} u ON (s.authorid = u.id) @@ -989,8 +991,9 @@ class workshop { if (empty($authorid)) { return false; } - $authorfields = user_picture::fields('u', null, 'authoridx', 'author'); - $gradeoverbyfields = user_picture::fields('g', null, 'gradeoverbyx', 'gradeoverby'); + $userfieldsapi = \core\user_fields::for_userpic(); + $authorfields = $userfieldsapi->get_sql('u', false, 'author', 'authoridx', false)->selects; + $gradeoverbyfields = $userfieldsapi->get_sql('g', false, 'gradeoverby', 'gradeoverbyx', false)->selects; $sql = "SELECT s.*, $authorfields, $gradeoverbyfields FROM {workshop_submissions} s INNER JOIN {user} u ON (s.authorid = u.id) @@ -1008,7 +1011,8 @@ class workshop { public function get_published_submissions($orderby='finalgrade DESC') { global $DB; - $authorfields = user_picture::fields('u', null, 'authoridx', 'author'); + $userfieldsapi = \core\user_fields::for_userpic(); + $authorfields = $userfieldsapi->get_sql('u', false, 'author', 'authoridx', false)->selects; $sql = "SELECT s.id, s.authorid, s.timecreated, s.timemodified, s.title, s.grade, s.gradeover, COALESCE(s.gradeover,s.grade) AS finalgrade, $authorfields @@ -1301,9 +1305,10 @@ class workshop { public function get_all_assessments() { global $DB; - $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); - $authorfields = user_picture::fields('author', null, 'authorid', 'author'); - $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); + $userfieldsapi = \core\user_fields::for_userpic(); + $reviewerfields = $userfieldsapi->get_sql('reviewer', false, '', 'revieweridx', false)->selects; + $authorfields = $userfieldsapi->get_sql('author', false, 'author', 'authorid', false)->selects; + $overbyfields = $userfieldsapi->get_sql('overby', false, 'overby', 'gradinggradeoverbyx', false)->selects; list($sort, $params) = users_order_by_sql('reviewer'); $sql = "SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified, a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby, @@ -1330,9 +1335,10 @@ class workshop { public function get_assessment_by_id($id) { global $DB; - $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); - $authorfields = user_picture::fields('author', null, 'authorid', 'author'); - $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); + $userfieldsapi = \core\user_fields::for_userpic(); + $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', 'revieweridx', false)->selects; + $authorfields = $userfieldsapi->get_sql('author', false, 'author', 'authorid', false)->selects; + $overbyfields = $userfieldsapi->get_sql('overby', false, 'overby', 'gradinggradeoverbyx', false)->selects; $sql = "SELECT a.*, s.title, $reviewerfields, $authorfields, $overbyfields FROM {workshop_assessments} a INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id) @@ -1355,9 +1361,10 @@ class workshop { public function get_assessment_of_submission_by_user($submissionid, $reviewerid) { global $DB; - $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); - $authorfields = user_picture::fields('author', null, 'authorid', 'author'); - $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); + $userfieldsapi = \core\user_fields::for_userpic(); + $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', 'revieweridx', false)->selects; + $authorfields = $userfieldsapi->get_sql('author', false, 'author', 'authorid', false)->selects; + $overbyfields = $userfieldsapi->get_sql('overby', false, 'overby', 'gradinggradeoverbyx', false)->selects; $sql = "SELECT a.*, s.title, $reviewerfields, $authorfields, $overbyfields FROM {workshop_assessments} a INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id) @@ -1379,8 +1386,9 @@ class workshop { public function get_assessments_of_submission($submissionid) { global $DB; - $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); - $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); + $userfieldsapi = \core\user_fields::for_userpic(); + $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', 'revieweridx', false)->selects; + $overbyfields = $userfieldsapi->get_sql('overby', false, 'overby', 'gradinggradeoverbyx', false)->selects; list($sort, $params) = users_order_by_sql('reviewer'); $sql = "SELECT a.*, s.title, $reviewerfields, $overbyfields FROM {workshop_assessments} a @@ -1404,9 +1412,10 @@ class workshop { public function get_assessments_by_reviewer($reviewerid) { global $DB; - $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); - $authorfields = user_picture::fields('author', null, 'authorid', 'author'); - $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); + $userfieldsapi = \core\user_fields::for_userpic(); + $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', 'revieweridx', false)->selects; + $authorfields = $userfieldsapi->get_sql('author', false, 'author', 'authorid', false)->selects; + $overbyfields = $userfieldsapi->get_sql('overby', false, 'overby', 'gradinggradeoverbyx', false)->selects; $sql = "SELECT a.*, $reviewerfields, $authorfields, $overbyfields, s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated, s.timemodified AS submissionmodified @@ -2034,7 +2043,8 @@ class workshop { $sqlsort[] = $sqlsortfieldname . ' ' . $sqlsortfieldhow; } $sqlsort = implode(',', $sqlsort); - $picturefields = user_picture::fields('u', array(), 'userid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $picturefields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects; $sql = "SELECT $picturefields, s.title AS submissiontitle, s.timemodified AS submissionmodified, s.grade AS submissiongrade, ag.gradinggrade FROM {user} u @@ -2051,7 +2061,7 @@ class workshop { $userinfo = array(); // get the user details for all participants to display - $additionalnames = get_all_user_name_fields(); + $additionalnames = \core\user_fields::get_name_fields(); foreach ($participants as $participant) { if (!isset($userinfo[$participant->userid])) { $userinfo[$participant->userid] = new stdclass(); @@ -2089,7 +2099,8 @@ class workshop { if ($submissions) { list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED); list($sort, $sortparams) = users_order_by_sql('r'); - $picturefields = user_picture::fields('r', array(), 'reviewerid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $picturefields = $userfieldsapi->get_sql('r', false, '', 'reviewerid', false)->selects; $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.weight, $picturefields, s.id AS submissionid, s.authorid FROM {workshop_assessments} a @@ -2118,7 +2129,8 @@ class workshop { list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED); list($sort, $sortparams) = users_order_by_sql('e'); $params['workshopid'] = $this->id; - $picturefields = user_picture::fields('e', array(), 'authorid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $picturefields = $userfieldsapi->get_sql('e', false, '', 'authorid', false)->selects; $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid, a.weight, s.id AS submissionid, $picturefields FROM {user} u @@ -3393,7 +3405,8 @@ class workshop { list($esql, $params) = get_enrolled_sql($this->context, $capability, $groupid, true); - $userfields = user_picture::fields('u'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT $userfields FROM {user} u @@ -4042,7 +4055,7 @@ abstract class workshop_submission_base { * Usually this is called by the contructor but can be called explicitely, too. */ public function anonymize() { - $authorfields = explode(',', user_picture::fields()); + $authorfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); foreach ($authorfields as $field) { $prefixedusernamefield = 'author' . $field; unset($this->{$prefixedusernamefield}); diff --git a/mod/workshop/renderer.php b/mod/workshop/renderer.php index 5b4bf751e63..4b1576ca73a 100644 --- a/mod/workshop/renderer.php +++ b/mod/workshop/renderer.php @@ -102,7 +102,7 @@ class mod_workshop_renderer extends plugin_renderer_base { if (!$anonymous) { $author = new stdclass(); - $additionalfields = explode(',', user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $author = username_load_fields_from_object($author, $submission, 'author', $additionalfields); $userpic = $this->output->user_picture($author, array('courseid' => $this->page->course->id, 'size' => 64)); $userurl = new moodle_url('/user/view.php', @@ -180,7 +180,7 @@ class mod_workshop_renderer extends plugin_renderer_base { if (!$anonymous) { $author = new stdClass(); - $additionalfields = explode(',', user_picture::fields()); + $additionalfields = explode(',', implode(',', \core\user_fields::get_picture_fields())); $author = username_load_fields_from_object($author, $summary, 'author', $additionalfields); $userpic = $this->output->user_picture($author, array('courseid' => $this->page->course->id, 'size' => 35)); $userurl = new moodle_url('/user/view.php', diff --git a/mod/workshop/view.php b/mod/workshop/view.php index 31e3f448c7f..ecdef78d0bc 100644 --- a/mod/workshop/view.php +++ b/mod/workshop/view.php @@ -405,7 +405,7 @@ case workshop::PHASE_ASSESSMENT: $submission->title = $assessment->submissiontitle; $submission->timecreated = $assessment->submissioncreated; $submission->timemodified = $assessment->submissionmodified; - $userpicturefields = explode(',', user_picture::fields()); + $userpicturefields = explode(',', implode(',', \core\user_fields::get_picture_fields())); foreach ($userpicturefields as $userpicturefield) { $prefixedusernamefield = 'author' . $userpicturefield; $submission->$prefixedusernamefield = $assessment->$prefixedusernamefield; @@ -533,7 +533,7 @@ case workshop::PHASE_EVALUATION: $submission->title = $assessment->submissiontitle; $submission->timecreated = $assessment->submissioncreated; $submission->timemodified = $assessment->submissionmodified; - $userpicturefields = explode(',', user_picture::fields()); + $userpicturefields = explode(',', implode(',', \core\user_fields::get_picture_fields())); foreach ($userpicturefields as $userpicturefield) { $prefixedusernamefield = 'author' . $userpicturefield; $submission->$prefixedusernamefield = $assessment->$prefixedusernamefield; @@ -647,7 +647,7 @@ case workshop::PHASE_CLOSED: $submission->title = $assessment->submissiontitle; $submission->timecreated = $assessment->submissioncreated; $submission->timemodified = $assessment->submissionmodified; - $userpicturefields = explode(',', user_picture::fields()); + $userpicturefields = explode(',', implode(',', \core\user_fields::get_picture_fields())); foreach ($userpicturefields as $userpicturefield) { $prefixedusernamefield = 'author' . $userpicturefield; $submission->$prefixedusernamefield = $assessment->$prefixedusernamefield; diff --git a/question/classes/bank/creator_name_column.php b/question/classes/bank/creator_name_column.php index f217286d7b9..835bef07d61 100644 --- a/question/classes/bank/creator_name_column.php +++ b/question/classes/bank/creator_name_column.php @@ -55,7 +55,7 @@ class creator_name_column extends column_base { } public function get_required_fields() { - $allnames = get_all_user_name_fields(); + $allnames = \core\user_fields::get_name_fields(); $requiredfields = array(); foreach ($allnames as $allname) { $requiredfields[] = 'uc.' . $allname . ' AS creator' . $allname; diff --git a/question/classes/bank/modifier_name_column.php b/question/classes/bank/modifier_name_column.php index 29d7645c885..db0a5c26509 100644 --- a/question/classes/bank/modifier_name_column.php +++ b/question/classes/bank/modifier_name_column.php @@ -55,7 +55,7 @@ class modifier_name_column extends column_base { } public function get_required_fields() { - $allnames = get_all_user_name_fields(); + $allnames = \core\user_fields::get_name_fields(); $requiredfields = array(); foreach ($allnames as $allname) { $requiredfields[] = 'um.' . $allname . ' AS modifier' . $allname; diff --git a/rating/classes/external.php b/rating/classes/external.php index 0ae8a6c95e5..4fac9d67cf6 100644 --- a/rating/classes/external.php +++ b/rating/classes/external.php @@ -154,7 +154,7 @@ class core_rating_external extends external_api { $result['timemodified'] = $rating->timemodified; // The rating object has all the required fields for generating the picture url. - // Undo the aliasing of the user id column from user_picture::fields(). + // Undo the aliasing of the user id column from user_fields::get_sql. $rating->id = $rating->userid; $userpicture = new user_picture($rating); $userpicture->size = 1; // Size f1. diff --git a/rating/index.php b/rating/index.php index e61857290b9..aa8e3179687 100644 --- a/rating/index.php +++ b/rating/index.php @@ -126,7 +126,7 @@ if (!$ratings) { continue; } - // Undo the aliasing of the user id column from user_picture::fields(). + // Undo the aliasing of the user id column from user_fields::get_sql(). // We could clone the rating object or preserve the rating id if we needed it again // but we don't. $rating->id = $rating->userid; diff --git a/rating/lib.php b/rating/lib.php index f70fbb3ffc8..eef1d4dc19c 100644 --- a/rating/lib.php +++ b/rating/lib.php @@ -455,7 +455,8 @@ class rating_manager { 'component' => $options->component, 'ratingarea' => $options->ratingarea, ); - $userfields = user_picture::fields('u', null, 'userid'); + $userfieldsapi = \core\user_fields::for_userpic(); + $userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects; $sql = "SELECT r.id, r.rating, r.itemid, r.userid, r.timemodified, r.component, r.ratingarea, $userfields FROM {rating} r LEFT JOIN {user} u ON r.userid = u.id diff --git a/report/completion/index.php b/report/completion/index.php index a2355118056..a94b83ee2fd 100644 --- a/report/completion/index.php +++ b/report/completion/index.php @@ -66,7 +66,8 @@ $sifirst = optional_param('sifirst', 'all', PARAM_NOTAGS); $silast = optional_param('silast', 'all', PARAM_NOTAGS); // Whether to show extra user identity information -$extrafields = get_extra_user_fields($context); +// TODO Does not support custom user profile fields (MDL-70456). +$extrafields = \core\user_fields::get_identity_fields($context, false); $leftcols = 1 + count($extrafields); // Check permissions @@ -440,7 +441,7 @@ if (!$csv) { // Print user identity columns foreach ($extrafields as $field) { echo '' . - get_user_field_name($field) . ''; + \core\user_fields::get_display_name($field) . ''; } /// @@ -511,7 +512,7 @@ if (!$csv) { $row[] = get_string('id', 'report_completion'); $row[] = get_string('name', 'report_completion'); foreach ($extrafields as $field) { - $row[] = get_user_field_name($field); + $row[] = \core\user_fields::get_display_name($field); } // Add activity headers diff --git a/report/configlog/classes/output/report_table.php b/report/configlog/classes/output/report_table.php index 3909387c3ff..952b0cb3e5a 100644 --- a/report/configlog/classes/output/report_table.php +++ b/report/configlog/classes/output/report_table.php @@ -86,7 +86,8 @@ class report_table extends \table_sql implements \renderable { protected function init_sql() { global $DB; - $userfields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $fields = 'cl.id, cl.timemodified, cl.plugin, cl.name, cl.value, cl.oldvalue, cl.userid, ' . $userfields; $from = '{config_log} cl @@ -190,4 +191,4 @@ class report_table extends \table_sql implements \renderable { public function col_oldvalue(\stdClass $row) { return $this->format_text($row->oldvalue, FORMAT_PLAIN);; } -} \ No newline at end of file +} diff --git a/report/log/classes/renderable.php b/report/log/classes/renderable.php index 10b580ffff7..3da88e962a1 100644 --- a/report/log/classes/renderable.php +++ b/report/log/classes/renderable.php @@ -377,7 +377,9 @@ class report_log_renderable implements renderable { $context = context_course::instance($courseid); $limitfrom = empty($this->showusers) ? 0 : ''; $limitnum = empty($this->showusers) ? COURSE_MAX_USERS_PER_DROPDOWN + 1 : ''; - $courseusers = get_enrolled_users($context, '', $this->groupid, 'u.id, ' . get_all_user_name_fields(true, 'u'), + $userfieldsapi = \core\user_fields::for_name(); + $courseusers = get_enrolled_users($context, '', $this->groupid, 'u.id, ' . + $userfieldsapi->get_sql('u', false, '', '', false)->selects, null, $limitfrom, $limitnum); if (count($courseusers) < COURSE_MAX_USERS_PER_DROPDOWN && !$this->showusers) { diff --git a/report/log/classes/table_log.php b/report/log/classes/table_log.php index 845fac2e696..782dc351f79 100644 --- a/report/log/classes/table_log.php +++ b/report/log/classes/table_log.php @@ -125,7 +125,9 @@ class report_log_table_log extends table_sql { // If we reach that point new users logs have been generated since the last users db query. list($usql, $uparams) = $DB->get_in_or_equal($userid); - $sql = "SELECT id," . get_all_user_name_fields(true) . " FROM {user} WHERE id " . $usql; + $userfieldsapi = \core\user_fields::for_name(); + $sql = "SELECT id," . $userfieldsapi->get_sql('', false, '', '', false)->selects . + " FROM {user} WHERE id " . $usql; if (!$user = $DB->get_records_sql($sql, $uparams)) { return false; } @@ -589,7 +591,9 @@ class report_log_table_log extends table_sql { // Get user fullname and put that in return list. if (!empty($userids)) { list($usql, $uparams) = $DB->get_in_or_equal($userids); - $users = $DB->get_records_sql("SELECT id," . get_all_user_name_fields(true) . " FROM {user} WHERE id " . $usql, + $userfieldsapi = \core\user_fields::for_name(); + $users = $DB->get_records_sql("SELECT id," . $userfieldsapi->get_sql('', false, '', '', false)->selects . + " FROM {user} WHERE id " . $usql, $uparams); foreach ($users as $userid => $user) { $this->userfullnames[$userid] = fullname($user); diff --git a/report/log/locallib.php b/report/log/locallib.php index e0251c7cabc..4440e1dd76d 100644 --- a/report/log/locallib.php +++ b/report/log/locallib.php @@ -289,11 +289,15 @@ function report_log_print_mnet_selector_form($hostid, $course, $selecteduser=0, // If looking at a different host, we're interested in all our site users if ($hostid == $CFG->mnet_localhost_id && $course->id != SITEID) { - $courseusers = get_enrolled_users($context, '', $selectedgroup, 'u.id, ' . get_all_user_name_fields(true, 'u'), + $userfieldsapi = \core\user_fields::for_name(); + $courseusers = get_enrolled_users($context, '', $selectedgroup, 'u.id, ' . + $userfieldsapi->get_sql('u', false, '', '', false)->selects, null, $limitfrom, $limitnum); } else { // this may be a lot of users :-( - $courseusers = $DB->get_records('user', array('deleted'=>0), 'lastaccess DESC', 'id, ' . get_all_user_name_fields(true), + $userfieldsapi = \core\user_fields::for_name(); + $courseusers = $DB->get_records('user', array('deleted'=>0), 'lastaccess DESC', 'id, ' . + $userfieldsapi->get_sql('', false, '', '', false)->selects, $limitfrom, $limitnum); } diff --git a/report/loglive/classes/table_log.php b/report/loglive/classes/table_log.php index 798a801467a..21e997baf43 100644 --- a/report/loglive/classes/table_log.php +++ b/report/loglive/classes/table_log.php @@ -374,7 +374,9 @@ class report_loglive_table_log extends table_sql { // Get user fullname and put that in return list. if (!empty($userids)) { list($usql, $uparams) = $DB->get_in_or_equal($userids); - $users = $DB->get_records_sql("SELECT id," . get_all_user_name_fields(true) . " FROM {user} WHERE id " . $usql, + $userfieldsapi = \core\user_fields::for_name(); + $users = $DB->get_records_sql("SELECT id," . + $userfieldsapi->get_sql('', false, '', '', false)->selects . " FROM {user} WHERE id " . $usql, $uparams); foreach ($users as $userid => $user) { $this->userfullnames[$userid] = fullname($user); diff --git a/report/participation/index.php b/report/participation/index.php index c328f657607..f6ef6c21301 100644 --- a/report/participation/index.php +++ b/report/participation/index.php @@ -225,7 +225,8 @@ if (!empty($instanceid) && !empty($roleid)) { $params = array_merge($params, $crudparams); } - $usernamefields = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $users = array(); // If using legacy log then get users from old table. if ($uselegacyreader || $onlyuselegacyreader) { diff --git a/report/progress/index.php b/report/progress/index.php index 596692cb7a5..704e030c929 100644 --- a/report/progress/index.php +++ b/report/progress/index.php @@ -52,7 +52,8 @@ $silast = optional_param('silast', 'all', PARAM_NOTAGS); $start = optional_param('start', 0, PARAM_INT); // Whether to show extra user identity information -$extrafields = get_extra_user_fields($context); +// TODO Does not support custom user profile fields (MDL-70456). +$extrafields = \core\user_fields::get_identity_fields($context, false); $leftcols = 1 + count($extrafields); function csv_quote($value) { @@ -300,11 +301,11 @@ if (!$csv) { // Print user identity columns foreach ($extrafields as $field) { echo '' . - get_user_field_name($field) . ''; + \core\user_fields::get_display_name($field) . ''; } } else { foreach ($extrafields as $field) { - echo $sep . csv_quote(get_user_field_name($field)); + echo $sep . csv_quote(\core\user_fields::get_display_name($field)); } } diff --git a/report/stats/locallib.php b/report/stats/locallib.php index 65389046c75..0569ba1c401 100644 --- a/report/stats/locallib.php +++ b/report/stats/locallib.php @@ -131,7 +131,8 @@ function report_stats_report($course, $report, $mode, $user, $roleid, $time) { list($sort, $moreparams) = users_order_by_sql('u'); $moreparams['courseid'] = $course->id; - $fields = user_picture::fields('u', array('idnumber')); + $userfieldsapi = \core\user_fields::for_userpic()->including('idnumber'); + $fields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT DISTINCT $fields FROM {stats_user_{$param->table}} s JOIN {user} u ON u.id = s.userid diff --git a/search/classes/engine.php b/search/classes/engine.php index 722e0fb404d..442a6341fba 100644 --- a/search/classes/engine.php +++ b/search/classes/engine.php @@ -150,7 +150,8 @@ abstract class engine { global $DB; if (empty(self::$cachedusers[$userid])) { - $fields = get_all_user_name_fields(true); + $userfieldsapi = \core\user_fields::for_name(); + $fields = $userfieldsapi->get_sql('', false, '', '', false)->selects; self::$cachedusers[$userid] = $DB->get_record('user', array('id' => $userid), 'id, ' . $fields); } return self::$cachedusers[$userid]; diff --git a/tag/classes/manage_table.php b/tag/classes/manage_table.php index ed2e88abd85..2072784a970 100644 --- a/tag/classes/manage_table.php +++ b/tag/classes/manage_table.php @@ -155,7 +155,8 @@ class core_tag_manage_table extends table_sql { $sort = "tg.name"; } - $allusernames = get_all_user_name_fields(true, 'u'); + $userfieldsapi = \core\user_fields::for_name(); + $allusernames = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = " SELECT tg.id, tg.name, tg.rawname, tg.isstandard, tg.flag, tg.timemodified, u.id AS owner, $allusernames, diff --git a/user/action_redir.php b/user/action_redir.php index cccc8e95251..4c0bf0ebb26 100644 --- a/user/action_redir.php +++ b/user/action_redir.php @@ -93,7 +93,8 @@ if ($formaction == 'bulkchange.php') { 'lastname' => get_string('lastname'), ); - $identityfields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $identityfields = \core\user_fields::get_identity_fields($context, false); $identityfieldsselect = ''; foreach ($identityfields as $field) { diff --git a/user/classes/search/user.php b/user/classes/search/user.php index 03f034a71a6..7882fb7f63c 100644 --- a/user/classes/search/user.php +++ b/user/classes/search/user.php @@ -94,7 +94,7 @@ class user extends \core_search\base { $doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname); // Include all alternate names in title. $array = []; - foreach (get_all_user_name_fields(false, null, null, null, true) as $field) { + foreach (\core\user_fields::get_name_fields(true) as $field) { $array[$field] = $record->$field; } $fullusername = join(' ', $array); diff --git a/user/classes/table/participants.php b/user/classes/table/participants.php index f03abed8d54..95c57587e95 100644 --- a/user/classes/table/participants.php +++ b/user/classes/table/participants.php @@ -141,9 +141,10 @@ class participants extends \table_sql implements dynamic_table { $headers[] = get_string('fullname'); $columns[] = 'fullname'; - $extrafields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $extrafields = \core\user_fields::get_identity_fields($this->context, false); foreach ($extrafields as $field) { - $headers[] = get_user_field_name($field); + $headers[] = \core\user_fields::get_display_name($field); $columns[] = $field; } diff --git a/user/classes/table/participants_search.php b/user/classes/table/participants_search.php index 781b86545fa..8e2c11c27a7 100644 --- a/user/classes/table/participants_search.php +++ b/user/classes/table/participants_search.php @@ -77,7 +77,8 @@ class participants_search { $this->context = $context; $this->filterset = $filterset; - $this->userfields = get_extra_user_fields($this->context); + // TODO Does not support custom user profile fields (MDL-70456). + $this->userfields = \core\user_fields::get_identity_fields($this->context, false); } /** diff --git a/user/editlib.php b/user/editlib.php index 56c267045aa..99c3410b4ee 100644 --- a/user/editlib.php +++ b/user/editlib.php @@ -480,7 +480,7 @@ function useredit_get_enabled_name_fields() { global $CFG; // Get all of the other name fields which are not ranked as necessary. - $additionalusernamefields = array_diff(get_all_user_name_fields(), array('firstname', 'lastname')); + $additionalusernamefields = array_diff(\core\user_fields::get_name_fields(), array('firstname', 'lastname')); // Find out which additional name fields are actually being used from the fullnamedisplay setting. $enabledadditionalusernames = array(); foreach ($additionalusernamefields as $enabledname) { @@ -507,7 +507,14 @@ function useredit_get_disabled_name_fields($enabledadditionalusernames = null) { } // These are the additional fields that are not currently enabled. - $nonusednamefields = array_diff(get_all_user_name_fields(), + $nonusednamefields = array_diff(\core\user_fields::get_name_fields(), array_merge(array('firstname', 'lastname'), $enabledadditionalusernames)); - return $nonusednamefields; + + // It may not be significant anywhere, but for compatibility, this used to return an array + // with keys and values the same. + $result = []; + foreach ($nonusednamefields as $field) { + $result[$field] = $field; + } + return $result; } diff --git a/user/lib.php b/user/lib.php index 0134a2ee0d3..9d01bc00321 100644 --- a/user/lib.php +++ b/user/lib.php @@ -302,7 +302,8 @@ function user_get_user_details($user, $course = null, array $userfields = array( $currentuser = ($user->id == $USER->id); $isadmin = is_siteadmin($USER); - $showuseridentityfields = get_extra_user_fields($context); + // TODO Does not support custom user profile fields (MDL-70456). + $showuseridentityfields = \core\user_fields::get_identity_fields($context, false); if (!empty($course)) { $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context); diff --git a/user/selector/lib.php b/user/selector/lib.php index 7d79d579553..a1ba079f2ab 100644 --- a/user/selector/lib.php +++ b/user/selector/lib.php @@ -114,7 +114,8 @@ abstract class user_selector_base { } // Populate the list of additional user identifiers to display. - $this->extrafields = get_extra_user_fields($this->accesscontext); + // TODO Does not support custom user profile fields (MDL-70456). + $this->extrafields = \core\user_fields::get_identity_fields($this->accesscontext, false); if (isset($options['exclude']) && is_array($options['exclude'])) { $this->exclude = $options['exclude']; @@ -438,7 +439,7 @@ abstract class user_selector_base { // Raw list of fields. $fields = array('id'); // Add additional name fields. - $fields = array_merge($fields, get_all_user_name_fields(), $this->extrafields); + $fields = array_merge($fields, \core\user_fields::get_name_fields(), $this->extrafields); // Prepend the table alias. if ($u) { diff --git a/userpix/index.php b/userpix/index.php index dedef09d006..99a7758b62f 100644 --- a/userpix/index.php +++ b/userpix/index.php @@ -23,7 +23,7 @@ $PAGE->set_title($title); $PAGE->set_heading($title); echo $OUTPUT->header(); -$rs = $DB->get_recordset_select("user", "deleted = 0 AND picture > 0", array(), "lastaccess DESC", user_picture::fields()); +$rs = $DB->get_recordset_select("user", "deleted = 0 AND picture > 0", array(), "lastaccess DESC", implode(',', \core\user_fields::get_picture_fields())); foreach ($rs as $user) { $fullname = s(fullname($user)); echo "wwwroot/user/view.php?id=$user->id&course=1\" ". diff --git a/webservice/classes/token_table.php b/webservice/classes/token_table.php index 059fc049d8f..f6220570031 100644 --- a/webservice/classes/token_table.php +++ b/webservice/classes/token_table.php @@ -217,8 +217,9 @@ class token_table extends \table_sql { debugging('Initial bar not implemented yet. Call out($pagesize, false)'); } - $usernamefields = get_all_user_name_fields(true, 'u'); - $creatorfields = get_all_user_name_fields(true, 'c', null, 'creator'); + $userfieldsapi = \core\user_fields::for_name(); + $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $creatorfields = $userfieldsapi->get_sql('c', false, 'creator', '', false)->selects; $params = ["tokenmode" => EXTERNAL_TOKEN_PERMANENT]; From 1d7d3c984d5d86d995d6360edad8d7b7630dca6c Mon Sep 17 00:00:00 2001 From: sam marshall Date: Tue, 20 Oct 2020 12:09:43 +0100 Subject: [PATCH 7/9] MDL-45242 Admin: User list supports custom profile fields --- admin/tests/behat/filter_users.feature | 33 +++++++-- admin/user.php | 10 ++- lib/datalib.php | 27 +++++--- lib/tests/datalib_test.php | 96 ++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 22 deletions(-) diff --git a/admin/tests/behat/filter_users.feature b/admin/tests/behat/filter_users.feature index c11b6133e94..85bbf711673 100644 --- a/admin/tests/behat/filter_users.feature +++ b/admin/tests/behat/filter_users.feature @@ -5,12 +5,16 @@ Feature: An administrator can filter user accounts by role, cohort and other pro I need to filter the users account list using different filter Background: - Given the following "users" exist: - | username | firstname | lastname | email | auth | confirmed | lastip | institution | department | - | user1 | User | One | one@example.com | manual | 0 | 127.0.1.1 | moodle | red | - | user2 | User | Two | two@example.com | ldap | 1 | 0.0.0.0 | moodle | blue | - | user3 | User | Three | three@example.com | manual | 1 | 0.0.0.0 | | | - | user4 | User | Four | four@example.com | ldap | 0 | 127.0.1.2 | | | + Given the following "custom profile fields" exist: + | datatype | shortname | name | + | text | frog | Favourite frog | + | text | undead | Type of undead | + And the following "users" exist: + | username | firstname | lastname | email | auth | confirmed | lastip | institution | department | profile_field_frog | profile_field_undead | + | user1 | User | One | one@example.com | manual | 0 | 127.0.1.1 | moodle | red | Kermit | | + | user2 | User | Two | two@example.com | ldap | 1 | 0.0.0.0 | moodle | blue | Mr Toad | Zombie | + | user3 | User | Three | three@example.com | manual | 1 | 0.0.0.0 | | | | | + | user4 | User | Four | four@example.com | ldap | 0 | 127.0.1.2 | | | | | And the following "cohorts" exist: | name | idnumber | | Cohort 1 | CH1 | @@ -116,3 +120,20 @@ Feature: An administrator can filter user accounts by role, cohort and other pro And I press "Add filter" And I should see "User One" And I should not see "User Two" + + Scenario: Filter users by custom profile field (specific or any) + When I set the field "id_profile_fld" to "Favourite frog" + And I set the field "id_profile" to "Kermit" + And I press "Add filter" + Then I should see "User One" + And I should not see "User Two" + And I should not see "User Three" + And I should not see "User Four" + And I press "Remove all filters" + And I set the field "id_profile_fld" to "any field" + And I set the field "id_profile" to "Zombie" + And I press "Add filter" + And I should see "User Two" + And I should not see "User One" + And I should not see "User Three" + And I should not see "User Four" diff --git a/admin/user.php b/admin/user.php index 0f0eec9582b..06ee33bc18e 100644 --- a/admin/user.php +++ b/admin/user.php @@ -183,12 +183,10 @@ // These columns are always shown in the users list. $requiredcolumns = array('city', 'country', 'lastaccess'); // Extra columns containing the extra user fields, excluding the required columns (city and country, to be specific). - // TODO Does not support custom user profile fields (MDL-70456). - $userfields = \core\user_fields::for_identity($context, false)->excluding(...$requiredcolumns); + $userfields = \core\user_fields::for_identity($context, true)->with_name()->excluding(...$requiredcolumns); $extracolumns = $userfields->get_required_fields(); - // Get all user name fields as an array, but with firstname and lastname first. - $allusernamefields = \core\user_fields::get_name_fields(true); - $columns = array_merge($allusernamefields, $extracolumns, $requiredcolumns); + // Get all user name fields as an array. + $columns = array_merge($extracolumns, $requiredcolumns); foreach ($columns as $column) { $string[$column] = \core\user_fields::get_display_name($column); @@ -228,7 +226,7 @@ } // Order in string will ensure that the name columns are in the correct order. - $usernames = order_in_string($allusernamefields, $fullnamesetting); + $usernames = order_in_string($extracolumns, $fullnamesetting); $fullnamedisplay = array(); foreach ($usernames as $name) { // Use the link from $$column for sorting on the user's name. diff --git a/lib/datalib.php b/lib/datalib.php index 68d89f89f8a..64d0c34d358 100644 --- a/lib/datalib.php +++ b/lib/datalib.php @@ -480,7 +480,7 @@ function get_users_listing($sort='lastaccess', $dir='ASC', $page=0, $recordsperp $fullname = $DB->sql_fullname(); - $select = "deleted <> 1 AND id <> :guestid"; + $select = "deleted <> 1 AND u.id <> :guestid"; $params = array('guestid' => $CFG->siteguest); if (!empty($search)) { @@ -503,6 +503,10 @@ function get_users_listing($sort='lastaccess', $dir='ASC', $page=0, $recordsperp } if ($extraselect) { + // The extra WHERE clause may refer to the 'id' column which can now be ambiguous because we + // changed the query to include joins, so replace any 'id' that is on its own (no alias) + // with 'u.id'. + $extraselect = preg_replace('~([ =]|^)id([ =]|$)~', '$1u.id$2', $extraselect); $select .= " AND $extraselect"; $params = $params + (array)$extraparams; } @@ -512,18 +516,21 @@ function get_users_listing($sort='lastaccess', $dir='ASC', $page=0, $recordsperp } // If a context is specified, get extra user fields that the current user - // is supposed to see. - // TODO Does not support custom user profile fields (MDL-70456). - $userfieldsapi = \core\user_fields::for_identity($extracontext, false)->with_name() - ->excluding('id', 'username', 'email', 'firstname', 'lastname', 'city', 'country', - 'lastaccess', 'confirmed', 'mnethostid'); - $extrafields = $userfields->get_sql()->selects; + // is supposed to see, otherwise just get the name fields. + $userfields = \core\user_fields::for_name(); + if ($extracontext) { + $userfields->with_identity($extracontext, true); + } + $userfields->excluding('id', 'username', 'email', 'city', 'country', 'lastaccess', 'confirmed', 'mnethostid'); + ['selects' => $selects, 'joins' => $joins, 'params' => $joinparams] = + (array)$userfields->get_sql('u', true); // warning: will return UNCONFIRMED USERS - return $DB->get_records_sql("SELECT id, username, email, city, country, lastaccess, confirmed, mnethostid, suspended $extrafields - FROM {user} + return $DB->get_records_sql("SELECT u.id, username, email, city, country, lastaccess, confirmed, mnethostid, suspended $selects + FROM {user} u + $joins WHERE $select - $sort", $params, $page, $recordsperpage); + $sort", array_merge($params, $joinparams), $page, $recordsperpage); } diff --git a/lib/tests/datalib_test.php b/lib/tests/datalib_test.php index 76250b27649..8ba4e43e6c1 100644 --- a/lib/tests/datalib_test.php +++ b/lib/tests/datalib_test.php @@ -746,4 +746,100 @@ class core_datalib_testcase extends advanced_testcase { "Please also make sure \$CFG->maxcoursesincategory * MAX_COURSE_CATEGORIES less than max integer. " . "See tracker issues: MDL-25669 and MDL-69573"); } + + /** + * 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,profile_field_specialid'); + + // 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, + 'profile_field_specialid' => 'special_' . $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 the custom field or department because no context specified. + $this->assertObjectNotHasAttribute('department', $results[$userids[0]]); + $this->assertObjectNotHasAttribute('profile_field_specialid', $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)); + + // 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->assertObjectNotHasAttribute('profile_field_specialid', $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); + $this->assertEquals('special_a', $results[$userids[0]]->profile_field_specialid); + + // 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, '', '', '', 'u.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)); + } } From 4f435cc40f702fd4ca57568b1118dabd861af1bb Mon Sep 17 00:00:00 2001 From: sam marshall Date: Tue, 20 Oct 2020 17:55:35 +0100 Subject: [PATCH 8/9] MDL-45242 Course: Participants list supports custom profile fields --- user/classes/table/participants.php | 3 +- user/classes/table/participants_search.php | 39 +++++--- user/tests/behat/filter_participants.feature | 98 +++++++++---------- user/tests/table/participants_search_test.php | 75 +++++++++++++- 4 files changed, 145 insertions(+), 70 deletions(-) diff --git a/user/classes/table/participants.php b/user/classes/table/participants.php index 95c57587e95..09823d73a59 100644 --- a/user/classes/table/participants.php +++ b/user/classes/table/participants.php @@ -141,8 +141,7 @@ class participants extends \table_sql implements dynamic_table { $headers[] = get_string('fullname'); $columns[] = 'fullname'; - // TODO Does not support custom user profile fields (MDL-70456). - $extrafields = \core\user_fields::get_identity_fields($this->context, false); + $extrafields = \core\user_fields::get_identity_fields($this->context); foreach ($extrafields as $field) { $headers[] = \core\user_fields::get_display_name($field); $columns[] = $field; diff --git a/user/classes/table/participants_search.php b/user/classes/table/participants_search.php index 8e2c11c27a7..754c42ec0ba 100644 --- a/user/classes/table/participants_search.php +++ b/user/classes/table/participants_search.php @@ -30,7 +30,7 @@ use core_table\local\filter\filterset; use core_user; use moodle_recordset; use stdClass; -use user_picture; +use core\user_fields; defined('MOODLE_INTERNAL') || die; @@ -77,8 +77,7 @@ class participants_search { $this->context = $context; $this->filterset = $filterset; - // TODO Does not support custom user profile fields (MDL-70456). - $this->userfields = \core\user_fields::get_identity_fields($this->context, false); + $this->userfields = user_fields::get_identity_fields($this->context); } /** @@ -193,7 +192,15 @@ class participants_search { 'params' => $params, ] = $this->get_enrolled_sql(); - $userfieldssql = user_picture::fields('u', $this->userfields); + // Get the fields for all contexts because there is a special case later where it allows + // matches of fields you can't access if they are on your own account. + $userfields = user_fields::for_identity(null)->with_userpic(); + ['selects' => $userfieldssql, 'joins' => $userfieldsjoin, 'params' => $userfieldsparams, 'mappings' => $mappings] = + (array)$userfields->get_sql('u', true); + if ($userfieldsjoin) { + $outerjoins[] = $userfieldsjoin; + $params = array_merge($params, $userfieldsparams); + } // Include any compulsory enrolment SQL (eg capability related filtering that must be applied). if (!empty($esqlforced)) { @@ -207,12 +214,12 @@ class participants_search { } if ($isfrontpage) { - $outerselect = "SELECT {$userfieldssql}, u.lastaccess"; + $outerselect = "SELECT u.lastaccess $userfieldssql"; if ($accesssince) { $wheres[] = user_get_user_lastaccess_sql($accesssince, 'u', $matchaccesssince); } } else { - $outerselect = "SELECT {$userfieldssql}, COALESCE(ul.timeaccess, 0) AS lastaccess"; + $outerselect = "SELECT COALESCE(ul.timeaccess, 0) AS lastaccess $userfieldssql"; // Not everybody has accessed the course yet. $outerjoins[] = 'LEFT JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = :courseid2)'; $params['courseid2'] = $this->course->id; @@ -255,7 +262,7 @@ class participants_search { [ 'where' => $keywordswhere, 'params' => $keywordsparams, - ] = $this->get_keywords_search_sql(); + ] = $this->get_keywords_search_sql($mappings); if (!empty($keywordswhere)) { $wheres[] = $keywordswhere; @@ -873,9 +880,10 @@ class participants_search { /** * Prepare SQL where clause and associated parameters for any keyword searches being performed. * + * @param array $mappings Array of field mappings (fieldname => SQL code for the value) * @return array SQL query data in the format ['where' => '', 'params' => []]. */ - protected function get_keywords_search_sql(): array { + protected function get_keywords_search_sql(array $mappings): array { global $CFG, $DB, $USER; $keywords = []; @@ -964,24 +972,25 @@ class participants_search { $conditions[] = $idnumber; // Search all user identify fields. - // TODO Does not support custom user profile fields (MDL-70456). - $extrasearchfields = \core\user_fields::get_identity_fields(null, false); - foreach ($extrasearchfields as $extrasearchfield) { + $extrasearchfields = user_fields::get_identity_fields(null); + foreach ($extrasearchfields as $fieldindex => $extrasearchfield) { if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) { // Already covered above. Search by country not supported. continue; } - $param = $searchkey3 . $extrasearchfield; - $condition = $DB->sql_like($extrasearchfield, ':' . $param, false, false); + // The param must be short (max 32 characters) so don't include field name. + $param = $searchkey3 . '_ident' . $fieldindex; + $fieldsql = $mappings[$extrasearchfield]; + $condition = $DB->sql_like($fieldsql, ':' . $param, false, false); $params[$param] = "%$keyword%"; if ($notjoin) { - $condition = "($extrasearchfield IS NOT NULL AND {$condition})"; + $condition = "($fieldsql IS NOT NULL AND {$condition})"; } if (!in_array($extrasearchfield, $this->userfields)) { // User cannot see this field, but allow match if their own account. - $userid3 = 'userid' . $index . '3' . $extrasearchfield; + $userid3 = 'userid' . $index . '3_ident' . $fieldindex; $condition = "(". $condition . " AND u.id = :$userid3)"; $params[$userid3] = $USER->id; } diff --git a/user/tests/behat/filter_participants.feature b/user/tests/behat/filter_participants.feature index a7d6715fc6d..730b58b2d54 100644 --- a/user/tests/behat/filter_participants.feature +++ b/user/tests/behat/filter_participants.feature @@ -10,14 +10,17 @@ Feature: Course participants can be filtered | Course 1 | C1 | 1 | ##5 months ago## | | Course 2 | C2 | 0 | ##4 months ago## | | Course 3 | C3 | 0 | ##3 months ago## | + And the following "custom profile fields" exist: + | datatype | shortname | name | + | text | frog | Favourite frog | And the following "users" exist: - | username | firstname | lastname | email | idnumber | country | city | maildisplay | - | student1 | Student | 1 | student1@example.com | SID1 | | SCITY1 | 0 | - | student2 | Student | 2 | student2@example.com | SID2 | GB | SCITY2 | 1 | - | student3 | Student | 3 | student3@example.com | SID3 | AU | SCITY3 | 0 | - | student4 | Student | 4 | student4@moodle.com | SID4 | AT | SCITY4 | 0 | - | student5 | Trendy | Learnson | trendy@learnson.com | SID5 | AU | SCITY5 | 0 | - | patricia | Patricia | Pea | patricia.pea1@example.org | TID1 | US | TCITY1 | 0 | + | username | firstname | lastname | email | idnumber | country | city | maildisplay | profile_field_frog | + | student1 | Student | 1 | student1@example.com | SID1 | | SCITY1 | 0 | Kermit | + | student2 | Student | 2 | student2@example.com | SID2 | GB | SCITY2 | 1 | Mr Toad | + | student3 | Student | 3 | student3@example.com | SID3 | AU | SCITY3 | 0 | | + | student4 | Student | 4 | student4@moodle.com | SID4 | AT | SCITY4 | 0 | | + | student5 | Trendy | Learnson | trendy@learnson.com | SID5 | AU | SCITY5 | 0 | | + | patricia | Patricia | Pea | patricia.pea1@example.org | TID1 | US | TCITY1 | 0 | | And the following "course enrolments" exist: | user | course | role | status | timeend | | student1 | C1 | student | 0 | | @@ -58,8 +61,7 @@ Feature: Course participants can be filtered @javascript Scenario: No filters applied - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants Then I should see "Student 1" in the "participants" "table" And I should see "Student 2" in the "participants" "table" @@ -68,8 +70,7 @@ Feature: Course participants can be filtered @javascript Scenario Outline: Filter users for a course with a single value - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants And I set the field "Match" in the "Filter 1" "fieldset" to "" And I set the field "type" in the "Filter 1" "fieldset" to "" @@ -99,8 +100,7 @@ Feature: Course participants can be filtered @javascript Scenario Outline: Filter users for a course with multiple values for a single filter - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants And I set the field "Match" in the "Filter 1" "fieldset" to "" And I set the field "type" in the "Filter 1" "fieldset" to "" @@ -121,8 +121,7 @@ Feature: Course participants can be filtered @javascript Scenario Outline: Filter users which are group members in several courses - Given I log in as "patricia" - And I am on "Course 3" course homepage + Given I am on the "C3" "Course" page logged in as "patricia" And I navigate to course participants And I set the field "type" in the "Filter 1" "fieldset" to "" And I set the field "Type or select..." in the "Filter 1" "fieldset" to "" @@ -141,8 +140,7 @@ Feature: Course participants can be filtered @javascript Scenario: In separate groups mode, a student in a single group can only view and filter by users in their own group - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants # Unsuspend student 2 for to improve coverage of this test. @@ -201,8 +199,7 @@ Feature: Course participants can be filtered @javascript Scenario: In separate groups mode, a student in multiple groups can only view and filter by users in their own groups - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants # Unsuspend student 2 for to improve coverage of this test. @@ -265,8 +262,7 @@ Feature: Course participants can be filtered @javascript Scenario: Filter users who have no role in a course - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants # Remove the user role. @@ -292,8 +288,7 @@ Feature: Course participants can be filtered @javascript Scenario: Multiple filters applied (All filterset match type) - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants # Match Any: @@ -535,8 +530,7 @@ Feature: Course participants can be filtered @javascript Scenario: Filter match by one or more keywords and modified match types - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants # Match: @@ -610,8 +604,7 @@ Feature: Course participants can be filtered @javascript Scenario: Reorder users without losing filter - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants When I set the field "type" in the "Filter 1" "fieldset" to "Roles" @@ -633,8 +626,7 @@ Feature: Course participants can be filtered @javascript Scenario: Only possible to add filter rows for the number of filters available - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants When I set the field "type" in the "Filter 1" "fieldset" to "Keyword" And I click on "Add condition" "button" @@ -652,8 +644,7 @@ Feature: Course participants can be filtered @javascript Scenario: Rendering filter options for teachers in a course that don't support groups - Given I log in as "patricia" - And I am on "Course 2" course homepage + Given I am on the "C2" "Course" page logged in as "patricia" When I navigate to course participants Then I should see "Roles" in the "type" "field" And I should see "Enrolment methods" in the "type" "field" @@ -661,8 +652,7 @@ Feature: Course participants can be filtered @javascript Scenario: Rendering filter options for students who have limited privileges - Given I log in as "student1" - And I am on "Course 2" course homepage + Given I am on the "C2" "Course" page logged in as "student1" When I navigate to course participants Then I should see "Roles" in the "type" "field" But I should not see "Status" in the "type" "field" @@ -670,10 +660,9 @@ Feature: Course participants can be filtered @javascript Scenario: Filter by user identity fields - Given I log in as "patricia" - And the following config values are set as admin: + Given the following config values are set as admin: | showuseridentity | idnumber,email,city,country | - And I am on "Course 1" course homepage + And I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants # Search by email (only) - should only see visible email + own. @@ -750,8 +739,7 @@ Feature: Course participants can be filtered | showuseridentity | idnumber,email,city,country | And I log out - And I log in as "patricia" - And I am on "Course 1" course homepage + And I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants # Match: @@ -820,8 +808,7 @@ Feature: Course participants can be filtered # Keyword Any ["@example.com"]. # Set the Roles to "All" ["Student"]. - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants And I set the field "Match" in the "Filter 1" "fieldset" to "All" And I set the field "type" in the "Filter 1" "fieldset" to "Roles" @@ -859,8 +846,7 @@ Feature: Course participants can be filtered # Keyword Any ["@example.com"]. # Set the Roles to "All" ["Student"]. - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants When I set the field "Match" in the "Filter 1" "fieldset" to "All" And I set the field "type" in the "Filter 1" "fieldset" to "Roles" @@ -895,8 +881,7 @@ Feature: Course participants can be filtered # Match None: # Keyword Any ["@example.com"]; and # Roles All ["Teacher"]. - Given I log in as "patricia" - And I am on "Course 1" course homepage + Given I am on the "C1" "Course" page logged in as "patricia" And I navigate to course participants # Set the Keyword to "Any" ["@example.com"] @@ -952,8 +937,7 @@ Feature: Course participants can be filtered # Match: # No filters; and # First initial "T". - Given I log in as "patricia" - And I am on "Course 2" course homepage + Given I am on the "C2" "Course" page logged in as "patricia" And I navigate to course participants And I should see "Student 1" in the "participants" "table" And I should see "Student 2" in the "participants" "table" @@ -972,8 +956,7 @@ Feature: Course participants can be filtered # Match: # No filters; and # Last initial "L". - Given I log in as "patricia" - And I am on "Course 2" course homepage + Given I am on the "C2" "Course" page logged in as "patricia" And I navigate to course participants And I should see "Student 1" in the "participants" "table" And I should see "Student 2" in the "participants" "table" @@ -993,8 +976,7 @@ Feature: Course participants can be filtered # No filters; and # First initial "T"; and # Last initial "L". - Given I log in as "patricia" - And I am on "Course 2" course homepage + Given I am on the "C2" "Course" page logged in as "patricia" And I navigate to course participants And I should see "Student 1" in the "participants" "table" And I should see "Student 2" in the "participants" "table" @@ -1014,8 +996,7 @@ Feature: Course participants can be filtered # Match: # Roles All ["Teacher"]; and # First initial "T". - Given I log in as "patricia" - And I am on "Course 2" course homepage + Given I am on the "C2" "Course" page logged in as "patricia" And I navigate to course participants And I should see "Student 1" in the "participants" "table" And I should see "Student 2" in the "participants" "table" @@ -1036,3 +1017,16 @@ Feature: Course participants can be filtered And I should not see "Student 2" in the "participants" "table" And I should not see "Student 3" in the "participants" "table" And I should not see "Patricia Pea" in the "participants" "table" + + @javascript @frogfrog + Scenario: Filtering works correctly with custom profile fields + Given the following config values are set as admin: + | showuseridentity | email,profile_field_frog | + And I am on the "C2" "Course" page logged in as "patricia" + And I navigate to course participants + And I set the field "type" in the "Filter 1" "fieldset" to "Keyword" + And I set the field "Type..." to "Kermit" + And I press enter + And I click on "Apply filters" "button" + Then I should see "Student 1" in the "participants" "table" + And I should not see "Student 2" in the "participants" "table" diff --git a/user/tests/table/participants_search_test.php b/user/tests/table/participants_search_test.php index 89f720e39e5..c95cd085845 100644 --- a/user/tests/table/participants_search_test.php +++ b/user/tests/table/participants_search_test.php @@ -765,13 +765,22 @@ class participants_search_test extends advanced_testcase { * @param int $jointype The join type to use when combining filter values * @param int $count The expected count * @param array $expectedusers + * @param string $asuser If non-blank, uses that user account (for identify field permission checks) * @dataProvider keywords_provider */ - public function test_keywords_filter(array $usersdata, array $keywords, int $jointype, int $count, array $expectedusers): void { + public function test_keywords_filter(array $usersdata, array $keywords, int $jointype, int $count, + array $expectedusers, string $asuser): void { + global $DB; + $course = $this->getDataGenerator()->create_course(); $coursecontext = context_course::instance($course->id); $users = []; + // Create the custom user profile field and put it into showuseridentity. + $this->getDataGenerator()->create_custom_profile_field( + ['datatype' => 'text', 'shortname' => 'frog', 'name' => 'Fave frog']); + set_config('showuseridentity', 'email,profile_field_frog'); + foreach ($usersdata as $username => $userdata) { // Prevent randomly generated field values that may cause false fails. $userdata['firstnamephonetic'] = $userdata['firstnamephonetic'] ?? $userdata['firstname']; @@ -801,6 +810,10 @@ class participants_search_test extends advanced_testcase { } $keywordfilter->set_join_type($jointype); + if ($asuser) { + $this->setUser($DB->get_record('user', ['username' => $asuser])); + } + // Run the search. $search = new participants_search($course, $coursecontext, $filterset); $rs = $search->get_participants(); @@ -835,6 +848,7 @@ class participants_search_test extends advanced_testcase { 'alternatename' => 'Babs', 'firstnamephonetic' => 'Barbra', 'lastnamephonetic' => 'Benit', + 'profile_field_frog' => 'Kermit', ], 'colin.carnforth' => [ 'firstname' => 'Colin', @@ -845,6 +859,7 @@ class participants_search_test extends advanced_testcase { 'firstname' => 'Anthony', 'lastname' => 'Rogers', 'lastnamephonetic' => 'Rowjours', + 'profile_field_frog' => 'Mr Toad', ], 'sarah.rester' => [ 'firstname' => 'Sarah', @@ -958,6 +973,23 @@ class participants_search_test extends advanced_testcase { 'tony.rogers', ], ], + 'ANY: Filter on custom profile field' => (object) [ + 'keywords' => ['Kermit', 'Mr Toad'], + 'jointype' => filter::JOINTYPE_ANY, + 'count' => 2, + 'expectedusers' => [ + 'barbara.bennett', + 'tony.rogers', + ], + 'asuser' => 'admin' + ], + 'ANY: Filter on custom profile field (no permissions)' => (object) [ + 'keywords' => ['Kermit', 'Mr Toad'], + 'jointype' => filter::JOINTYPE_ANY, + 'count' => 0, + 'expectedusers' => [], + 'asuser' => 'barbara.bennett' + ], // Tests for jointype: ALL. 'ALL: No filter' => (object) [ @@ -1065,6 +1097,22 @@ class participants_search_test extends advanced_testcase { 'barbara.bennett', ], ], + 'ALL: Filter on custom profile field' => (object) [ + 'keywords' => ['Kermit', 'Kermi'], + 'jointype' => filter::JOINTYPE_ALL, + 'count' => 1, + 'expectedusers' => [ + 'barbara.bennett', + ], + 'asuser' => 'admin', + ], + 'ALL: Filter on custom profile field (no permissions)' => (object) [ + 'keywords' => ['Kermit', 'Kermi'], + 'jointype' => filter::JOINTYPE_ALL, + 'count' => 0, + 'expectedusers' => [], + 'asuser' => 'barbara.bennett', + ], // Tests for jointype: NONE. 'NONE: No filter' => (object) [ @@ -1205,6 +1253,30 @@ class participants_search_test extends advanced_testcase { 'sarah.rester', ], ], + 'NONE: Filter on custom profile field' => (object) [ + 'keywords' => ['Kermit', 'Mr Toad'], + 'jointype' => filter::JOINTYPE_NONE, + 'count' => 3, + 'expectedusers' => [ + 'adam.ant', + 'colin.carnforth', + 'sarah.rester', + ], + 'asuser' => 'admin', + ], + 'NONE: Filter on custom profile field (no permissions)' => (object) [ + 'keywords' => ['Kermit', 'Mr Toad'], + 'jointype' => filter::JOINTYPE_NONE, + 'count' => 5, + 'expectedusers' => [ + 'adam.ant', + 'barbara.bennett', + 'colin.carnforth', + 'tony.rogers', + 'sarah.rester', + ], + 'asuser' => 'barbara.bennett', + ], ], ], ]; @@ -1218,6 +1290,7 @@ class participants_search_test extends advanced_testcase { 'jointype' => $expectdata->jointype, 'count' => $expectdata->count, 'expectedusers' => $expectdata->expectedusers, + 'asuser' => $expectdata->asuser ?? '' ]; } } From 3cc4d3d2e23bf54737ab5bd67489288c6c3f6266 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Wed, 21 Oct 2020 18:44:10 +0100 Subject: [PATCH 9/9] MDL-45242 Course: Enrol feature supports custom profile fields --- badges/criteria/award_criteria_profile.php | 3 +- enrol/externallib.php | 23 +- enrol/locallib.php | 89 ++++--- .../build/form-potential-user-selector.min.js | 2 +- .../form-potential-user-selector.min.js.map | 2 +- .../amd/src/form-potential-user-selector.js | 20 +- enrol/manual/classes/enrol_users_form.php | 3 +- .../manual/tests/behat/quickenrolment.feature | 227 ++++++++++-------- enrol/tests/course_enrolment_manager_test.php | 173 +++++++++++++ enrol/tests/externallib_test.php | 75 ++++++ grade/report/grader/lib.php | 2 +- lib/moodlelib.php | 6 +- lib/outputcomponents.php | 2 +- mod/chat/lib.php | 5 +- report/completion/index.php | 2 +- report/log/locallib.php | 2 +- user/lib.php | 3 +- userpix/index.php | 3 +- 18 files changed, 482 insertions(+), 160 deletions(-) diff --git a/badges/criteria/award_criteria_profile.php b/badges/criteria/award_criteria_profile.php index bd887db96d1..dbba62aae38 100644 --- a/badges/criteria/award_criteria_profile.php +++ b/badges/criteria/award_criteria_profile.php @@ -88,7 +88,8 @@ class award_criteria_profile extends award_criteria { if (in_array($field, $existing)) { $checked = true; } - $this->config_options($mform, array('id' => $field, 'checked' => $checked, 'name' => \core\user_fields::get_display_name($field), 'error' => false)); + $this->config_options($mform, array('id' => $field, 'checked' => $checked, + 'name' => \core\user_fields::get_display_name($field), 'error' => false)); $none = false; } } diff --git a/enrol/externallib.php b/enrol/externallib.php index e2cfb216587..26dee5065d1 100644 --- a/enrol/externallib.php +++ b/enrol/externallib.php @@ -558,10 +558,20 @@ class core_enrol_external extends external_api { $results = array(); // Add also extra user fields. + $identityfields = \core\user_fields::get_identity_fields($context, true); + $customprofilefields = []; + foreach ($identityfields as $key => $value) { + if ($fieldname = \core\user_fields::match_custom_field($value)) { + unset($identityfields[$key]); + $customprofilefields[$fieldname] = true; + } + } + if ($customprofilefields) { + $identityfields[] = 'customfields'; + } $requiredfields = array_merge( ['id', 'fullname', 'profileimageurl', 'profileimageurlsmall'], - // TODO Does not support custom user profile fields (MDL-70456). - \core\user_fields::get_identity_fields($context, false) + $identityfields ); foreach ($users['users'] as $id => $user) { // Note: We pass the course here to validate that the current user can at least view user details in this course. @@ -569,6 +579,15 @@ class core_enrol_external extends external_api { // user records, and the user has been validated to have course:enrolreview in this course. Otherwise // there is no way to find users who aren't in the course in order to enrol them. if ($userdetails = user_get_user_details($user, $course, $requiredfields)) { + // For custom fields, only return the ones we actually need. + if ($customprofilefields && array_key_exists('customfields', $userdetails)) { + foreach ($userdetails['customfields'] as $key => $data) { + if (!array_key_exists($data['shortname'], $customprofilefields)) { + unset($userdetails['customfields'][$key]); + } + } + $userdetails['customfields'] = array_values($userdetails['customfields']); + } $results[] = $userdetails; } } diff --git a/enrol/locallib.php b/enrol/locallib.php index cd6f9fd8788..ba49c560265 100644 --- a/enrol/locallib.php +++ b/enrol/locallib.php @@ -23,6 +23,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core\user_fields; + defined('MOODLE_INTERNAL') || die(); /** @@ -238,14 +240,15 @@ class course_enrolment_manager { list($instancessql, $params, $filter) = $this->get_instance_sql(); list($filtersql, $moreparams) = $this->get_filter_sql(); $params += $moreparams; - // TODO Does not support custom user profile fields (MDL-70456). - $extrafields = \core\user_fields::get_identity_fields($this->get_context(), false); - $extrafields[] = 'lastaccess'; - $ufields = user_picture::fields('u', $extrafields); - $sql = "SELECT DISTINCT $ufields, COALESCE(ul.timeaccess, 0) AS lastcourseaccess + $userfields = user_fields::for_identity($this->get_context())->with_userpic()->excluding('lastaccess'); + ['selects' => $fieldselect, 'joins' => $fieldjoin, 'params' => $fieldjoinparams] = + (array)$userfields->get_sql('u', true, '', '', false); + $params += $fieldjoinparams; + $sql = "SELECT DISTINCT $fieldselect, COALESCE(ul.timeaccess, 0) AS lastcourseaccess FROM {user} u JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid $instancessql) JOIN {enrol} e ON (e.id = ue.enrolid) + $fieldjoin LEFT JOIN {user_lastaccess} ul ON (ul.courseid = e.courseid AND ul.userid = u.id)"; if ($this->groupfilter) { $sql .= " LEFT JOIN ({groups_members} gm JOIN {groups} g ON (g.id = gm.groupid)) @@ -270,7 +273,7 @@ class course_enrolment_manager { // Search condition. // TODO Does not support custom user profile fields (MDL-70456). - $extrafields = \core\user_fields::get_identity_fields($this->get_context(), false); + $extrafields = user_fields::get_identity_fields($this->get_context(), false); list($sql, $params) = users_search_sql($this->searchfilter, 'u', true, $extrafields); // Role condition. @@ -343,23 +346,26 @@ class course_enrolment_manager { list($ctxcondition, $params) = $DB->get_in_or_equal($this->context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'ctx'); $params['courseid'] = $this->course->id; $params['cid'] = $this->course->id; - // TODO Does not support custom user profile fields (MDL-70456). - $extrafields = \core\user_fields::get_identity_fields($this->get_context(), false); - $ufields = user_picture::fields('u', $extrafields); - $sql = "SELECT ra.id as raid, ra.contextid, ra.component, ctx.contextlevel, ra.roleid, $ufields, - coalesce(u.lastaccess,0) AS lastaccess - FROM {role_assignments} ra - JOIN {user} u ON u.id = ra.userid - JOIN {context} ctx ON ra.contextid = ctx.id - LEFT JOIN ( + $userfields = user_fields::for_identity($this->get_context())->with_userpic(); + ['selects' => $fieldselect, 'joins' => $fieldjoin, 'params' => $fieldjoinparams] = + (array)$userfields->get_sql('u', true); + $params += $fieldjoinparams; + $sql = "SELECT ra.id as raid, ra.contextid, ra.component, ctx.contextlevel, ra.roleid, + coalesce(u.lastaccess,0) AS lastaccess + $fieldselect + FROM {role_assignments} ra + JOIN {user} u ON u.id = ra.userid + JOIN {context} ctx ON ra.contextid = ctx.id + $fieldjoin + LEFT JOIN ( SELECT ue.id, ue.userid FROM {user_enrolments} ue JOIN {enrol} e ON e.id = ue.enrolid WHERE e.courseid = :courseid ) ue ON ue.userid=u.id - WHERE ctx.id $ctxcondition AND - ue.id IS NULL - ORDER BY $sort $direction, ctx.depth DESC"; + WHERE ctx.id $ctxcondition AND + ue.id IS NULL + ORDER BY $sort $direction, ctx.depth DESC"; $this->otherusers[$key] = $DB->get_records_sql($sql, $params, $page*$perpage, $perpage); } return $this->otherusers[$key]; @@ -372,20 +378,33 @@ class course_enrolment_manager { * @param bool $searchanywhere Can the search term be anywhere, or must it be at the start. * @return array with three elements: * string list of fields to SELECT, + * string possible database joins for user fields * string contents of SQL WHERE clause, * array query params. Note that the SQL snippets use named parameters. */ protected function get_basic_search_conditions($search, $searchanywhere) { global $DB, $CFG; + // Get custom user field SQL used for querying all the fields we need (identity, name, and + // user picture). + $userfields = user_fields::for_identity($this->context)->with_name()->with_userpic() + ->excluding('username', 'lastaccess', 'maildisplay'); + ['selects' => $fieldselects, 'joins' => $fieldjoins, 'params' => $params, 'mappings' => $mappings] = + (array)$userfields->get_sql('u', true, '', '', false); + + // Searchable fields are only the identity and name ones (not userpic). + $searchable = array_fill_keys($userfields->get_required_fields( + [user_fields::PURPOSE_IDENTITY, user_fields::PURPOSE_NAME]), true); + // Add some additional sensible conditions $tests = array("u.id <> :guestid", 'u.deleted = 0', 'u.confirmed = 1'); - $params = array('guestid' => $CFG->siteguest); + $params['guestid'] = $CFG->siteguest; if (!empty($search)) { - // TODO Does not support custom user profile fields (MDL-70456). - $conditions = \core\user_fields::get_identity_fields($this->get_context(), false); - foreach (\core\user_fields::get_name_fields() as $field) { - $conditions[] = 'u.'.$field; + // Include identity and name fields as conditions. + foreach ($mappings as $fieldname => $fieldsql) { + if (array_key_exists($fieldname, $searchable)) { + $conditions[] = $fieldsql; + } } $conditions[] = $DB->sql_fullname('u.firstname', 'u.lastname'); if ($searchanywhere) { @@ -403,15 +422,8 @@ class course_enrolment_manager { } $wherecondition = implode(' AND ', $tests); - // TODO Does not support custom user profile fields (MDL-70456). - $userfieldsapi = \core\user_fields::for_identity($this->get_context(), false)->excluding('username', 'lastaccess'); - $extrafields = $userfieldsapi->get_required_fields(); - $extrafields[] = 'username'; - $extrafields[] = 'lastaccess'; - $extrafields[] = 'maildisplay'; - $ufields = user_picture::fields('u', $extrafields); - - return array($ufields, $params, $wherecondition); + $selects = $fieldselects . ', u.username, u.lastaccess, u.maildisplay'; + return [$selects, $fieldjoins, $params, $wherecondition]; } /** @@ -492,11 +504,12 @@ class course_enrolment_manager { $addedenrollment = 0, $returnexactcount = false) { global $DB; - list($ufields, $params, $wherecondition) = $this->get_basic_search_conditions($search, $searchanywhere); + [$ufields, $joins, $params, $wherecondition] = $this->get_basic_search_conditions($search, $searchanywhere); $fields = 'SELECT '.$ufields; $countfields = 'SELECT COUNT(1)'; $sql = " FROM {user} u + $joins LEFT JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = :enrolid) WHERE $wherecondition AND ue.id IS NULL"; @@ -524,11 +537,12 @@ class course_enrolment_manager { public function search_other_users($search = '', $searchanywhere = false, $page = 0, $perpage = 25, $returnexactcount = false) { global $DB, $CFG; - list($ufields, $params, $wherecondition) = $this->get_basic_search_conditions($search, $searchanywhere); + [$ufields, $joins, $params, $wherecondition] = $this->get_basic_search_conditions($search, $searchanywhere); $fields = 'SELECT ' . $ufields; $countfields = 'SELECT COUNT(u.id)'; $sql = " FROM {user} u + $joins LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid = :contextid) WHERE $wherecondition AND ra.id IS NULL"; @@ -552,11 +566,12 @@ class course_enrolment_manager { */ public function search_users(string $search = '', bool $searchanywhere = false, int $page = 0, int $perpage = 25, bool $returnexactcount = false) { - list($ufields, $params, $wherecondition) = $this->get_basic_search_conditions($search, $searchanywhere); + [$ufields, $joins, $params, $wherecondition] = $this->get_basic_search_conditions($search, $searchanywhere); $fields = 'SELECT ' . $ufields; $countfields = 'SELECT COUNT(u.id)'; $sql = " FROM {user} u + $joins JOIN {user_enrolments} ue ON ue.userid = u.id JOIN {enrol} e ON ue.enrolid = e.id WHERE $wherecondition @@ -1053,7 +1068,7 @@ class course_enrolment_manager { $context = $this->get_context(); $now = time(); // TODO Does not support custom user profile fields (MDL-70456). - $extrafields = \core\user_fields::get_identity_fields($context, false); + $extrafields = user_fields::get_identity_fields($context, false); $users = array(); foreach ($userroles as $userrole) { @@ -1132,7 +1147,7 @@ class course_enrolment_manager { $url = new moodle_url($pageurl, $this->get_url_params()); // TODO Does not support custom user profile fields (MDL-70456). - $extrafields = \core\user_fields::get_identity_fields($context, false); + $extrafields = user_fields::get_identity_fields($context, false); $enabledplugins = $this->get_enrolment_plugins(true); diff --git a/enrol/manual/amd/build/form-potential-user-selector.min.js b/enrol/manual/amd/build/form-potential-user-selector.min.js index 70944792b30..520f6313a69 100644 --- a/enrol/manual/amd/build/form-potential-user-selector.min.js +++ b/enrol/manual/amd/build/form-potential-user-selector.min.js @@ -1,2 +1,2 @@ -define ("enrol_manual/form-potential-user-selector",["jquery","core/ajax","core/templates","core/str"],function(a,b,c,d){return{processResults:function processResults(b,c){var d=[];if(a.isArray(c)){a.each(c,function(a,b){d.push({value:b.id,label:b._label})});return d}else{return c}},transport:function transport(e,f,g,h){var i,j=a(e).attr("courseid"),k=a(e).attr("userfields").split(",");if("undefined"==typeof j){j="1"}var l=a(e).attr("enrolid");if("undefined"==typeof l){l=""}var m=parseInt(a(e).attr("perpage"));if(isNaN(m)){m=100}i=b.call([{methodname:"core_enrol_get_potential_users",args:{courseid:j,enrolid:l,search:f,searchanywhere:!0,page:0,perpage:m+1}}]);i[0].then(function(b){var e=[],f=0;if(b.length<=m){a.each(b,function(b,d){var f=d,g=[];a.each(k,function(a,b){if("undefined"!=typeof d[b]&&""!==d[b]){f.hasidentity=!0;g.push(d[b])}});f.identity=g.join(", ");e.push(c.render("enrol_manual/form-user-selector-suggestion",f))});return a.when.apply(a.when,e).then(function(){var c=arguments;a.each(b,function(a,b){b._label=c[f];f++});g(b)})}else{return d.get_string("toomanyuserstoshow","core",">"+m).then(function(a){g(a)})}}).fail(h)}}}); +define ("enrol_manual/form-potential-user-selector",["jquery","core/ajax","core/templates","core/str"],function(a,b,c,d){return{processResults:function processResults(b,c){var d=[];if(a.isArray(c)){a.each(c,function(a,b){d.push({value:b.id,label:b._label})});return d}else{return c}},transport:function transport(e,f,g,h){var i,j=a(e).attr("courseid"),k=a(e).attr("userfields").split(",");if("undefined"==typeof j){j="1"}var l=a(e).attr("enrolid");if("undefined"==typeof l){l=""}var m=parseInt(a(e).attr("perpage"));if(isNaN(m)){m=100}i=b.call([{methodname:"core_enrol_get_potential_users",args:{courseid:j,enrolid:l,search:f,searchanywhere:!0,page:0,perpage:m+1}}]);i[0].then(function(b){var e=[],f=0;if(b.length<=m){a.each(b,function(b,d){var f=d,g=[];a.each(k,function(a,b){var c=/^profile_field_(.*)$/.exec(b);if(c){if(d.customfields){d.customfields.forEach(function(a){if(a.shortname===c[1]){f.hasidentity=!0;g.push(a.value)}})}}else{if("undefined"!=typeof d[b]&&""!==d[b]){f.hasidentity=!0;g.push(d[b])}}});f.identity=g.join(", ");e.push(c.render("enrol_manual/form-user-selector-suggestion",f))});return a.when.apply(a.when,e).then(function(){var c=arguments;a.each(b,function(a,b){b._label=c[f];f++});g(b)})}else{return d.get_string("toomanyuserstoshow","core",">"+m).then(function(a){g(a)})}}).fail(h)}}}); //# sourceMappingURL=form-potential-user-selector.min.js.map diff --git a/enrol/manual/amd/build/form-potential-user-selector.min.js.map b/enrol/manual/amd/build/form-potential-user-selector.min.js.map index 3f9f5baa2cd..33ca074160c 100644 --- a/enrol/manual/amd/build/form-potential-user-selector.min.js.map +++ b/enrol/manual/amd/build/form-potential-user-selector.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/form-potential-user-selector.js"],"names":["define","$","Ajax","Templates","Str","processResults","selector","results","users","isArray","each","index","user","push","value","id","label","_label","transport","query","success","failure","promise","courseid","attr","userfields","split","enrolid","perpage","parseInt","isNaN","call","methodname","args","search","searchanywhere","page","then","promises","i","length","ctx","identity","k","hasidentity","join","render","when","apply","arguments","get_string","toomanyuserstoshow","fail"],"mappings":"AAyBAA,OAAM,6CAAC,CAAC,QAAD,CAAW,WAAX,CAAwB,gBAAxB,CAA0C,UAA1C,CAAD,CAAwD,SAASC,CAAT,CAAYC,CAAZ,CAAkBC,CAAlB,CAA6BC,CAA7B,CAAkC,CAE5F,MAAsE,CAElEC,cAAc,CAAE,wBAASC,CAAT,CAAmBC,CAAnB,CAA4B,CACxC,GAAIC,CAAAA,CAAK,CAAG,EAAZ,CACA,GAAIP,CAAC,CAACQ,OAAF,CAAUF,CAAV,CAAJ,CAAwB,CACpBN,CAAC,CAACS,IAAF,CAAOH,CAAP,CAAgB,SAASI,CAAT,CAAgBC,CAAhB,CAAsB,CAClCJ,CAAK,CAACK,IAAN,CAAW,CACPC,KAAK,CAAEF,CAAI,CAACG,EADL,CAEPC,KAAK,CAAEJ,CAAI,CAACK,MAFL,CAAX,CAIH,CALD,EAMA,MAAOT,CAAAA,CAEV,CATD,IASO,CACH,MAAOD,CAAAA,CACV,CACJ,CAhBiE,CAkBlEW,SAAS,CAAE,mBAASZ,CAAT,CAAmBa,CAAnB,CAA0BC,CAA1B,CAAmCC,CAAnC,CAA4C,IAC/CC,CAAAA,CAD+C,CAE/CC,CAAQ,CAAGtB,CAAC,CAACK,CAAD,CAAD,CAAYkB,IAAZ,CAAiB,UAAjB,CAFoC,CAG/CC,CAAU,CAAGxB,CAAC,CAACK,CAAD,CAAD,CAAYkB,IAAZ,CAAiB,YAAjB,EAA+BE,KAA/B,CAAqC,GAArC,CAHkC,CAInD,GAAwB,WAApB,QAAOH,CAAAA,CAAX,CAAqC,CACjCA,CAAQ,CAAG,GACd,CACD,GAAII,CAAAA,CAAO,CAAG1B,CAAC,CAACK,CAAD,CAAD,CAAYkB,IAAZ,CAAiB,SAAjB,CAAd,CACA,GAAuB,WAAnB,QAAOG,CAAAA,CAAX,CAAoC,CAChCA,CAAO,CAAG,EACb,CACD,GAAIC,CAAAA,CAAO,CAAGC,QAAQ,CAAC5B,CAAC,CAACK,CAAD,CAAD,CAAYkB,IAAZ,CAAiB,SAAjB,CAAD,CAAtB,CACA,GAAIM,KAAK,CAACF,CAAD,CAAT,CAAoB,CAChBA,CAAO,CAAG,GACb,CAEDN,CAAO,CAAGpB,CAAI,CAAC6B,IAAL,CAAU,CAAC,CACjBC,UAAU,CAAE,gCADK,CAEjBC,IAAI,CAAE,CACFV,QAAQ,CAAEA,CADR,CAEFI,OAAO,CAAEA,CAFP,CAGFO,MAAM,CAAEf,CAHN,CAIFgB,cAAc,GAJZ,CAKFC,IAAI,CAAE,CALJ,CAMFR,OAAO,CAAEA,CAAO,CAAG,CANjB,CAFW,CAAD,CAAV,CAAV,CAYAN,CAAO,CAAC,CAAD,CAAP,CAAWe,IAAX,CAAgB,SAAS9B,CAAT,CAAkB,CAC9B,GAAI+B,CAAAA,CAAQ,CAAG,EAAf,CACIC,CAAC,CAAG,CADR,CAGA,GAAIhC,CAAO,CAACiC,MAAR,EAAkBZ,CAAtB,CAA+B,CAE3B3B,CAAC,CAACS,IAAF,CAAOH,CAAP,CAAgB,SAASI,CAAT,CAAgBC,CAAhB,CAAsB,CAClC,GAAI6B,CAAAA,CAAG,CAAG7B,CAAV,CACI8B,CAAQ,CAAG,EADf,CAEAzC,CAAC,CAACS,IAAF,CAAOe,CAAP,CAAmB,SAASc,CAAT,CAAYI,CAAZ,CAAe,CAC9B,GAAuB,WAAnB,QAAO/B,CAAAA,CAAI,CAAC+B,CAAD,CAAX,EAA8C,EAAZ,GAAA/B,CAAI,CAAC+B,CAAD,CAA1C,CAAsD,CAClDF,CAAG,CAACG,WAAJ,IACAF,CAAQ,CAAC7B,IAAT,CAAcD,CAAI,CAAC+B,CAAD,CAAlB,CACH,CACJ,CALD,EAMAF,CAAG,CAACC,QAAJ,CAAeA,CAAQ,CAACG,IAAT,CAAc,IAAd,CAAf,CACAP,CAAQ,CAACzB,IAAT,CAAcV,CAAS,CAAC2C,MAAV,CAAiB,4CAAjB,CAA+DL,CAA/D,CAAd,CACH,CAXD,EAcA,MAAOxC,CAAAA,CAAC,CAAC8C,IAAF,CAAOC,KAAP,CAAa/C,CAAC,CAAC8C,IAAf,CAAqBT,CAArB,EAA+BD,IAA/B,CAAoC,UAAW,CAClD,GAAIJ,CAAAA,CAAI,CAAGgB,SAAX,CACAhD,CAAC,CAACS,IAAF,CAAOH,CAAP,CAAgB,SAASI,CAAT,CAAgBC,CAAhB,CAAsB,CAClCA,CAAI,CAACK,MAAL,CAAcgB,CAAI,CAACM,CAAD,CAAlB,CACAA,CAAC,EACJ,CAHD,EAIAnB,CAAO,CAACb,CAAD,CAEV,CARM,CAUV,CA1BD,IA0BO,CACH,MAAOH,CAAAA,CAAG,CAAC8C,UAAJ,CAAe,oBAAf,CAAqC,MAArC,CAA6C,IAAMtB,CAAnD,EAA4DS,IAA5D,CAAiE,SAASc,CAAT,CAA6B,CACjG/B,CAAO,CAAC+B,CAAD,CAEV,CAHM,CAIV,CAEJ,CArCD,EAqCGC,IArCH,CAqCQ/B,CArCR,CAsCH,CApFiE,CAwFzE,CA1FK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Potential user selector module.\n *\n * @module enrol_manual/form-potential-user-selector\n * @class form-potential-user-selector\n * @package enrol_manual\n * @copyright 2016 Damyon Wiese\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/ajax', 'core/templates', 'core/str'], function($, Ajax, Templates, Str) {\n\n return /** @alias module:enrol_manual/form-potential-user-selector */ {\n\n processResults: function(selector, results) {\n var users = [];\n if ($.isArray(results)) {\n $.each(results, function(index, user) {\n users.push({\n value: user.id,\n label: user._label\n });\n });\n return users;\n\n } else {\n return results;\n }\n },\n\n transport: function(selector, query, success, failure) {\n var promise;\n var courseid = $(selector).attr('courseid');\n var userfields = $(selector).attr('userfields').split(',');\n if (typeof courseid === \"undefined\") {\n courseid = '1';\n }\n var enrolid = $(selector).attr('enrolid');\n if (typeof enrolid === \"undefined\") {\n enrolid = '';\n }\n var perpage = parseInt($(selector).attr('perpage'));\n if (isNaN(perpage)) {\n perpage = 100;\n }\n\n promise = Ajax.call([{\n methodname: 'core_enrol_get_potential_users',\n args: {\n courseid: courseid,\n enrolid: enrolid,\n search: query,\n searchanywhere: true,\n page: 0,\n perpage: perpage + 1\n }\n }]);\n\n promise[0].then(function(results) {\n var promises = [],\n i = 0;\n\n if (results.length <= perpage) {\n // Render the label.\n $.each(results, function(index, user) {\n var ctx = user,\n identity = [];\n $.each(userfields, function(i, k) {\n if (typeof user[k] !== 'undefined' && user[k] !== '') {\n ctx.hasidentity = true;\n identity.push(user[k]);\n }\n });\n ctx.identity = identity.join(', ');\n promises.push(Templates.render('enrol_manual/form-user-selector-suggestion', ctx));\n });\n\n // Apply the label to the results.\n return $.when.apply($.when, promises).then(function() {\n var args = arguments;\n $.each(results, function(index, user) {\n user._label = args[i];\n i++;\n });\n success(results);\n return;\n });\n\n } else {\n return Str.get_string('toomanyuserstoshow', 'core', '>' + perpage).then(function(toomanyuserstoshow) {\n success(toomanyuserstoshow);\n return;\n });\n }\n\n }).fail(failure);\n }\n\n };\n\n});\n"],"file":"form-potential-user-selector.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/form-potential-user-selector.js"],"names":["define","$","Ajax","Templates","Str","processResults","selector","results","users","isArray","each","index","user","push","value","id","label","_label","transport","query","success","failure","promise","courseid","attr","userfields","split","enrolid","perpage","parseInt","isNaN","call","methodname","args","search","searchanywhere","page","then","promises","i","length","ctx","identity","k","result","exec","customfields","forEach","customfield","shortname","hasidentity","join","render","when","apply","arguments","get_string","toomanyuserstoshow","fail"],"mappings":"AAyBAA,OAAM,6CAAC,CAAC,QAAD,CAAW,WAAX,CAAwB,gBAAxB,CAA0C,UAA1C,CAAD,CAAwD,SAASC,CAAT,CAAYC,CAAZ,CAAkBC,CAAlB,CAA6BC,CAA7B,CAAkC,CAE5F,MAAsE,CAElEC,cAAc,CAAE,wBAASC,CAAT,CAAmBC,CAAnB,CAA4B,CACxC,GAAIC,CAAAA,CAAK,CAAG,EAAZ,CACA,GAAIP,CAAC,CAACQ,OAAF,CAAUF,CAAV,CAAJ,CAAwB,CACpBN,CAAC,CAACS,IAAF,CAAOH,CAAP,CAAgB,SAASI,CAAT,CAAgBC,CAAhB,CAAsB,CAClCJ,CAAK,CAACK,IAAN,CAAW,CACPC,KAAK,CAAEF,CAAI,CAACG,EADL,CAEPC,KAAK,CAAEJ,CAAI,CAACK,MAFL,CAAX,CAIH,CALD,EAMA,MAAOT,CAAAA,CAEV,CATD,IASO,CACH,MAAOD,CAAAA,CACV,CACJ,CAhBiE,CAkBlEW,SAAS,CAAE,mBAASZ,CAAT,CAAmBa,CAAnB,CAA0BC,CAA1B,CAAmCC,CAAnC,CAA4C,IAC/CC,CAAAA,CAD+C,CAE/CC,CAAQ,CAAGtB,CAAC,CAACK,CAAD,CAAD,CAAYkB,IAAZ,CAAiB,UAAjB,CAFoC,CAG/CC,CAAU,CAAGxB,CAAC,CAACK,CAAD,CAAD,CAAYkB,IAAZ,CAAiB,YAAjB,EAA+BE,KAA/B,CAAqC,GAArC,CAHkC,CAInD,GAAwB,WAApB,QAAOH,CAAAA,CAAX,CAAqC,CACjCA,CAAQ,CAAG,GACd,CACD,GAAII,CAAAA,CAAO,CAAG1B,CAAC,CAACK,CAAD,CAAD,CAAYkB,IAAZ,CAAiB,SAAjB,CAAd,CACA,GAAuB,WAAnB,QAAOG,CAAAA,CAAX,CAAoC,CAChCA,CAAO,CAAG,EACb,CACD,GAAIC,CAAAA,CAAO,CAAGC,QAAQ,CAAC5B,CAAC,CAACK,CAAD,CAAD,CAAYkB,IAAZ,CAAiB,SAAjB,CAAD,CAAtB,CACA,GAAIM,KAAK,CAACF,CAAD,CAAT,CAAoB,CAChBA,CAAO,CAAG,GACb,CAEDN,CAAO,CAAGpB,CAAI,CAAC6B,IAAL,CAAU,CAAC,CACjBC,UAAU,CAAE,gCADK,CAEjBC,IAAI,CAAE,CACFV,QAAQ,CAAEA,CADR,CAEFI,OAAO,CAAEA,CAFP,CAGFO,MAAM,CAAEf,CAHN,CAIFgB,cAAc,GAJZ,CAKFC,IAAI,CAAE,CALJ,CAMFR,OAAO,CAAEA,CAAO,CAAG,CANjB,CAFW,CAAD,CAAV,CAAV,CAYAN,CAAO,CAAC,CAAD,CAAP,CAAWe,IAAX,CAAgB,SAAS9B,CAAT,CAAkB,CAC9B,GAAI+B,CAAAA,CAAQ,CAAG,EAAf,CACIC,CAAC,CAAG,CADR,CAGA,GAAIhC,CAAO,CAACiC,MAAR,EAAkBZ,CAAtB,CAA+B,CAG3B3B,CAAC,CAACS,IAAF,CAAOH,CAAP,CAAgB,SAASI,CAAT,CAAgBC,CAAhB,CAAsB,CAClC,GAAI6B,CAAAA,CAAG,CAAG7B,CAAV,CACI8B,CAAQ,CAAG,EADf,CAEAzC,CAAC,CAACS,IAAF,CAAOe,CAAP,CAAmB,SAASc,CAAT,CAAYI,CAAZ,CAAe,CAC9B,GAAMC,CAAAA,CAAM,CALC,sBAKE,CAAaC,IAAb,CAAkBF,CAAlB,CAAf,CACA,GAAIC,CAAJ,CAAY,CACR,GAAIhC,CAAI,CAACkC,YAAT,CAAuB,CACnBlC,CAAI,CAACkC,YAAL,CAAkBC,OAAlB,CAA0B,SAASC,CAAT,CAAsB,CAC5C,GAAIA,CAAW,CAACC,SAAZ,GAA0BL,CAAM,CAAC,CAAD,CAApC,CAAyC,CACrCH,CAAG,CAACS,WAAJ,IACAR,CAAQ,CAAC7B,IAAT,CAAcmC,CAAW,CAAClC,KAA1B,CACH,CAEJ,CAND,CAOH,CACJ,CAVD,IAUO,CACH,GAAuB,WAAnB,QAAOF,CAAAA,CAAI,CAAC+B,CAAD,CAAX,EAA8C,EAAZ,GAAA/B,CAAI,CAAC+B,CAAD,CAA1C,CAAsD,CAClDF,CAAG,CAACS,WAAJ,IACAR,CAAQ,CAAC7B,IAAT,CAAcD,CAAI,CAAC+B,CAAD,CAAlB,CACH,CACJ,CACJ,CAlBD,EAmBAF,CAAG,CAACC,QAAJ,CAAeA,CAAQ,CAACS,IAAT,CAAc,IAAd,CAAf,CACAb,CAAQ,CAACzB,IAAT,CAAcV,CAAS,CAACiD,MAAV,CAAiB,4CAAjB,CAA+DX,CAA/D,CAAd,CACH,CAxBD,EA2BA,MAAOxC,CAAAA,CAAC,CAACoD,IAAF,CAAOC,KAAP,CAAarD,CAAC,CAACoD,IAAf,CAAqBf,CAArB,EAA+BD,IAA/B,CAAoC,UAAW,CAClD,GAAIJ,CAAAA,CAAI,CAAGsB,SAAX,CACAtD,CAAC,CAACS,IAAF,CAAOH,CAAP,CAAgB,SAASI,CAAT,CAAgBC,CAAhB,CAAsB,CAClCA,CAAI,CAACK,MAAL,CAAcgB,CAAI,CAACM,CAAD,CAAlB,CACAA,CAAC,EACJ,CAHD,EAIAnB,CAAO,CAACb,CAAD,CAEV,CARM,CAUV,CAxCD,IAwCO,CACH,MAAOH,CAAAA,CAAG,CAACoD,UAAJ,CAAe,oBAAf,CAAqC,MAArC,CAA6C,IAAM5B,CAAnD,EAA4DS,IAA5D,CAAiE,SAASoB,CAAT,CAA6B,CACjGrC,CAAO,CAACqC,CAAD,CAEV,CAHM,CAIV,CAEJ,CAnDD,EAmDGC,IAnDH,CAmDQrC,CAnDR,CAoDH,CAlGiE,CAsGzE,CAxGK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Potential user selector module.\n *\n * @module enrol_manual/form-potential-user-selector\n * @class form-potential-user-selector\n * @package enrol_manual\n * @copyright 2016 Damyon Wiese\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/ajax', 'core/templates', 'core/str'], function($, Ajax, Templates, Str) {\n\n return /** @alias module:enrol_manual/form-potential-user-selector */ {\n\n processResults: function(selector, results) {\n var users = [];\n if ($.isArray(results)) {\n $.each(results, function(index, user) {\n users.push({\n value: user.id,\n label: user._label\n });\n });\n return users;\n\n } else {\n return results;\n }\n },\n\n transport: function(selector, query, success, failure) {\n var promise;\n var courseid = $(selector).attr('courseid');\n var userfields = $(selector).attr('userfields').split(',');\n if (typeof courseid === \"undefined\") {\n courseid = '1';\n }\n var enrolid = $(selector).attr('enrolid');\n if (typeof enrolid === \"undefined\") {\n enrolid = '';\n }\n var perpage = parseInt($(selector).attr('perpage'));\n if (isNaN(perpage)) {\n perpage = 100;\n }\n\n promise = Ajax.call([{\n methodname: 'core_enrol_get_potential_users',\n args: {\n courseid: courseid,\n enrolid: enrolid,\n search: query,\n searchanywhere: true,\n page: 0,\n perpage: perpage + 1\n }\n }]);\n\n promise[0].then(function(results) {\n var promises = [],\n i = 0;\n\n if (results.length <= perpage) {\n // Render the label.\n const profileRegex = /^profile_field_(.*)$/;\n $.each(results, function(index, user) {\n var ctx = user,\n identity = [];\n $.each(userfields, function(i, k) {\n const result = profileRegex.exec(k);\n if (result) {\n if (user.customfields) {\n user.customfields.forEach(function(customfield) {\n if (customfield.shortname === result[1]) {\n ctx.hasidentity = true;\n identity.push(customfield.value);\n }\n\n });\n }\n } else {\n if (typeof user[k] !== 'undefined' && user[k] !== '') {\n ctx.hasidentity = true;\n identity.push(user[k]);\n }\n }\n });\n ctx.identity = identity.join(', ');\n promises.push(Templates.render('enrol_manual/form-user-selector-suggestion', ctx));\n });\n\n // Apply the label to the results.\n return $.when.apply($.when, promises).then(function() {\n var args = arguments;\n $.each(results, function(index, user) {\n user._label = args[i];\n i++;\n });\n success(results);\n return;\n });\n\n } else {\n return Str.get_string('toomanyuserstoshow', 'core', '>' + perpage).then(function(toomanyuserstoshow) {\n success(toomanyuserstoshow);\n return;\n });\n }\n\n }).fail(failure);\n }\n\n };\n\n});\n"],"file":"form-potential-user-selector.min.js"} \ No newline at end of file diff --git a/enrol/manual/amd/src/form-potential-user-selector.js b/enrol/manual/amd/src/form-potential-user-selector.js index c2e53d82fd5..f485cbf38c4 100644 --- a/enrol/manual/amd/src/form-potential-user-selector.js +++ b/enrol/manual/amd/src/form-potential-user-selector.js @@ -77,13 +77,27 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/str'], function($, Ajax, if (results.length <= perpage) { // Render the label. + const profileRegex = /^profile_field_(.*)$/; $.each(results, function(index, user) { var ctx = user, identity = []; $.each(userfields, function(i, k) { - if (typeof user[k] !== 'undefined' && user[k] !== '') { - ctx.hasidentity = true; - identity.push(user[k]); + const result = profileRegex.exec(k); + if (result) { + if (user.customfields) { + user.customfields.forEach(function(customfield) { + if (customfield.shortname === result[1]) { + ctx.hasidentity = true; + identity.push(customfield.value); + } + + }); + } + } else { + if (typeof user[k] !== 'undefined' && user[k] !== '') { + ctx.hasidentity = true; + identity.push(user[k]); + } } }); ctx.identity = identity.join(', '); diff --git a/enrol/manual/classes/enrol_users_form.php b/enrol/manual/classes/enrol_users_form.php index 8fde2485872..959cf7224b8 100644 --- a/enrol/manual/classes/enrol_users_form.php +++ b/enrol/manual/classes/enrol_users_form.php @@ -93,8 +93,7 @@ class enrol_manual_enrol_users_form extends moodleform { 'courseid' => $course->id, 'enrolid' => $instance->id, 'perpage' => $CFG->maxusersperpage, - // TODO Does not support custom user profile fields (MDL-70456). - 'userfields' => implode(',', \core\user_fields::get_identity_fields($context, false)) + 'userfields' => implode(',', \core\user_fields::get_identity_fields($context, true)) ); $mform->addElement('autocomplete', 'userlist', get_string('selectusers', 'enrol_manual'), array(), $options); diff --git a/enrol/manual/tests/behat/quickenrolment.feature b/enrol/manual/tests/behat/quickenrolment.feature index 564f69f02e5..d0b720abc67 100644 --- a/enrol/manual/tests/behat/quickenrolment.feature +++ b/enrol/manual/tests/behat/quickenrolment.feature @@ -5,108 +5,111 @@ Feature: Teacher can search and enrol users one by one into the course I can search for the students and enrol them into the course Background: - Given the following "users" exist: - | username | firstname | lastname | email | - | teacher001 | Teacher | 001 | teacher001@example.com | - | student001 | Student | 001 | student001@example.com | - | student002 | Student | 002 | student002@example.com | - | student003 | Student | 003 | student003@example.com | - | student004 | Student | 004 | student004@example.com | - | student005 | Student | 005 | student005@example.com | - | student006 | Student | 006 | student006@example.com | - | student007 | Student | 007 | student007@example.com | - | student008 | Student | 008 | student008@example.com | - | student009 | Student | 009 | student009@example.com | - | student010 | Student | 010 | student010@example.com | - | student011 | Student | 011 | student011@example.com | - | student012 | Student | 012 | student012@example.com | - | student013 | Student | 013 | student013@example.com | - | student014 | Student | 014 | student014@example.com | - | student015 | Student | 015 | student015@example.com | - | student016 | Student | 016 | student016@example.com | - | student017 | Student | 017 | student017@example.com | - | student018 | Student | 018 | student018@example.com | - | student019 | Student | 019 | student019@example.com | - | student020 | Student | 020 | student020@example.com | - | student021 | Student | 021 | student021@example.com | - | student022 | Student | 022 | student022@example.com | - | student023 | Student | 023 | student023@example.com | - | student024 | Student | 024 | student024@example.com | - | student025 | Student | 025 | student025@example.com | - | student026 | Student | 026 | student026@example.com | - | student027 | Student | 027 | student027@example.com | - | student028 | Student | 028 | student028@example.com | - | student029 | Student | 029 | student029@example.com | - | student030 | Student | 030 | student030@example.com | - | student031 | Student | 031 | student031@example.com | - | student032 | Student | 032 | student032@example.com | - | student033 | Student | 033 | student033@example.com | - | student034 | Student | 034 | student034@example.com | - | student035 | Student | 035 | student035@example.com | - | student036 | Student | 036 | student036@example.com | - | student037 | Student | 037 | student037@example.com | - | student038 | Student | 038 | student038@example.com | - | student039 | Student | 039 | student039@example.com | - | student040 | Student | 040 | student040@example.com | - | student041 | Student | 041 | student041@example.com | - | student042 | Student | 042 | student042@example.com | - | student043 | Student | 043 | student043@example.com | - | student044 | Student | 044 | student044@example.com | - | student045 | Student | 045 | student045@example.com | - | student046 | Student | 046 | student046@example.com | - | student047 | Student | 047 | student047@example.com | - | student048 | Student | 048 | student048@example.com | - | student049 | Student | 049 | student049@example.com | - | student050 | Student | 050 | student050@example.com | - | student051 | Student | 051 | student051@example.com | - | student052 | Student | 052 | student052@example.com | - | student053 | Student | 053 | student053@example.com | - | student054 | Student | 054 | student054@example.com | - | student055 | Student | 055 | student055@example.com | - | student056 | Student | 056 | student056@example.com | - | student057 | Student | 057 | student057@example.com | - | student058 | Student | 058 | student058@example.com | - | student059 | Student | 059 | student059@example.com | - | student060 | Student | 060 | student060@example.com | - | student061 | Student | 061 | student061@example.com | - | student062 | Student | 062 | student062@example.com | - | student063 | Student | 063 | student063@example.com | - | student064 | Student | 064 | student064@example.com | - | student065 | Student | 065 | student065@example.com | - | student066 | Student | 066 | student066@example.com | - | student067 | Student | 067 | student067@example.com | - | student068 | Student | 068 | student068@example.com | - | student069 | Student | 069 | student069@example.com | - | student070 | Student | 070 | student070@example.com | - | student071 | Student | 071 | student071@example.com | - | student072 | Student | 072 | student072@example.com | - | student073 | Student | 073 | student073@example.com | - | student074 | Student | 074 | student074@example.com | - | student075 | Student | 075 | student075@example.com | - | student076 | Student | 076 | student076@example.com | - | student077 | Student | 077 | student077@example.com | - | student078 | Student | 078 | student078@example.com | - | student079 | Student | 079 | student079@example.com | - | student080 | Student | 080 | student080@example.com | - | student081 | Student | 081 | student081@example.com | - | student082 | Student | 082 | student082@example.com | - | student083 | Student | 083 | student083@example.com | - | student084 | Student | 084 | student084@example.com | - | student085 | Student | 085 | student085@example.com | - | student086 | Student | 086 | student086@example.com | - | student087 | Student | 087 | student087@example.com | - | student088 | Student | 088 | student088@example.com | - | student089 | Student | 089 | student089@example.com | - | student090 | Student | 090 | student090@example.com | - | student091 | Student | 091 | student091@example.com | - | student092 | Student | 092 | student092@example.com | - | student093 | Student | 093 | student093@example.com | - | student094 | Student | 094 | student094@example.com | - | student095 | Student | 095 | student095@example.com | - | student096 | Student | 096 | student096@example.com | - | student097 | Student | 097 | student097@example.com | - | student098 | Student | 098 | student098@example.com | - | student099 | Student | 099 | student099@example.com | + Given the following "custom profile fields" exist: + | datatype | shortname | name | + | text | customid | Custom user id | + And the following "users" exist: + | username | firstname | lastname | email | profile_field_customid | + | teacher001 | Teacher | 001 | teacher001@example.com | | + | student001 | Student | 001 | student001@example.com | Q994 | + | student002 | Student | 002 | student002@example.com | Q008 | + | student003 | Student | 003 | student003@example.com | Z442 | + | student004 | Student | 004 | student004@example.com | | + | student005 | Student | 005 | student005@example.com | | + | student006 | Student | 006 | student006@example.com | | + | student007 | Student | 007 | student007@example.com | | + | student008 | Student | 008 | student008@example.com | | + | student009 | Student | 009 | student009@example.com | | + | student010 | Student | 010 | student010@example.com | | + | student011 | Student | 011 | student011@example.com | | + | student012 | Student | 012 | student012@example.com | | + | student013 | Student | 013 | student013@example.com | | + | student014 | Student | 014 | student014@example.com | | + | student015 | Student | 015 | student015@example.com | | + | student016 | Student | 016 | student016@example.com | | + | student017 | Student | 017 | student017@example.com | | + | student018 | Student | 018 | student018@example.com | | + | student019 | Student | 019 | student019@example.com | | + | student020 | Student | 020 | student020@example.com | | + | student021 | Student | 021 | student021@example.com | | + | student022 | Student | 022 | student022@example.com | | + | student023 | Student | 023 | student023@example.com | | + | student024 | Student | 024 | student024@example.com | | + | student025 | Student | 025 | student025@example.com | | + | student026 | Student | 026 | student026@example.com | | + | student027 | Student | 027 | student027@example.com | | + | student028 | Student | 028 | student028@example.com | | + | student029 | Student | 029 | student029@example.com | | + | student030 | Student | 030 | student030@example.com | | + | student031 | Student | 031 | student031@example.com | | + | student032 | Student | 032 | student032@example.com | | + | student033 | Student | 033 | student033@example.com | | + | student034 | Student | 034 | student034@example.com | | + | student035 | Student | 035 | student035@example.com | | + | student036 | Student | 036 | student036@example.com | | + | student037 | Student | 037 | student037@example.com | | + | student038 | Student | 038 | student038@example.com | | + | student039 | Student | 039 | student039@example.com | | + | student040 | Student | 040 | student040@example.com | | + | student041 | Student | 041 | student041@example.com | | + | student042 | Student | 042 | student042@example.com | | + | student043 | Student | 043 | student043@example.com | | + | student044 | Student | 044 | student044@example.com | | + | student045 | Student | 045 | student045@example.com | | + | student046 | Student | 046 | student046@example.com | | + | student047 | Student | 047 | student047@example.com | | + | student048 | Student | 048 | student048@example.com | | + | student049 | Student | 049 | student049@example.com | | + | student050 | Student | 050 | student050@example.com | | + | student051 | Student | 051 | student051@example.com | | + | student052 | Student | 052 | student052@example.com | | + | student053 | Student | 053 | student053@example.com | | + | student054 | Student | 054 | student054@example.com | | + | student055 | Student | 055 | student055@example.com | | + | student056 | Student | 056 | student056@example.com | | + | student057 | Student | 057 | student057@example.com | | + | student058 | Student | 058 | student058@example.com | | + | student059 | Student | 059 | student059@example.com | | + | student060 | Student | 060 | student060@example.com | | + | student061 | Student | 061 | student061@example.com | | + | student062 | Student | 062 | student062@example.com | | + | student063 | Student | 063 | student063@example.com | | + | student064 | Student | 064 | student064@example.com | | + | student065 | Student | 065 | student065@example.com | | + | student066 | Student | 066 | student066@example.com | | + | student067 | Student | 067 | student067@example.com | | + | student068 | Student | 068 | student068@example.com | | + | student069 | Student | 069 | student069@example.com | | + | student070 | Student | 070 | student070@example.com | | + | student071 | Student | 071 | student071@example.com | | + | student072 | Student | 072 | student072@example.com | | + | student073 | Student | 073 | student073@example.com | | + | student074 | Student | 074 | student074@example.com | | + | student075 | Student | 075 | student075@example.com | | + | student076 | Student | 076 | student076@example.com | | + | student077 | Student | 077 | student077@example.com | | + | student078 | Student | 078 | student078@example.com | | + | student079 | Student | 079 | student079@example.com | | + | student080 | Student | 080 | student080@example.com | | + | student081 | Student | 081 | student081@example.com | | + | student082 | Student | 082 | student082@example.com | | + | student083 | Student | 083 | student083@example.com | | + | student084 | Student | 084 | student084@example.com | | + | student085 | Student | 085 | student085@example.com | | + | student086 | Student | 086 | student086@example.com | | + | student087 | Student | 087 | student087@example.com | | + | student088 | Student | 088 | student088@example.com | | + | student089 | Student | 089 | student089@example.com | | + | student090 | Student | 090 | student090@example.com | | + | student091 | Student | 091 | student091@example.com | | + | student092 | Student | 092 | student092@example.com | | + | student093 | Student | 093 | student093@example.com | | + | student094 | Student | 094 | student094@example.com | | + | student095 | Student | 095 | student095@example.com | | + | student096 | Student | 096 | student096@example.com | | + | student097 | Student | 097 | student097@example.com | | + | student098 | Student | 098 | student098@example.com | | + | student099 | Student | 099 | student099@example.com | | And the following "courses" exist: | fullname | shortname | format | startdate | | Course 001 | C001 | weeks | ##1 month ago## | @@ -189,6 +192,26 @@ Feature: Teacher can search and enrol users one by one into the course And I type "student100@example.com" And I should see "student100@example.com, 1234567892, 1234567893, ABC1, ABC2" + @javascript + Scenario: Custom user profile fields work for search and display, if user has permission + Given the following config values are set as admin: + | showuseridentity | email,profile_field_customid | + And I navigate to course participants + And I press "Enrol users" + When I set the field "Select users" to "Q994" + Then I should see "student001@example.com, Q994" + And I click on "Cancel" "button" in the "Enrol users" "dialogue" + And the following "permission overrides" exist: + | capability | permission | role | contextlevel | reference | + | moodle/site:viewuseridentity | Prevent | editingteacher | Course | C001 | + And I press "Enrol users" + # Do this by keyboard because the 'I set the field' step doesn't let you set it to a missing value. + And I press tab + And I press tab + And I press tab + And I type "Q994" + And I should see "No suggestions" + # The following tests are commented out as a result of MDL-66339. # @javascript # Scenario: Enrol user from participants page diff --git a/enrol/tests/course_enrolment_manager_test.php b/enrol/tests/course_enrolment_manager_test.php index 11c4146d410..0eaa3fe24f2 100644 --- a/enrol/tests/course_enrolment_manager_test.php +++ b/enrol/tests/course_enrolment_manager_test.php @@ -254,6 +254,127 @@ class core_course_enrolment_manager_testcase extends advanced_testcase { $this->assertArrayHasKey($this->users['user22']->id, $users); } + /** + * Sets up a custom profile field and the showuseridentity option, and creates a test user + * with suitable values set. + * + * @return stdClass Test user + */ + protected function setup_for_user_identity_tests(): stdClass { + // Configure extra fields to include one normal user field and one profile field, and + // set the values for a new test user. + $generator = $this->getDataGenerator(); + $generator->create_custom_profile_field(['datatype' => 'text', + 'shortname' => 'researchtopic', 'name' => 'Research topic']); + set_config('showuseridentity', 'email,department,profile_field_researchtopic'); + return $generator->create_user( + ['username' => 'newuser', 'department' => 'Amphibian studies', 'email' => 'x@x.org', + 'profile_field_researchtopic' => 'Frogs', 'imagealt' => 'Smart suit']); + } + + /** + * Checks that the get_users function returns the correct user fields. + */ + public function test_get_users_fields() { + global $PAGE; + + $this->resetAfterTest(); + $newuser = $this->setup_for_user_identity_tests(); + + // Enrol the user in test course. + $this->getDataGenerator()->enrol_user($newuser->id, $this->course->id, 'student'); + + // Get all users and fish out the one we're interested in. + $manager = new course_enrolment_manager($PAGE, $this->course); + $users = $manager->get_users('id'); + $user = $users[$newuser->id]; + + // Should include core required fields... + $this->assertEquals($newuser->id, $user->id); + + // ...And the ones specified in showuseridentity (one of which is also needed for user pics). + $this->assertEquals('Amphibian studies', $user->department); + $this->assertEquals('Frogs', $user->profile_field_researchtopic); + $this->assertEquals('x@x.org', $user->email); + + // And the ones necessary for user pics. + $this->assertEquals('Smart suit', $user->imagealt); + + // But not some random other field like city. + $this->assertObjectNotHasAttribute('city', $user); + } + + /** + * Checks that the get_other_users function returns the correct user fields. + */ + public function test_get_other_users_fields() { + global $PAGE, $DB; + + $this->resetAfterTest(); + + // Configure extra fields to include one normal user field and one profile field, and + // set the values for a new test user. + $newuser = $this->setup_for_user_identity_tests(); + $context = \context_course::instance($this->course->id); + role_assign($DB->get_field('role', 'id', ['shortname' => 'manager']), $newuser->id, $context->id); + + // Get the 'other' (role but not enrolled) users and fish out the one we're interested in. + $manager = new course_enrolment_manager($PAGE, $this->course); + $users = array_values($manager->get_other_users('id')); + $user = $users[0]; + + // Should include core required fields... + $this->assertEquals($newuser->id, $user->id); + + // ...And the ones specified in showuseridentity (one of which is also needed for user pics). + $this->assertEquals('Amphibian studies', $user->department); + $this->assertEquals('Frogs', $user->profile_field_researchtopic); + $this->assertEquals('x@x.org', $user->email); + + // And the ones necessary for user pics. + $this->assertEquals('Smart suit', $user->imagealt); + + // But not some random other field like city. + $this->assertObjectNotHasAttribute('city', $user); + } + + /** + * Checks that the get_potential_users function returns the correct user fields. + */ + public function test_get_potential_users_fields() { + global $PAGE; + + $this->resetAfterTest(); + + // Configure extra fields to include one normal user field and one profile field, and + // set the values for a new test user. + $newuser = $this->setup_for_user_identity_tests(); + + // Get the 'potential' (not enrolled) users and fish out the one we're interested in. + $manager = new course_enrolment_manager($PAGE, $this->course); + foreach (enrol_get_instances($this->course->id, true) as $enrolinstance) { + if ($enrolinstance->enrol === 'manual') { + $enrolid = $enrolinstance->id; + } + } + $users = array_values($manager->get_potential_users($enrolid)); + $user = $users[0][$newuser->id]; + + // Should include core required fields... + $this->assertEquals($newuser->id, $user->id); + + // ...And the ones specified in showuseridentity (one of which is also needed for user pics). + $this->assertEquals('Amphibian studies', $user->department); + $this->assertEquals('Frogs', $user->profile_field_researchtopic); + $this->assertEquals('x@x.org', $user->email); + + // And the ones necessary for user pics. + $this->assertEquals('Smart suit', $user->imagealt); + + // But not some random other field like city. + $this->assertObjectNotHasAttribute('city', $user); + } + /** * Test get_potential_users without returnexactcount param. * @@ -290,6 +411,58 @@ class core_course_enrolment_manager_testcase extends advanced_testcase { } } + /** + * Tests get_potential_users when the search term includes a custom field. + */ + public function test_get_potential_users_search_fields() { + global $PAGE; + + $this->resetAfterTest(); + + // Configure extra fields to include one normal user field and one profile field, and + // set the values for a new test user. + $newuser = $this->setup_for_user_identity_tests(); + + // Set up the enrolment manager. + $manager = new course_enrolment_manager($PAGE, $this->course); + foreach (enrol_get_instances($this->course->id, true) as $enrolinstance) { + if ($enrolinstance->enrol === 'manual') { + $enrolid = $enrolinstance->id; + } + } + + // Search for text included in a 'standard' (user table) identity field. + $users = array_values($manager->get_potential_users($enrolid, 'Amphibian studies')); + $this->assertEquals([$newuser->id], array_keys($users[0])); + + // And for text included in a custom field. + $users = array_values($manager->get_potential_users($enrolid, 'Frogs')); + $this->assertEquals([$newuser->id], array_keys($users[0])); + + // With partial matches. + $users = array_values($manager->get_potential_users($enrolid, 'Amphibian')); + $this->assertEquals([$newuser->id], array_keys($users[0])); + $users = array_values($manager->get_potential_users($enrolid, 'Fro')); + $this->assertEquals([$newuser->id], array_keys($users[0])); + + // With partial in-the-middle matches. + $users = array_values($manager->get_potential_users($enrolid, 'phibian')); + $this->assertEquals([], array_keys($users[0])); + $users = array_values($manager->get_potential_users($enrolid, 'rog')); + $this->assertEquals([], array_keys($users[0])); + $users = array_values($manager->get_potential_users($enrolid, 'phibian', true)); + $this->assertEquals([$newuser->id], array_keys($users[0])); + $users = array_values($manager->get_potential_users($enrolid, 'rog', true)); + $this->assertEquals([$newuser->id], array_keys($users[0])); + + // If the current user doesn't have access to identity fields then these searches won't work. + $this->setUser($this->getDataGenerator()->create_user()); + $users = array_values($manager->get_potential_users($enrolid, 'Amphibian studies')); + $this->assertEquals([], array_keys($users[0])); + $users = array_values($manager->get_potential_users($enrolid, 'Frogs')); + $this->assertEquals([], array_keys($users[0])); + } + /** * Test search_other_users with returnexactcount param. * diff --git a/enrol/tests/externallib_test.php b/enrol/tests/externallib_test.php index c126c90b312..3db6a354395 100644 --- a/enrol/tests/externallib_test.php +++ b/enrol/tests/externallib_test.php @@ -1476,4 +1476,79 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase { $result = core_enrol_external::search_users($course1->id, 'yada yada', true, 0, 30); $this->assertCount(0, $result); } + + /** + * Tests the get_potential_users external function (not too much detail because the back-end + * is covered in another test). + */ + public function test_get_potential_users(): void { + $this->resetAfterTest(); + + // Create a couple of custom profile fields, one of which is in user identity. + $generator = $this->getDataGenerator(); + $generator->create_custom_profile_field(['datatype' => 'text', + 'shortname' => 'researchtopic', 'name' => 'Research topic']); + $generator->create_custom_profile_field(['datatype' => 'text', + 'shortname' => 'specialid', 'name' => 'Special id']); + set_config('showuseridentity', 'department,profile_field_specialid'); + + // Create a course. + $course = $generator->create_course(); + + // Get enrol id for manual enrol plugin. + foreach (enrol_get_instances($course->id, true) as $instance) { + if ($instance->enrol === 'manual') { + $enrolid = $instance->id; + } + } + + // Create a couple of test users. + $user1 = $generator->create_user(['firstname' => 'Eigh', 'lastname' => 'User', + 'department' => 'Amphibians', 'profile_field_specialid' => 'Q123', + 'profile_field_researchtopic' => 'Frogs']); + $user2 = $generator->create_user(['firstname' => 'Anne', 'lastname' => 'Other', + 'department' => 'Amphibians', 'profile_field_specialid' => 'Q456', + 'profile_field_researchtopic' => 'Toads']); + + // Do this as admin user. + $this->setAdminUser(); + + // Get potential users and extract the 2 we care about. + $result = core_enrol_external::get_potential_users($course->id, $enrolid, '', false, 0, 10); + $result1 = $this->extract_user_from_result($result, $user1->id); + $result2 = $this->extract_user_from_result($result, $user2->id); + + // Check the fields are the expected ones. + $this->assertEquals(['id', 'fullname', 'customfields', + 'profileimageurl', 'profileimageurlsmall', 'department'], array_keys($result1)); + $this->assertEquals('Eigh User', $result1['fullname']); + $this->assertEquals('Amphibians', $result1['department']); + + // Check the custom fields ONLY include the user identity one. + $fieldvalues = []; + foreach ($result1['customfields'] as $customfield) { + $fieldvalues[$customfield['shortname']] = $customfield['value']; + } + $this->assertEquals(['specialid'], array_keys($fieldvalues)); + $this->AssertEquals('Q123', $fieldvalues['specialid']); + + // Just check user 2 is the right user. + $this->assertEquals('Anne Other', $result2['fullname']); + } + + /** + * Utility function to get one user out of the get_potential_users result. + * + * @param array $result Result array + * @param int $userid User id + * @return array Data for that user + */ + protected function extract_user_from_result(array $result, int $userid): array { + foreach ($result as $item) { + if ($item['id'] == $userid) { + return $item; + } + } + $this->fail('User not in result: ' . $userid); + } } diff --git a/grade/report/grader/lib.php b/grade/report/grader/lib.php index 4409d500963..38a6659edaf 100644 --- a/grade/report/grader/lib.php +++ b/grade/report/grader/lib.php @@ -1963,7 +1963,7 @@ class grade_report_grader extends grade_report { foreach ($extrafields as $field) { $fieldlink = html_writer::link(new moodle_url($this->baseurl, - array('sortitemid'=>$field)), \core\user_fields::get_display_name($field)); + array('sortitemid' => $field)), \core\user_fields::get_display_name($field)); $arrows[$field] = $fieldlink; if ($field == $this->sortitemid) { diff --git a/lib/moodlelib.php b/lib/moodlelib.php index f6fea8ee107..107016a06a5 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -3645,9 +3645,9 @@ function fullname($user, $override=false) { * @return object User name fields. */ function username_load_fields_from_object($addtoobject, $secondobject, $prefix = null, $additionalfields = null) { - $fields = \core\user_fields::get_name_fields(); - foreach ($fields as &$field) { - $field = $prefix . $field; + $fields = []; + foreach (\core\user_fields::get_name_fields() as $field) { + $fields[$field] = $prefix . $field; } if ($additionalfields) { // Additional fields can specify their own 'alias' such as 'id' => 'userid'. This checks to see if diff --git a/lib/outputcomponents.php b/lib/outputcomponents.php index a3d3b1f3c86..2a76c0f34e9 100644 --- a/lib/outputcomponents.php +++ b/lib/outputcomponents.php @@ -230,7 +230,7 @@ class user_picture implements renderable { } if ($needrec) { - $this->user = $DB->get_record('user', array('id'=>$user->id), + $this->user = $DB->get_record('user', array('id' => $user->id), implode(',', \core\user_fields::get_picture_fields()), MUST_EXIST); } else { $this->user = clone($user); diff --git a/mod/chat/lib.php b/mod/chat/lib.php index 9a2b1bf0793..ee019d6088e 100644 --- a/mod/chat/lib.php +++ b/mod/chat/lib.php @@ -907,7 +907,7 @@ function chat_format_message($message, $courseid, $currentuser, $chatlastrow=nul if (isset($users[$message->userid])) { $user = $users[$message->userid]; - } else if ($user = $DB->get_record('user', array('id' => $message->userid), implode(',', \core\user_fields::get_picture_fields()))) { + } else if ($user = $DB->get_record('user', ['id' => $message->userid], implode(',', \core\user_fields::get_picture_fields()))) { $users[$message->userid] = $user; } else { return null; @@ -938,7 +938,8 @@ function chat_format_message_theme ($message, $chatuser, $currentuser, $grouping if (isset($users[$message->userid])) { $sender = $users[$message->userid]; - } else if ($sender = $DB->get_record('user', array('id' => $message->userid), implode(',', \core\user_fields::get_picture_fields()))) { + } else if ($sender = $DB->get_record('user', array('id' => $message->userid), + implode(',', \core\user_fields::get_picture_fields()))) { $users[$message->userid] = $sender; } else { return null; diff --git a/report/completion/index.php b/report/completion/index.php index a94b83ee2fd..dd862a8fdee 100644 --- a/report/completion/index.php +++ b/report/completion/index.php @@ -512,7 +512,7 @@ if (!$csv) { $row[] = get_string('id', 'report_completion'); $row[] = get_string('name', 'report_completion'); foreach ($extrafields as $field) { - $row[] = \core\user_fields::get_display_name($field); + $row[] = \core\user_fields::get_display_name($field); } // Add activity headers diff --git a/report/log/locallib.php b/report/log/locallib.php index 4440e1dd76d..4b6c70bf5a8 100644 --- a/report/log/locallib.php +++ b/report/log/locallib.php @@ -296,7 +296,7 @@ function report_log_print_mnet_selector_form($hostid, $course, $selecteduser=0, } else { // this may be a lot of users :-( $userfieldsapi = \core\user_fields::for_name(); - $courseusers = $DB->get_records('user', array('deleted'=>0), 'lastaccess DESC', 'id, ' . + $courseusers = $DB->get_records('user', array('deleted' => 0), 'lastaccess DESC', 'id, ' . $userfieldsapi->get_sql('', false, '', '', false)->selects, $limitfrom, $limitnum); } diff --git a/user/lib.php b/user/lib.php index 9d01bc00321..65ffbcebd95 100644 --- a/user/lib.php +++ b/user/lib.php @@ -302,7 +302,8 @@ function user_get_user_details($user, $course = null, array $userfields = array( $currentuser = ($user->id == $USER->id); $isadmin = is_siteadmin($USER); - // TODO Does not support custom user profile fields (MDL-70456). + // This does not need to include custom profile fields as it is only used to check specific + // fields below. $showuseridentityfields = \core\user_fields::get_identity_fields($context, false); if (!empty($course)) { diff --git a/userpix/index.php b/userpix/index.php index 99a7758b62f..f3d035cb01b 100644 --- a/userpix/index.php +++ b/userpix/index.php @@ -23,7 +23,8 @@ $PAGE->set_title($title); $PAGE->set_heading($title); echo $OUTPUT->header(); -$rs = $DB->get_recordset_select("user", "deleted = 0 AND picture > 0", array(), "lastaccess DESC", implode(',', \core\user_fields::get_picture_fields())); +$rs = $DB->get_recordset_select("user", "deleted = 0 AND picture > 0", array(), "lastaccess DESC", + implode(',', \core\user_fields::get_picture_fields())); foreach ($rs as $user) { $fullname = s(fullname($user)); echo "wwwroot/user/view.php?id=$user->id&course=1\" ".