require_once(__DIR__ . '/../config.php');
require_once('preferences_form.php');
+require_once($CFG->dirroot.'/user/editlib.php');
$url = new moodle_url('/badges/preferences.php');
$mform->set_data(array('badgeprivacysetting' => get_user_preferences('badgeprivacysetting')));
if (!$mform->is_cancelled() && $data = $mform->get_data()) {
- $setting = $data->badgeprivacysetting;
- set_user_preference('badgeprivacysetting', $setting);
+ useredit_update_user_preference(['id' => $USER->id,
+ 'preference_badgeprivacysetting' => $data->badgeprivacysetting]);
}
if ($mform->is_cancelled()) {
// If preference was not found, look in the old location and convert if found.
$order = array();
if ($value = get_user_preferences('course_overview_course_order')) {
- $order = unserialize($value);
+ $order = unserialize_array($value);
block_course_overview_update_myorder($order);
unset_user_preference('course_overview_course_order');
}
require_once('../config.php');
require_once($CFG->dirroot.'/blog/lib.php');
require_once('preferences_form.php');
+require_once($CFG->dirroot.'/user/editlib.php');
$courseid = optional_param('courseid', SITEID, PARAM_INT);
$modid = optional_param('modid', null, PARAM_INT);
if ($pagesize < 1) {
print_error('invalidpagesize');
}
- set_user_preference('blogpagesize', $pagesize);
+ useredit_update_user_preference(['id' => $USER->id,
+ 'preference_blogpagesize' => $pagesize]);
}
if ($mform->is_cancelled()) {
}
return $context;
}
+
+/**
+ * Implements callback user_preferences, whitelists preferences that users are allowed to update directly
+ *
+ * Used in {@see core_user::fill_preferences_cache()}, see also {@see useredit_update_user_preference()}
+ *
+ * @return array
+ */
+function core_calendar_user_preferences() {
+ $preferences = [];
+ $preferences['calendar_timeformat'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED, 'default' => '0',
+ 'choices' => array('0', CALENDAR_TF_12, CALENDAR_TF_24)
+ );
+ $preferences['calendar_startwday'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0,
+ 'choices' => array(0, 1, 2, 3, 4, 5, 6));
+ $preferences['calendar_maxevents'] = array('type' => PARAM_INT, 'choices' => range(1, 20));
+ $preferences['calendar_lookahead'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 365,
+ 'choices' => array(365, 270, 180, 150, 120, 90, 60, 30, 21, 14, 7, 6, 5, 4, 3, 2, 1));
+ $preferences['calendar_persistflt'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0,
+ 'choices' => array(0, 1));
+ return $preferences;
+}
\ No newline at end of file
// Try looking for old location of user setting that used to store all courses in one serialized user preference.
if (($oldcollapsedpref = get_user_preferences('grade_report_grader_collapsed_categories')) !== null) {
- if ($collapsedall = @unserialize($oldcollapsedpref)) {
+ if ($collapsedall = unserialize_array($oldcollapsedpref)) {
// We found the old-style preference, filter out only categories that belong to this course and update the prefs.
$collapsed = static::filter_collapsed_categories($courseid, $collapsedall);
if (!empty($collapsed['aggregatesonly']) || !empty($collapsed['gradesonly'])) {
/** @var array store user fields properties cache. */
protected static $propertiescache = null;
+ /** @var array store user preferences cache. */
+ protected static $preferencescache = null;
+
/**
* Return user object from db or create noreply or support user,
* if userid matches corse_user::NOREPLY_USER or corse_user::SUPPORT_USER
return self::$propertiescache[$property]['default'];
}
+
+ /**
+ * Definition of updateable user preferences and rules for data and access validation.
+ *
+ * array(
+ * 'preferencename' => array( // Either exact preference name or a regular expression.
+ * 'null' => NULL_ALLOWED, // Defaults to NULL_NOT_ALLOWED. Takes NULL_NOT_ALLOWED or NULL_ALLOWED.
+ * 'type' => PARAM_TYPE, // Expected parameter type of the user field - mandatory
+ * 'choices' => array(1, 2..) // An array of accepted values of the user field - optional
+ * 'default' => $CFG->setting // An default value for the field - optional
+ * 'isregex' => false/true // Whether the name of the preference is a regular expression (default false).
+ * 'permissioncallback' => callable // Function accepting arguments ($user, $preferencename) that checks if current user
+ * // is allowed to modify this preference for given user.
+ * // If not specified core_user::default_preference_permission_check() will be assumed.
+ * 'cleancallback' => callable // Custom callback for cleaning value if something more difficult than just type/choices is needed
+ * // accepts arguments ($value, $preferencename)
+ * )
+ * )
+ *
+ * @return void
+ */
+ protected static function fill_preferences_cache() {
+ if (self::$preferencescache !== null) {
+ return;
+ }
+
+ // Array of user preferences and expected types/values.
+ // Every preference that can be updated directly by user should be added here.
+ $preferences = array();
+ $preferences['auth_forcepasswordchange'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'choices' => array(0, 1),
+ 'permissioncallback' => function($user, $preferencename) {
+ global $USER;
+ $systemcontext = context_system::instance();
+ return ($USER->id != $user->id && (has_capability('moodle/user:update', $systemcontext) ||
+ ($user->timecreated > time() - 10 && has_capability('moodle/user:create', $systemcontext))));
+ });
+ $preferences['usemodchooser'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1,
+ 'choices' => array(0, 1));
+ $preferences['forum_markasreadonnotification'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1,
+ 'choices' => array(0, 1));
+ $preferences['htmleditor'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED,
+ 'cleancallback' => function($value, $preferencename) {
+ if (empty($value) || !array_key_exists($value, core_component::get_plugin_list('editor'))) {
+ return null;
+ }
+ return $value;
+ });
+ $preferences['badgeprivacysetting'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1,
+ 'choices' => array(0, 1), 'permissioncallback' => function($user, $preferencename) {
+ global $CFG, $USER;
+ return !empty($CFG->enablebadges) && $user->id == $USER->id;
+ });
+ $preferences['blogpagesize'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 10,
+ 'permissioncallback' => function($user, $preferencename) {
+ global $USER;
+ return $USER->id == $user->id && has_capability('moodle/blog:view', context_system::instance());
+ });
+
+ // Core components that may want to define their preferences.
+ // List of core components implementing callback is hardcoded here for performance reasons.
+ // TODO MDL-58184 cache list of core components implementing a function.
+ $corecomponents = ['core_message', 'core_calendar'];
+ foreach ($corecomponents as $component) {
+ if (($pluginpreferences = component_callback($component, 'user_preferences')) && is_array($pluginpreferences)) {
+ $preferences += $pluginpreferences;
+ }
+ }
+
+ // Plugins that may define their preferences.
+ if ($pluginsfunction = get_plugins_with_function('user_preferences')) {
+ foreach ($pluginsfunction as $plugintype => $plugins) {
+ foreach ($plugins as $function) {
+ if (($pluginpreferences = call_user_func($function)) && is_array($pluginpreferences)) {
+ $preferences += $pluginpreferences;
+ }
+ }
+ }
+ }
+
+ self::$preferencescache = $preferences;
+ }
+
+ /**
+ * Retrieves the preference definition
+ *
+ * @param string $preferencename
+ * @return array
+ */
+ protected static function get_preference_definition($preferencename) {
+ self::fill_preferences_cache();
+
+ foreach (self::$preferencescache as $key => $preference) {
+ if (empty($preference['isregex'])) {
+ if ($key === $preferencename) {
+ return $preference;
+ }
+ } else {
+ if (preg_match($key, $preferencename)) {
+ return $preference;
+ }
+ }
+ }
+
+ throw new coding_exception('Invalid preference requested.');
+ }
+
+ /**
+ * Default callback used for checking if current user is allowed to change permission of user $user
+ *
+ * @param stdClass $user
+ * @param string $preferencename
+ * @return bool
+ */
+ protected static function default_preference_permission_check($user, $preferencename) {
+ global $USER;
+ if (is_mnet_remote_user($user)) {
+ // Can't edit MNET user.
+ return false;
+ }
+
+ if ($user->id == $USER->id) {
+ // Editing own profile.
+ $systemcontext = context_system::instance();
+ return has_capability('moodle/user:editownprofile', $systemcontext);
+ } else {
+ // Teachers, parents, etc.
+ $personalcontext = context_user::instance($user->id);
+ if (!has_capability('moodle/user:editprofile', $personalcontext)) {
+ return false;
+ }
+ if (is_siteadmin($user->id) and !is_siteadmin($USER)) {
+ // Only admins may edit other admins.
+ return false;
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Can current user edit preference of this/another user
+ *
+ * @param string $preferencename
+ * @param stdClass $user
+ * @return bool
+ */
+ public static function can_edit_preference($preferencename, $user) {
+ if (!isloggedin() || isguestuser()) {
+ // Guests can not edit anything.
+ return false;
+ }
+
+ try {
+ $definition = self::get_preference_definition($preferencename);
+ } catch (coding_exception $e) {
+ return false;
+ }
+
+ if ($user->deleted || !context_user::instance($user->id, IGNORE_MISSING)) {
+ // User is deleted.
+ return false;
+ }
+
+ if (isset($definition['permissioncallback'])) {
+ $callback = $definition['permissioncallback'];
+ if (is_callable($callback)) {
+ return call_user_func_array($callback, [$user, $preferencename]);
+ } else {
+ throw new coding_exception('Permission callback for preference ' . s($preferencename) . ' is not callable');
+ return false;
+ }
+ } else {
+ return self::default_preference_permission_check($user, $preferencename);
+ }
+ }
+
+ /**
+ * Clean value of a user preference
+ *
+ * @param string $value the user preference value to be cleaned.
+ * @param string $preferencename the user preference name
+ * @return string the cleaned preference value
+ */
+ public static function clean_preference($value, $preferencename) {
+
+ $definition = self::get_preference_definition($preferencename);
+
+ if (isset($definition['type']) && $value !== null) {
+ $value = clean_param($value, $definition['type']);
+ }
+
+ if (isset($definition['cleancallback'])) {
+ $callback = $definition['cleancallback'];
+ if (is_callable($callback)) {
+ return $callback($value, $preferencename);
+ } else {
+ throw new coding_exception('Clean callback for preference ' . s($preferencename) . ' is not callable');
+ }
+ } else if ($value === null && (!isset($definition['null']) || $definition['null'] == NULL_ALLOWED)) {
+ return null;
+ } else if (isset($definition['choices'])) {
+ if (!in_array($value, $definition['choices'])) {
+ if (isset($definition['default'])) {
+ return $definition['default'];
+ } else {
+ $first = reset($definition['choices']);
+ return $first;
+ }
+ } else {
+ return $value;
+ }
+ } else {
+ if ($value === null) {
+ return isset($definition['default']) ? $definition['default'] : '';
+ }
+ return $value;
+ }
+ }
}
*
* If a $user object is submitted it's 'preference' property is used for the preferences cache.
*
+ * When additional validation/permission check is needed it is better to use {@see useredit_update_user_preference()}
+ *
* @package core
* @category preference
* @access public
}
}
+/**
+ * Safe analogue of unserialize() that can only parse arrays
+ *
+ * Arrays may contain only integers or strings as both keys and values. Nested arrays are allowed.
+ * Note: If any string (key or value) has semicolon (;) as part of the string parsing will fail.
+ * This is a simple method to substitute unnecessary unserialize() in code and not intended to cover all possible cases.
+ *
+ * @param string $expression
+ * @return array|bool either parsed array or false if parsing was impossible.
+ */
+function unserialize_array($expression) {
+ $subs = [];
+ // Find nested arrays, parse them and store in $subs , substitute with special string.
+ while (preg_match('/([\^;\}])(a:\d+:\{[^\{\}]*\})/', $expression, $matches) && strlen($matches[2]) < strlen($expression)) {
+ $key = '--SUB' . count($subs) . '--';
+ $subs[$key] = unserialize_array($matches[2]);
+ if ($subs[$key] === false) {
+ return false;
+ }
+ $expression = str_replace($matches[2], $key . ';', $expression);
+ }
+
+ // Check the expression is an array.
+ if (!preg_match('/^a:(\d+):\{([^\}]*)\}$/', $expression, $matches1)) {
+ return false;
+ }
+ // Get the size and elements of an array (key;value;key;value;....).
+ $parts = explode(';', $matches1[2]);
+ $size = intval($matches1[1]);
+ if (count($parts) < $size * 2 + 1) {
+ return false;
+ }
+ // Analyze each part and make sure it is an integer or string or a substitute.
+ $value = [];
+ for ($i = 0; $i < $size * 2; $i++) {
+ if (preg_match('/^i:(\d+)$/', $parts[$i], $matches2)) {
+ $parts[$i] = (int)$matches2[1];
+ } else if (preg_match('/^s:(\d+):"(.*)"$/', $parts[$i], $matches3) && strlen($matches3[2]) == (int)$matches3[1]) {
+ $parts[$i] = $matches3[2];
+ } else if (preg_match('/^--SUB\d+--$/', $parts[$i])) {
+ $parts[$i] = $subs[$parts[$i]];
+ } else {
+ return false;
+ }
+ }
+ // Combine keys and values.
+ for ($i = 0; $i < $size * 2; $i += 2) {
+ $value[$parts[$i]] = $parts[$i+1];
+ }
+ return $value;
+}
+
/**
* The lang_string class
*
$CFG->mailprefix = 'mdl-';
$this->assertTrue(validate_email(generate_email_processing_address(23, $modargs)));
}
+
+ /**
+ * Test safe method unserialize_array().
+ */
+ public function test_unserialize_array() {
+ $a = [1, 2, 3];
+ $this->assertEquals($a, unserialize_array(serialize($a)));
+ $this->assertEquals($a, unserialize_array(serialize($a)));
+ $a = ['a' => 1, 2 => 2, 'b' => 'cde'];
+ $this->assertEquals($a, unserialize_array(serialize($a)));
+ $this->assertEquals($a, unserialize_array(serialize($a)));
+ $a = ['a' => 1, 2 => 2, 'b' => 'c"d"e'];
+ $this->assertEquals($a, unserialize_array(serialize($a)));
+ $a = ['a' => 1, 2 => ['c' => 'd', 'e' => 'f'], 'b' => 'cde'];
+ $this->assertEquals($a, unserialize_array(serialize($a)));
+
+ // Can not unserialize if any string contains semicolons.
+ $a = ['a' => 1, 2 => 2, 'b' => 'c"d";e'];
+ $this->assertEquals(false, unserialize_array(serialize($a)));
+
+ // Can not unserialize if there are any objects.
+ $a = (object)['a' => 1, 2 => 2, 'b' => 'cde'];
+ $this->assertEquals(false, unserialize_array(serialize($a)));
+ $a = ['a' => 1, 2 => 2, 'b' => (object)['a' => 'cde']];
+ $this->assertEquals(false, unserialize_array(serialize($a)));
+
+ // Array used in the grader report.
+ $a = array('aggregatesonly' => [51, 34], 'gradesonly' => [21, 45, 78]);
+ $this->assertEquals($a, unserialize_array(serialize($a)));
+ }
}
)
);
- if (empty($params['userid'])) {
- $params['userid'] = $USER->id;
- }
-
- $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
- core_user::require_active_user($user);
+ $user = self::validate_preferences_permissions($params['userid']);
$processor = get_message_processor($name);
$preferences = [];
return $renderer->render_from_template('core_message/preferences_processor', $processoroutput->export_for_template($renderer));
}
+
+/**
+ * Checks if current user is allowed to edit messaging preferences of another user
+ *
+ * @param stdClass $user user whose preferences we are updating
+ * @return bool
+ */
+function core_message_can_edit_message_profile($user) {
+ global $USER;
+ if ($user->id == $USER->id) {
+ return has_capability('moodle/user:editownmessageprofile', context_system::instance());
+ } else {
+ $personalcontext = context_user::instance($user->id);
+ if (!has_capability('moodle/user:editmessageprofile', $personalcontext)) {
+ return false;
+ }
+ if (isguestuser($user)) {
+ return false;
+ }
+ // No editing of admins by non-admins.
+ if (is_siteadmin($user) and !is_siteadmin($USER)) {
+ return false;
+ }
+ return true;
+ }
+}
+
+/**
+ * Implements callback user_preferences, whitelists preferences that users are allowed to update directly
+ *
+ * Used in {@see core_user::fill_preferences_cache()}, see also {@see useredit_update_user_preference()}
+ *
+ * @return array
+ */
+function core_message_user_preferences() {
+
+ $preferences = [];
+ $preferences['message_blocknoncontacts'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0,
+ 'choices' => array(0, 1));
+ $preferences['/^message_provider_([\w\d_]*)_logged(in|off)$/'] = array('isregex' => true, 'type' => PARAM_NOTAGS,
+ 'null' => NULL_NOT_ALLOWED, 'default' => 'none',
+ 'permissioncallback' => function ($user, $preferencename) {
+ global $CFG;
+ require_once($CFG->libdir.'/messagelib.php');
+ if (core_message_can_edit_message_profile($user) &&
+ preg_match('/^message_provider_([\w\d_]*)_logged(in|off)$/', $preferencename, $matches)) {
+ $providers = message_get_providers_for_user($user->id);
+ foreach ($providers as $provider) {
+ if ($matches[1] === $provider->component . '_' . $provider->name) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ 'cleancallback' => function ($value, $preferencename) {
+ if ($value === 'none' || empty($value)) {
+ return 'none';
+ }
+ $parts = explode('/,/', $value);
+ $processors = array_keys(get_message_processors());
+ array_filter($parts, function($v) use ($processors) {return in_array($v, $processors);});
+ return $parts ? join(',', $parts) : 'none';
+ });
+ return $preferences;
+}
global $CFG;
if (isset($form->email_email)) {
- $preferences['message_processor_email_email'] = $form->email_email;
+ $preferences['message_processor_email_email'] = clean_param($form->email_email, PARAM_EMAIL);
}
if (isset($form->preference_mailcharset)) {
$preferences['mailcharset'] = $form->preference_mailcharset;
+ if (!array_key_exists($preferences['mailcharset'], get_list_of_charsets())) {
+ $preferences['mailcharset'] = '0';
+ }
}
if (isset($form->mailformat) && isset($form->userid)) {
require_once($CFG->dirroot.'/user/lib.php');
} else if ($calendarform->is_submitted() && $calendarform->is_validated() && confirm_sesskey()) {
$data = $calendarform->get_data();
- // Time format.
- if ($data->timeformat != CALENDAR_TF_12 && $data->timeformat != CALENDAR_TF_24) {
- $data->timeformat = '';
- }
- set_user_preference('calendar_timeformat', $data->timeformat);
-
- // Start weekday.
- $data->startwday = intval($data->startwday);
- if ($data->startwday < 0 || $data->startwday > 6) {
- $data->startwday = abs($data->startwday % 7);
- }
- set_user_preference('calendar_startwday', $data->startwday);
-
- // Calendar events.
- if (intval($data->maxevents) >= 1) {
- set_user_preference('calendar_maxevents', $data->maxevents);
- }
-
- // Calendar lookahead.
- if (intval($data->lookahead) >= 1) {
- set_user_preference('calendar_lookahead', $data->lookahead);
- }
-
- set_user_preference('calendar_persistflt', intval($data->persistflt));
+ $usernew = ['id' => $USER->id,
+ 'preference_calendar_timeformat' => $data->timeformat,
+ 'preference_calendar_startwday' => $data->startwday,
+ 'preference_calendar_maxevents' => $data->maxevents,
+ 'preference_calendar_lookahead' => $data->lookahead,
+ 'preference_calendar_persistflt' => $data->persistflt
+ ];
+ useredit_update_user_preference($usernew);
// Calendar type.
$calendartype = $data->calendartype;
if ($courseform->is_cancelled()) {
redirect($redirect);
} else if ($data = $courseform->get_data()) {
- set_user_preference('usemodchooser', $data->enableactivitychooser, $user);
+ useredit_update_user_preference(['id' => $user->id,
+ 'preference_usemodchooser' => $data->enableactivitychooser]);
redirect($redirect);
}
// Other users require a confirmation email.
if (isset($usernew->email) and $user->email != $usernew->email && !has_capability('moodle/user:update', $systemcontext)) {
$a = new stdClass();
- $a->newemail = $usernew->preference_newemail = $usernew->email;
- $usernew->preference_newemailkey = random_string(20);
- $usernew->preference_newemailattemptsleft = 3;
+ $emailchangedkey = random_string(20);
+ set_user_preference('newemail', $usernew->email, $user->id);
+ set_user_preference('newemailkey', $emailchangedkey, $user->id);
+ set_user_preference('newemailattemptsleft', 3, $user->id);
+
+ $a->newemail = $emailchanged = $usernew->email;
$a->oldemail = $usernew->email = $user->email;
$emailchangedhtml = $OUTPUT->box(get_string('auth_changingemailaddress', 'auth', $a), 'generalbox', 'notice');
$emailchangedhtml .= $OUTPUT->continue_button($returnurl);
- $emailchanged = true;
}
}
\core\event\user_updated::create_from_userid($user->id)->trigger();
// If email was changed and confirmation is required, send confirmation email now to the new address.
- if ($emailchanged && $CFG->emailchangeconfirmation) {
+ if ($emailchanged !== false && $CFG->emailchangeconfirmation) {
$tempuser = $DB->get_record('user', array('id' => $user->id), '*', MUST_EXIST);
- $tempuser->email = $usernew->preference_newemail;
+ $tempuser->email = $emailchanged;
$supportuser = core_user::get_support_user();
$a = new stdClass();
- $a->url = $CFG->wwwroot . '/user/emailupdate.php?key=' . $usernew->preference_newemailkey . '&id=' . $user->id;
+ $a->url = $CFG->wwwroot . '/user/emailupdate.php?key=' . $emailchangedkey . '&id=' . $user->id;
$a->site = format_string($SITE->fullname, true, array('context' => context_course::instance(SITEID)));
$a->fullname = fullname($tempuser, true);
$a->supportemail = $supportuser->email;
}
/**
- * Updates the user preferences for teh given user.
+ * Updates the user preferences for the given user
*
- * @param stdClass|array $usernew
+ * Only preference that can be updated directly will be updated here. This method is called from various WS
+ * updating users and should be used when updating user details. Plugins may whitelist preferences that can
+ * be updated by defining 'user_preferences' callback, {@see core_user::fill_preferences_cache()}
+ *
+ * Some parts of code may use user preference table to store internal data, in these cases it is acceptable
+ * to call set_user_preference()
+ *
+ * @param stdClass|array $usernew object or array that has user preferences as attributes with keys starting with preference_
*/
function useredit_update_user_preference($usernew) {
+ global $USER;
$ua = (array)$usernew;
+ if (is_object($usernew) && isset($usernew->id) && isset($usernew->deleted) && isset($usernew->confirmed)) {
+ // This is already a full user object, maybe not completely full but these fields are enough.
+ $user = $usernew;
+ } else if (empty($ua['id']) || $ua['id'] == $USER->id) {
+ // We are updating current user.
+ $user = $USER;
+ } else {
+ // Retrieve user object.
+ $user = core_user::get_user($ua['id'], '*', MUST_EXIST);
+ }
+
foreach ($ua as $key => $value) {
if (strpos($key, 'preference_') === 0) {
$name = substr($key, strlen('preference_'));
- set_user_preference($name, $value, $usernew->id);
+ if (core_user::can_edit_preference($name, $user)) {
+ $value = core_user::clean_preference($value, $name);
+ set_user_preference($name, $value, $user->id);
+ }
}
}
}
$user->preference_htmleditor = $data->preference_htmleditor;
- useredit_update_user_preference($user, false, false);
+ useredit_update_user_preference($user);
// Trigger event.
\core\event\user_updated::create_from_userid($user->id)->trigger();
// Preferences.
if (!empty($user['preferences'])) {
+ $userpref = (object)$user;
foreach ($user['preferences'] as $preference) {
- set_user_preference($preference['type'], $preference['value'], $user['id']);
+ $userpref->{'preference_'.$preference['type']} = $preference['value'];
}
+ useredit_update_user_preference($userpref);
}
$userids[] = array('id' => $user['id'], 'username' => $user['username']);
global $USER, $CFG;
require_once($CFG->dirroot . '/user/lib.php');
+ require_once($CFG->dirroot . '/user/editlib.php');
+ require_once($CFG->dirroot . '/message/lib.php');
if (empty($userid)) {
$userid = $USER->id;
);
self::validate_parameters(self::update_user_preferences_parameters(), $params);
- if ($userid == $USER->id) {
- require_capability('moodle/user:editownmessageprofile', $systemcontext);
- } else {
- $personalcontext = context_user::instance($userid);
- require_capability('moodle/user:editmessageprofile', $personalcontext);
- // No editing of guest user account.
- if (isguestuser($userid)) {
- print_error('guestnoeditmessageother', 'message');
- }
- // No editing of admins by non-admins.
- if (is_siteadmin($userid) and !is_siteadmin($USER)) {
- print_error('useradmineditadmin');
- }
- }
-
// Preferences.
if (!empty($preferences)) {
+ $userpref = ['id' => $userid];
foreach ($preferences as $preference) {
- set_user_preference($preference['type'], $preference['value'], $userid);
+ $userpref['preference_' . $preference['type']] = $preference['value'];
}
+ useredit_update_user_preference($userpref);
}
// Check if they want to update the email.
if ($emailstop !== null) {
- $user = new stdClass();
- $user->id = $userid;
- $user->emailstop = $emailstop;
- user_update_user($user);
-
- // Update the $USER if we should.
- if ($userid == $USER->id) {
- $USER->emailstop = $emailstop;
+ $otheruser = ($userid == $USER->id) ? $USER : core_user::get_user($userid, '*', MUST_EXIST);
+ core_user::require_active_user($otheruser);
+ if (core_message_can_edit_message_profile($otheruser) && $otheruser->emailstop != $emailstop) {
+ $user = new stdClass();
+ $user->id = $userid;
+ $user->emailstop = $emailstop;
+ user_update_user($user);
+
+ // Update the $USER if we should.
+ if ($userid == $USER->id) {
+ $USER->emailstop = $emailstop;
+ }
}
}
global $CFG, $DB, $USER;
require_once($CFG->dirroot."/user/lib.php");
require_once($CFG->dirroot."/user/profile/lib.php"); // Required for customfields related function.
+ require_once($CFG->dirroot.'/user/editlib.php');
// Ensure the current user is allowed to run this function.
$context = context_system::instance();
// Preferences.
if (!empty($user['preferences'])) {
+ $userpref = clone($existinguser);
foreach ($user['preferences'] as $preference) {
- set_user_preference($preference['type'], $preference['value'], $user['id']);
+ $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']);
$context = context_system::instance();
self::validate_context($context);
- require_capability('moodle/site:config', $context);
$userscache = array();
foreach ($params['preferences'] as $pref) {
}
try {
- set_user_preference($pref['name'], $pref['value'], $user);
- $saved[] = array(
- 'name' => $pref['name'],
- 'userid' => $user->id,
- );
+ if (core_user::can_edit_preference($pref['name'], $user)) {
+ $value = core_user::clean_preference($pref['value'], $pref['name']);
+ set_user_preference($pref['name'], $value, $user->id);
+ $saved[] = array(
+ 'name' => $pref['name'],
+ 'userid' => $user->id,
+ );
+ } else {
+ $warnings[] = array(
+ 'item' => 'user',
+ 'itemid' => $user->id,
+ 'warningcode' => 'nopermission',
+ 'message' => 'You are not allowed to change the preference '.s($pref['name']).' for user '.$user->id
+ );
+ }
} catch (Exception $e) {
$warnings[] = array(
'item' => 'user',
'email' => 'usertest1@example.com',
'description' => 'This is a description for user 1',
'city' => 'Perth',
- 'country' => 'AU'
+ 'country' => 'AU',
+ 'preferences' => [[
+ 'type' => 'htmleditor',
+ 'value' => 'atto'
+ ], [
+ 'type' => 'invalidpreference',
+ 'value' => 'abcd'
+ ]]
);
$context = context_system::instance();
$roleid = $this->assignUserCapability('moodle/user:create', $context->id);
+ $this->assignUserCapability('moodle/user:editprofile', $context->id, $roleid);
// Call the external function.
$createdusers = core_user_external::create_users(array($user1));
$this->assertEquals($dbuser->description, $user1['description']);
$this->assertEquals($dbuser->city, $user1['city']);
$this->assertEquals($dbuser->country, $user1['country']);
+ $this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
+ $this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
}
// Call without required capability
'description' => 'This is a description for user 1',
'city' => 'Perth',
'userpicture' => $draftid,
- 'country' => 'AU'
+ 'country' => 'AU',
+ 'preferences' => [[
+ 'type' => 'htmleditor',
+ 'value' => 'atto'
+ ], [
+ 'type' => 'invialidpreference',
+ 'value' => 'abcd'
+ ]]
);
$context = context_system::instance();
$roleid = $this->assignUserCapability('moodle/user:update', $context->id);
+ $this->assignUserCapability('moodle/user:editprofile', $context->id, $roleid);
// Check we can't update deleted users, guest users, site admin.
$user2 = $user3 = $user4 = $user1;
$this->assertEquals($dbuser->city, $user1['city']);
$this->assertEquals($dbuser->country, $user1['country']);
$this->assertNotEquals(0, $dbuser->picture, 'Picture must be set to the new icon itemid for this user');
+ $this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
+ $this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
// Confirm no picture change when parameter is not supplied.
unset($user1['userpicture']);
$this->setAdminUser();
$preferences = array(
array(
- 'name' => 'some_random_pref',
- 'value' => 'abc',
+ 'name' => 'htmleditor',
+ 'value' => 'atto',
'userid' => $user1->id,
),
array(
- 'name' => 'some_random_pref',
- 'value' => 'def',
+ 'name' => 'htmleditor',
+ 'value' => 'tinymce',
'userid' => $user2->id,
)
);
$this->assertCount(2, $result['saved']);
// Get preference from DB to avoid cache.
- $this->assertEquals('abc', $DB->get_field('user_preferences', 'value',
- array('userid' => $user1->id, 'name' => 'some_random_pref')));
- $this->assertEquals('def', $DB->get_field('user_preferences', 'value',
- array('userid' => $user2->id, 'name' => 'some_random_pref')));
+ $this->assertEquals('atto', $DB->get_field('user_preferences', 'value',
+ array('userid' => $user1->id, 'name' => 'htmleditor')));
+ $this->assertEquals('tinymce', $DB->get_field('user_preferences', 'value',
+ array('userid' => $user2->id, 'name' => 'htmleditor')));
+ }
+
+ /**
+ * Test set_user_preferences
+ */
+ public function test_set_user_preferences_save_invalid_pref() {
+ global $DB;
+ $this->resetAfterTest(true);
+
+ $user1 = self::getDataGenerator()->create_user();
+
+ // Save users preferences.
+ $this->setAdminUser();
+ $preferences = array(
+ array(
+ 'name' => 'some_random_pref',
+ 'value' => 'abc',
+ 'userid' => $user1->id,
+ ),
+ );
+
+ $result = core_user_external::set_user_preferences($preferences);
+ $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
+ $this->assertCount(1, $result['warnings']);
+ $this->assertCount(0, $result['saved']);
+ $this->assertEquals('nopermission', $result['warnings'][0]['warningcode']);
+
+ // Nothing was written to DB.
+ $this->assertEmpty($DB->count_records('user_preferences', array('name' => 'some_random_pref')));
}
/**
* Test set_user_preferences using an invalid preference
*/
public function test_set_user_preferences_invalid_preference() {
- global $USER;
+ global $USER, $DB;
$this->resetAfterTest(true);
// Create a very long value.
$result = core_user_external::set_user_preferences($preferences);
$result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
- $this->assertCount(1, $result['warnings']);
- $this->assertCount(0, $result['saved']);
- $this->assertEquals('errorsavingpreference', $result['warnings'][0]['warningcode']);
+ $this->assertCount(0, $result['warnings']);
+ $this->assertCount(1, $result['saved']);
+ // Cleaned valud of the preference was saved.
+ $this->assertEquals(1, $DB->get_field('user_preferences', 'value',
+ array('userid' => $USER->id, 'name' => 'calendar_maxevents')));
}
/**
* Test set_user_preferences for other user not being admin
*/
- public function test_set_user_preferences_other_user_not_being_admin() {
+ public function test_set_user_preferences_capability() {
$this->resetAfterTest(true);
$user1 = self::getDataGenerator()->create_user();
)
);
- $this->expectException('required_capability_exception');
$result = core_user_external::set_user_preferences($preferences);
+
+ $this->assertCount(1, $result['warnings']);
+ $this->assertCount(0, $result['saved']);
+ $this->assertEquals('nopermission', $result['warnings'][0]['warningcode']);
+ $this->assertEquals($user2->id, $result['warnings'][0]['itemid']);
}
/**