Skip to content

Commit

Permalink
MDL-69194 user: make core_user_update_users return warnings
Browse files Browse the repository at this point in the history
The external function 'core_user_update_users()' always returned 'null' no matter
if a user or users were successfully updated or there were some failures.
So, there was no way for the caller to know which users were updated and which were not.
After the commit changes the function returns an 'external_warnings' instance. The function uses
a delegated transaction for each user to update within a loop. This enables the function to update
as many users as possible. This differs from the previous behavior of the function when it used
a delegate transaction outside of the loop where the users were updated. This resulted in a rollback
of the whole users updating in case any of the users had some invalid data. For each user within a loop
a 'try-catch' block is used to throw exceptions which are actually returned
as warnings by the function when they are caught.
  • Loading branch information
vtos authored and Matt Porritt committed Jun 7, 2021
1 parent 411150a commit 6e7d77d
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 75 deletions.
2 changes: 1 addition & 1 deletion lang/en/error.php
Expand Up @@ -626,4 +626,4 @@
$string['xmldberror'] = 'XMLDB error!';
$string['alreadyloggedin'] = 'You are already logged in as {$a}, you need to log out before logging in as different user.';
$string['youcannotdeletecategory'] = 'You cannot delete category \'{$a}\' because you can neither delete the contents, nor move them elsewhere.';
$string['protected_cc_not_supported'] = 'Protected cartridges not supported.';
$string['protected_cc_not_supported'] = 'Protected cartridges not supported.';
178 changes: 104 additions & 74 deletions user/externallib.php
Expand Up @@ -564,106 +564,136 @@ public static function update_users($users) {
'maxfiles' => 1,
'accepted_types' => 'optimised_image');

$transaction = $DB->start_delegated_transaction();

$warnings = array();
foreach ($params['users'] as $user) {
// First check the user exists.
if (!$existinguser = core_user::get_user($user['id'])) {
continue;
}
// Check if we are trying to update an admin.
if ($existinguser->id != $USER->id and is_siteadmin($existinguser) and !is_siteadmin($USER)) {
continue;
}
// Other checks (deleted, remote or guest users).
if ($existinguser->deleted or is_mnet_remote_user($existinguser) or isguestuser($existinguser->id)) {
continue;
}
// Check duplicated emails.
if (isset($user['email']) && $user['email'] !== $existinguser->email) {
if (!validate_email($user['email'])) {
continue;
} 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)) {
continue;
// Catch any exception while updating a user and return it as a warning.
try {
$transaction = $DB->start_delegated_transaction();

// First check the user exists.
if (!$existinguser = core_user::get_user($user['id'])) {
throw new moodle_exception('invaliduserid');
}
// Check if we are trying to update an admin.
if ($existinguser->id != $USER->id and is_siteadmin($existinguser) and !is_siteadmin($USER)) {
throw new moodle_exception('usernotupdatedadmin');
}
// Other checks (deleted, remote or guest users).
if ($existinguser->deleted) {
throw new moodle_exception('usernotupdateddeleted');
}
if (is_mnet_remote_user($existinguser)) {
throw new moodle_exception('usernotupdatedremote');
}
if (isguestuser($existinguser->id)) {
throw new moodle_exception('usernotupdatedguest');
}
// Check duplicated emails.
if (isset($user['email']) && $user['email'] !== $existinguser->email) {
if (!validate_email($user['email'])) {
throw new moodle_exception('useremailinvalid');
} 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');
}
}
}
}

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.
if (empty($CFG->disableuserimages) && isset($user['userpicture'])) {
$userobject->deletepicture = null;
// Update user picture if it was specified for this user.
if (empty($CFG->disableuserimages) && isset($user['userpicture'])) {
$userobject->deletepicture = null;

if ($user['userpicture'] == 0) {
$userobject->deletepicture = true;
} else {
$userobject->imagefile = $user['userpicture'];
}
if ($user['userpicture'] == 0) {
$userobject->deletepicture = true;
} else {
$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']));
$interests = array_filter($trimmedinterests, function($value) {
return !empty($value);
});
useredit_update_interests($userobject, $interests);
}
// Update user interests.
if (!empty($user['interests'])) {
$trimmedinterests = array_map('trim', explode(',', $user['interests']));
$interests = array_filter($trimmedinterests, function($value) {
return !empty($value);
});
useredit_update_interests($userobject, $interests);
}

// Update user custom fields.
if (!empty($user['customfields'])) {
// 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'];
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);
}
profile_save_data((object) $user);
}

// Trigger event.
\core\event\user_updated::create_from_userid($user['id'])->trigger();
// 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'];
// 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 null;
return ['warnings' => $warnings];
}

/**
* Returns description of method result value
*
* @return null
* @return external_description
* @since Moodle 2.2
*/
public static function update_users_returns() {
return null;
return new external_single_structure(
array(
'warnings' => new external_warnings()
)
);
}

/**
Expand Down

0 comments on commit 6e7d77d

Please sign in to comment.