This commit is contained in:
Ilya Tregubov 2023-02-23 09:57:45 +08:00
commit 07053e8140
7 changed files with 80 additions and 47 deletions

View file

@ -109,28 +109,30 @@ class user extends base {
} }
/** /**
* Returns column that corresponds to the given identity field * Returns column that corresponds to the given identity field, profile field identifiers will be converted to those
* used by the {@see user_profile_fields} helper
* *
* @param string $identityfield Field from the user table, or the shortname of a custom profile field * @param string $identityfield Field from the user table, or a custom profile field
* @return column * @return column
*/ */
public function get_identity_column(string $identityfield): column { public function get_identity_column(string $identityfield): column {
if (preg_match("/^profile_field_(?<shortname>.*)$/", $identityfield, $matches)) { if (preg_match(fields::PROFILE_FIELD_REGEX, $identityfield, $matches)) {
$identityfield = 'profilefield_' . $matches['shortname']; $identityfield = 'profilefield_' . $matches[1];
} }
return $this->get_column($identityfield); return $this->get_column($identityfield);
} }
/** /**
* Returns filter that corresponds to the given identity field * Returns filter that corresponds to the given identity field, profile field identifiers will be converted to those
* used by the {@see user_profile_fields} helper
* *
* @param string $identityfield Field from the user table, or the shortname of a custom profile field * @param string $identityfield Field from the user table, or a custom profile field
* @return filter * @return filter
*/ */
public function get_identity_filter(string $identityfield): filter { public function get_identity_filter(string $identityfield): filter {
if (preg_match("/^profile_field_(?<shortname>.*)$/", $identityfield, $matches)) { if (preg_match(fields::PROFILE_FIELD_REGEX, $identityfield, $matches)) {
$identityfield = 'profilefield_' . $matches['shortname']; $identityfield = 'profilefield_' . $matches[1];
} }
return $this->get_filter($identityfield); return $this->get_filter($identityfield);

View file

@ -74,8 +74,8 @@ class user_profile_fields {
* @return profile_field_base[] * @return profile_field_base[]
*/ */
private function get_user_profile_fields(): array { private function get_user_profile_fields(): array {
return array_filter(profile_get_user_fields_with_data(0), static function($profilefield): bool { return array_filter(profile_get_user_fields_with_data(0), static function(profile_field_base $profilefield): bool {
return (int)$profilefield->field->visible === (int)PROFILE_VISIBLE_ALL; return $profilefield->is_visible();
}); });
} }

View file

@ -377,20 +377,9 @@ class fields {
$allowed = false; $allowed = false;
if ($allowcustom) { if ($allowcustom) {
require_once($CFG->dirroot . '/user/profile/lib.php'); require_once($CFG->dirroot . '/user/profile/lib.php');
$fieldinfo = profile_get_custom_field_data_by_shortname($matches[1]); $field = profile_get_custom_field_data_by_shortname($matches[1], false);
switch ($fieldinfo->visible ?? -1) { $fieldinstance = profile_get_user_field($field->datatype, $field->id, 0, $field);
case PROFILE_VISIBLE_NONE: $allowed = $fieldinstance->is_visible($context);
case PROFILE_VISIBLE_PRIVATE:
$allowed = !$context || has_capability('moodle/user:viewalldetails', $context);
break;
case PROFILE_VISIBLE_TEACHERS:
// This is actually defined (in user/profile/lib.php) based on whether
// you have moodle/site:viewuseridentity in context. We already checked
// that, so treat it as visible (fall through).
case PROFILE_VISIBLE_ALL:
$allowed = true;
break;
}
} }
if (!$allowed) { if (!$allowed) {
unset($extra[$key]); unset($extra[$key]);

View file

@ -440,12 +440,16 @@ class profile_field_base {
/** /**
* Check if the field data is visible to the current user * Check if the field data is visible to the current user
* @internal This method should not generally be overwritten by child classes. * @internal This method should not generally be overwritten by child classes.
*
* @param context|null $context
* @return bool * @return bool
*/ */
public function is_visible() { public function is_visible(?context $context = null): bool {
global $USER, $COURSE; global $USER, $COURSE;
if ($context === null) {
$context = ($this->userid > 0) ? context_user::instance($this->userid) : context_system::instance(); $context = ($this->userid > 0) ? context_user::instance($this->userid) : context_system::instance();
}
switch ($this->field->visible) { switch ($this->field->visible) {
case PROFILE_VISIBLE_TEACHERS: case PROFILE_VISIBLE_TEACHERS:
@ -593,13 +597,32 @@ class profile_field_base {
} }
} }
/**
* Return profile field instance for given type
*
* @param string $type
* @param int $fieldid
* @param int $userid
* @param stdClass|null $fielddata
* @return profile_field_base
*/
function profile_get_user_field(string $type, int $fieldid = 0, int $userid = 0, ?stdClass $fielddata = null): profile_field_base {
global $CFG;
require_once("{$CFG->dirroot}/user/profile/field/{$type}/field.class.php");
// Return instance of profile field type.
$profilefieldtype = "profile_field_{$type}";
return new $profilefieldtype($fieldid, $userid, $fielddata);
}
/** /**
* Returns an array of all custom field records with any defined data (or empty data), for the specified user id. * Returns an array of all custom field records with any defined data (or empty data), for the specified user id.
* @param int $userid * @param int $userid
* @return profile_field_base[] * @return profile_field_base[]
*/ */
function profile_get_user_fields_with_data(int $userid): array { function profile_get_user_fields_with_data(int $userid): array {
global $DB, $CFG; global $DB;
// Join any user info data present with each user info field for the user object. // Join any user info data present with each user info field for the user object.
$sql = 'SELECT uif.*, uic.name AS categoryname '; $sql = 'SELECT uif.*, uic.name AS categoryname ';
@ -615,11 +638,8 @@ function profile_get_user_fields_with_data(int $userid): array {
$fields = $DB->get_records_sql($sql, ['userid' => $userid]); $fields = $DB->get_records_sql($sql, ['userid' => $userid]);
$data = []; $data = [];
foreach ($fields as $field) { foreach ($fields as $field) {
require_once($CFG->dirroot . '/user/profile/field/' . $field->datatype . '/field.class.php');
$classname = 'profile_field_' . $field->datatype;
$field->hasuserdata = !empty($field->hasuserdata); $field->hasuserdata = !empty($field->hasuserdata);
/** @var profile_field_base $fieldobject */ $fieldobject = profile_get_user_field($field->datatype, $field->id, $userid, $field);
$fieldobject = new $classname($field->id, $userid, $field);
$fieldobject->set_category_name($field->categoryname); $fieldobject->set_category_name($field->categoryname);
unset($field->categoryname); unset($field->categoryname);
$data[] = $fieldobject; $data[] = $fieldobject;

View file

@ -51,7 +51,7 @@ class fields_test extends \advanced_testcase {
* Tests getting the identity fields. * Tests getting the identity fields.
*/ */
public function test_get_identity_fields() { public function test_get_identity_fields() {
global $DB, $CFG; global $DB, $CFG, $COURSE;
$this->resetAfterTest(); $this->resetAfterTest();
@ -81,9 +81,8 @@ class fields_test extends \advanced_testcase {
$usercontext = \context_user::instance($anotheruser->id); $usercontext = \context_user::instance($anotheruser->id);
$generator->enrol_user($user->id, $course->id, 'student'); $generator->enrol_user($user->id, $course->id, 'student');
// When no context is provided, it does no access checks and should return all specified. // When no context is provided, it does no access checks and should return all specified (other than non-visible).
$this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b', $this->assertEquals(['email', 'department', 'profile_field_a', 'profile_field_b', 'profile_field_d'],
'profile_field_c', 'profile_field_d'],
fields::get_identity_fields(null)); fields::get_identity_fields(null));
// If you turn off custom profile fields, you don't get those. // If you turn off custom profile fields, you don't get those.
@ -104,6 +103,7 @@ class fields_test extends \advanced_testcase {
// Give the student the basic identity fields permission (also makes them count as 'teacher' // Give the student the basic identity fields permission (also makes them count as 'teacher'
// for the teacher-restricted field). // for the teacher-restricted field).
$COURSE = $course; // Horrible hack, because PROFILE_VISIBLE_TEACHERS relies on this global.
$roleid = $DB->get_field('role', 'id', ['shortname' => 'student']); $roleid = $DB->get_field('role', 'id', ['shortname' => 'student']);
role_change_permission($roleid, $coursecontext, 'moodle/site:viewuseridentity', CAP_ALLOW); role_change_permission($roleid, $coursecontext, 'moodle/site:viewuseridentity', CAP_ALLOW);
$this->assertEquals(['department', 'profile_field_a', 'profile_field_d'], $this->assertEquals(['department', 'profile_field_a', 'profile_field_d'],

View file

@ -24,14 +24,20 @@ namespace core_user;
* @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */
class profilelib_test extends \advanced_testcase { class profilelib_test extends \advanced_testcase {
/**
* Load required test libraries
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once("{$CFG->dirroot}/user/profile/lib.php");
}
/** /**
* Tests profile_get_custom_fields function and checks it is consistent * Tests profile_get_custom_fields function and checks it is consistent
* with profile_user_record. * with profile_user_record.
*/ */
public function test_get_custom_fields() { public function test_get_custom_fields() {
global $CFG;
require_once($CFG->dirroot . '/user/profile/lib.php');
$this->resetAfterTest(); $this->resetAfterTest();
$user = $this->getDataGenerator()->create_user(); $user = $this->getDataGenerator()->create_user();
@ -235,8 +241,7 @@ class profilelib_test extends \advanced_testcase {
* Tests the profile_get_custom_field_data_by_shortname function when working normally. * Tests the profile_get_custom_field_data_by_shortname function when working normally.
*/ */
public function test_profile_get_custom_field_data_by_shortname_normal() { public function test_profile_get_custom_field_data_by_shortname_normal() {
global $DB, $CFG; global $DB;
require_once($CFG->dirroot . '/user/profile/lib.php');
$this->resetAfterTest(); $this->resetAfterTest();
@ -268,9 +273,6 @@ class profilelib_test extends \advanced_testcase {
* Tests the profile_get_custom_field_data_by_shortname function with a field that doesn't exist. * 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() { public function test_profile_get_custom_field_data_by_shortname_missing() {
global $CFG;
require_once($CFG->dirroot . '/user/profile/lib.php');
$this->assertNull(profile_get_custom_field_data_by_shortname('speciality')); $this->assertNull(profile_get_custom_field_data_by_shortname('speciality'));
} }
@ -306,10 +308,6 @@ class profilelib_test extends \advanced_testcase {
bool $casesensitive, bool $casesensitive,
bool $expectmatch bool $expectmatch
): void { ): void {
global $CFG;
require_once("{$CFG->dirroot}/user/profile/lib.php");
$this->resetAfterTest(); $this->resetAfterTest();
$this->getDataGenerator()->create_custom_profile_field([ $this->getDataGenerator()->create_custom_profile_field([
@ -328,4 +326,26 @@ class profilelib_test extends \advanced_testcase {
$this->assertNull($customfield); $this->assertNull($customfield);
} }
} }
/**
* Test profile field loading via profile_get_user_field helper
*
* @covers ::profile_get_user_field
*/
public function test_profile_get_user_field(): void {
$this->resetAfterTest();
$profilefield = $this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'fruit',
'name' => 'Fruit',
'datatype' => 'text',
]);
$user = $this->getDataGenerator()->create_user(['profile_field_fruit' => 'Apple']);
$fieldinstance = profile_get_user_field('text', $profilefield->id, $user->id);
$this->assertInstanceOf(\profile_field_text::class, $fieldinstance);
$this->assertEquals($profilefield->id, $fieldinstance->fieldid);
$this->assertEquals($user->id, $fieldinstance->userid);
$this->assertEquals('Apple', $fieldinstance->data);
}
} }

View file

@ -12,6 +12,8 @@ This files describes API changes for code that uses the user API.
The get_profile_field_list() returns the profile fields The get_profile_field_list() returns the profile fields
in a format that can be used for choices in a group select menu. in a format that can be used for choices in a group select menu.
* New method `core_user::is_current_user`, useful for components implementing permission callbacks for their preferences * New method `core_user::is_current_user`, useful for components implementing permission callbacks for their preferences
* New `profile_get_user_field` method for returning profile field instance of given type
* The `profile_field_base::is_visible` method now accepts an optional `$context` argument
=== 4.1 === === 4.1 ===