Merge branch 'master_MDL-69194-core_user_update_users' of https://github.com/mattporritt/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2021-06-08 19:26:28 +02:00
commit 9f960ef744
3 changed files with 167 additions and 77 deletions

View file

@ -564,106 +564,143 @@ class core_user_external extends external_api {
'maxfiles' => 1, 'maxfiles' => 1,
'accepted_types' => 'optimised_image'); 'accepted_types' => 'optimised_image');
$transaction = $DB->start_delegated_transaction(); $warnings = array();
foreach ($params['users'] as $user) { foreach ($params['users'] as $user) {
// First check the user exists. // Catch any exception while updating a user and return it as a warning.
if (!$existinguser = core_user::get_user($user['id'])) { try {
continue; $transaction = $DB->start_delegated_transaction();
}
// Check if we are trying to update an admin. // First check the user exists.
if ($existinguser->id != $USER->id and is_siteadmin($existinguser) and !is_siteadmin($USER)) { if (!$existinguser = core_user::get_user($user['id'])) {
continue; throw new moodle_exception('invaliduserid', '', '', null,
} 'Invalid user ID');
// Other checks (deleted, remote or guest users). }
if ($existinguser->deleted or is_mnet_remote_user($existinguser) or isguestuser($existinguser->id)) { // Check if we are trying to update an admin.
continue; if ($existinguser->id != $USER->id and is_siteadmin($existinguser) and !is_siteadmin($USER)) {
} throw new moodle_exception('usernotupdatedadmin', '', '', null,
// Check duplicated emails. 'Cannot update admin accounts');
if (isset($user['email']) && $user['email'] !== $existinguser->email) { }
if (!validate_email($user['email'])) { // Other checks (deleted, remote or guest users).
continue; if ($existinguser->deleted) {
} else if (empty($CFG->allowaccountssameemail)) { throw new moodle_exception('usernotupdateddeleted', '', '', null,
// Make a case-insensitive query for the given email address and make sure to exclude the user being updated. 'User is a deleted user');
$select = $DB->sql_equal('email', ':email', false) . ' AND mnethostid = :mnethostid AND id <> :userid'; }
$params = array( if (is_mnet_remote_user($existinguser)) {
'email' => $user['email'], throw new moodle_exception('usernotupdatedremote', '', '', null,
'mnethostid' => $CFG->mnet_localhost_id, 'User is a remote user');
'userid' => $user['id'] }
); if (isguestuser($existinguser->id)) {
// Skip if there are other user(s) that already have the same email. throw new moodle_exception('usernotupdatedguest', '', '', null,
if ($DB->record_exists_select('user', $select, $params)) { 'Cannot update guest account');
continue; }
// Check duplicated emails.
if (isset($user['email']) && $user['email'] !== $existinguser->email) {
if (!validate_email($user['email'])) {
throw new moodle_exception('useremailinvalid', '', '', null,
'Invalid email address');
} else if (empty($CFG->allowaccountssameemail)) {
// Make a case-insensitive query for the given email address
// and make sure to exclude the user being updated.
$select = $DB->sql_equal('email', ':email', false) . ' AND mnethostid = :mnethostid AND id <> :userid';
$params = array(
'email' => $user['email'],
'mnethostid' => $CFG->mnet_localhost_id,
'userid' => $user['id']
);
// Skip if there are other user(s) that already have the same email.
if ($DB->record_exists_select('user', $select, $params)) {
throw new moodle_exception('useremailduplicate', '', '', null,
'Duplicate email address');
}
} }
} }
}
user_update_user($user, true, false); user_update_user($user, true, false);
$userobject = (object)$user; $userobject = (object)$user;
// Update user picture if it was specified for this user. // Update user picture if it was specified for this user.
if (empty($CFG->disableuserimages) && isset($user['userpicture'])) { if (empty($CFG->disableuserimages) && isset($user['userpicture'])) {
$userobject->deletepicture = null; $userobject->deletepicture = null;
if ($user['userpicture'] == 0) { if ($user['userpicture'] == 0) {
$userobject->deletepicture = true; $userobject->deletepicture = true;
} else { } else {
$userobject->imagefile = $user['userpicture']; $userobject->imagefile = $user['userpicture'];
}
core_user::update_picture($userobject, $filemanageroptions);
} }
core_user::update_picture($userobject, $filemanageroptions); // Update user interests.
} if (!empty($user['interests'])) {
$trimmedinterests = array_map('trim', explode(',', $user['interests']));
// Update user interests. $interests = array_filter($trimmedinterests, function($value) {
if (!empty($user['interests'])) { return !empty($value);
$trimmedinterests = array_map('trim', explode(',', $user['interests'])); });
$interests = array_filter($trimmedinterests, function($value) { useredit_update_interests($userobject, $interests);
return !empty($value);
});
useredit_update_interests($userobject, $interests);
}
// Update user custom fields.
if (!empty($user['customfields'])) {
foreach ($user['customfields'] as $customfield) {
// Profile_save_data() saves profile file it's expecting a user with the correct id,
// and custom field to be named profile_field_"shortname".
$user["profile_field_".$customfield['type']] = $customfield['value'];
} }
profile_save_data((object) $user);
}
// Trigger event. // Update user custom fields.
\core\event\user_updated::create_from_userid($user['id'])->trigger(); if (!empty($user['customfields'])) {
// Preferences. foreach ($user['customfields'] as $customfield) {
if (!empty($user['preferences'])) { // Profile_save_data() saves profile file it's expecting a user with the correct id,
$userpref = clone($existinguser); // and custom field to be named profile_field_"shortname".
foreach ($user['preferences'] as $preference) { $user["profile_field_".$customfield['type']] = $customfield['value'];
$userpref->{'preference_'.$preference['type']} = $preference['value']; }
profile_save_data((object) $user);
}
// Trigger event.
\core\event\user_updated::create_from_userid($user['id'])->trigger();
// Preferences.
if (!empty($user['preferences'])) {
$userpref = clone($existinguser);
foreach ($user['preferences'] as $preference) {
$userpref->{'preference_'.$preference['type']} = $preference['value'];
}
useredit_update_user_preference($userpref);
}
if (isset($user['suspended']) and $user['suspended']) {
\core\session\manager::kill_user_sessions($user['id']);
}
$transaction->allow_commit();
} catch (Exception $e) {
try {
$transaction->rollback($e);
} catch (Exception $e) {
$warning = [];
$warning['item'] = 'user';
$warning['itemid'] = $user['id'];
if ($e instanceof moodle_exception) {
$warning['warningcode'] = $e->errorcode;
} else {
$warning['warningcode'] = $e->getCode();
}
$warning['message'] = $e->getMessage();
$warnings[] = $warning;
} }
useredit_update_user_preference($userpref);
}
if (isset($user['suspended']) and $user['suspended']) {
\core\session\manager::kill_user_sessions($user['id']);
} }
} }
$transaction->allow_commit(); return ['warnings' => $warnings];
return null;
} }
/** /**
* Returns description of method result value * Returns description of method result value
* *
* @return null * @return external_description
* @since Moodle 2.2 * @since Moodle 2.2
*/ */
public static function update_users_returns() { public static function update_users_returns() {
return null; return new external_single_structure(
array(
'warnings' => new external_warnings()
)
);
} }
/** /**

View file

@ -715,6 +715,7 @@ class core_user_externallib_testcase extends externallib_advanced_testcase {
global $USER, $CFG, $DB; global $USER, $CFG, $DB;
$this->resetAfterTest(true); $this->resetAfterTest(true);
$this->preventResetByRollback();
$wsuser = self::getDataGenerator()->create_user(); $wsuser = self::getDataGenerator()->create_user();
self::setUser($wsuser); self::setUser($wsuser);
@ -780,8 +781,20 @@ class core_user_externallib_testcase extends externallib_advanced_testcase {
$user4['id'] = $userdeleted->id; $user4['id'] = $userdeleted->id;
user_delete_user($userdeleted); user_delete_user($userdeleted);
$user5 = self::getDataGenerator()->create_user();
$user5 = array('id' => $user5->id, 'email' => $user5->email);
// Call the external function. // Call the external function.
core_user_external::update_users(array($user1, $user2, $user3, $user4)); $returnvalue = core_user_external::update_users(array($user1, $user2, $user3, $user4));
$returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
// Check warnings.
$this->assertEquals($user2['id'], $returnvalue['warnings'][0]['itemid']); // Guest user.
$this->assertEquals('usernotupdatedguest', $returnvalue['warnings'][0]['warningcode']);
$this->assertEquals($user3['id'], $returnvalue['warnings'][1]['itemid']); // Admin user.
$this->assertEquals('usernotupdatedadmin', $returnvalue['warnings'][1]['warningcode']);
$this->assertEquals($user4['id'], $returnvalue['warnings'][2]['itemid']); // Deleted user.
$this->assertEquals('usernotupdateddeleted', $returnvalue['warnings'][2]['warningcode']);
$dbuser2 = $DB->get_record('user', array('id' => $user2['id'])); $dbuser2 = $DB->get_record('user', array('id' => $user2['id']));
$this->assertNotEquals($dbuser2->username, $user2['username']); $this->assertNotEquals($dbuser2->username, $user2['username']);
@ -824,6 +837,39 @@ class core_user_externallib_testcase extends externallib_advanced_testcase {
$dbuserdelpic = $DB->get_record('user', array('id' => $user1['id'])); $dbuserdelpic = $DB->get_record('user', array('id' => $user1['id']));
$this->assertEquals(0, $dbuserdelpic->picture, 'Picture must be deleted when sent as 0.'); $this->assertEquals(0, $dbuserdelpic->picture, 'Picture must be deleted when sent as 0.');
// Updating user with an invalid email.
$user5['email'] = 'bogus';
$returnvalue = core_user_external::update_users(array($user5));
$returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
$this->assertEquals('useremailinvalid', $returnvalue['warnings'][0]['warningcode']);
$this->assertStringContainsString('Invalid email address',
$returnvalue['warnings'][0]['message']);
// Updating user with a duplicate email.
$user5['email'] = $user1['email'];
$returnvalue = core_user_external::update_users(array($user1, $user5));
$returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
$this->assertEquals('useremailduplicate', $returnvalue['warnings'][0]['warningcode']);
$this->assertStringContainsString('Duplicate email address',
$returnvalue['warnings'][0]['message']);
// Updating a user that does not exist.
$user5['id'] = -1;
$returnvalue = core_user_external::update_users(array($user5));
$returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
$this->assertEquals('invaliduserid', $returnvalue['warnings'][0]['warningcode']);
$this->assertStringContainsString('Invalid user ID',
$returnvalue['warnings'][0]['message']);
// Updating a remote user.
$user1['mnethostid'] = 5;
user_update_user($user1); // Update user not using webservice.
unset($user1['mnethostid']); // The mnet host ID field is not in the allowed field list for the webservice.
$returnvalue = core_user_external::update_users(array($user1));
$returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
$this->assertEquals('usernotupdatedremote', $returnvalue['warnings'][0]['warningcode']);
$this->assertStringContainsString('User is a remote user',
$returnvalue['warnings'][0]['message']);
// Call without required capability. // Call without required capability.
$this->unassignUserCapability('moodle/user:update', $context->id, $roleid); $this->unassignUserCapability('moodle/user:update', $context->id, $roleid);

View file

@ -1,5 +1,12 @@
This files describes API changes for code that uses the user API. This files describes API changes for code that uses the user API.
=== 4.0 ===
* External function core_user_external::update_users() will now fail on a per user basis. Previously if one user
update failed all users in the operation would fail.
* External function core_user_external::update_users() now returns an error code and message to why a user update
action failed.
=== 3.11 === === 3.11 ===
* Added new core_user/form_user_selector JS module that can be used as the 'ajax' handler for the autocomplete form * Added new core_user/form_user_selector JS module that can be used as the 'ajax' handler for the autocomplete form