diff --git a/lang/en/message.php b/lang/en/message.php index ec137096654..9fa7272618c 100644 --- a/lang/en/message.php +++ b/lang/en/message.php @@ -31,6 +31,7 @@ $string['ajax_gui'] = 'Ajax chat room'; $string['allmine'] = 'All messages to me or from me'; $string['allstudents'] = 'All messages between students in course'; $string['allusers'] = 'All messages from all users'; +$string['alwayssend'] = 'Always send me'; $string['backupmessageshelp'] = 'If enabled, then instant messages will be included in SITE automated backups'; $string['beepnewmessage'] = 'Beep when popup notification is displayed'; $string['blockcontact'] = 'Block contact'; @@ -96,6 +97,8 @@ $string['messagehistory'] = 'Message history'; $string['messagehistoryfull'] = 'All messages'; $string['messagenavigation'] = 'Message navigation:'; $string['messagetosend'] = 'Message to send'; +$string['messagepreferences'] = 'Message preferences'; +$string['messageprocessors'] = 'Message processors'; $string['messages'] = 'Messages'; $string['messagesent'] = 'Message sent'; $string['messaging'] = 'Messaging'; diff --git a/lib/db/services.php b/lib/db/services.php index 85abb75509f..3c815ec84ba 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -740,6 +740,14 @@ $functions = array( 'type' => 'write', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + 'core_message_message_processor_config_form' => array( + 'classname' => 'core_message_external', + 'methodname' => 'message_processor_config_form', + 'classpath' => 'message/externallib.php', + 'description' => 'Process the message processor config form', + 'type' => 'write', + 'ajax' => true, + ), 'core_message_search_contacts' => array( 'classname' => 'core_message_external', 'methodname' => 'search_contacts', @@ -968,6 +976,15 @@ $functions = array( 'type' => 'write', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + 'core_user_update_user' => array( + 'classname' => 'core_user_external', + 'methodname' => 'update_user', + 'classpath' => 'user/externallib.php', + 'description' => 'Update logged in user', + 'type' => 'write', + 'capabilities' => 'moodle/user:update', + 'ajax' => true, + ), 'core_user_update_users' => array( 'classname' => 'core_user_external', 'methodname' => 'update_users', @@ -975,6 +992,7 @@ $functions = array( 'description' => 'Update users.', 'type' => 'write', 'capabilities' => 'moodle/user:update', + 'ajax' => true, ), 'core_user_view_user_list' => array( 'classname' => 'core_user_external', diff --git a/message/amd/src/preferences_general_settings_controller.js b/message/amd/src/preferences_general_settings_controller.js new file mode 100644 index 00000000000..212c6aaadca --- /dev/null +++ b/message/amd/src/preferences_general_settings_controller.js @@ -0,0 +1,221 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Controls the general settings on the message preferences page + * + * @module message/preferences_general_settings_controller + * @class preferences_processors_controller + * @package message + * @copyright 2016 Ryan Wyllie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 3.2 + */ +define(['jquery', 'core/ajax', 'core/notification'], function($, ajax, notification) { + var SELECTORS = { + SETTING: '[data-preference-key]', + }; + + /** + * Constructor for the UserPreference. + * + * @param element jQuery object root element of the processor + * @param int the current user id + * @return object UserPreference + */ + var UserPreference = function(element, userId) { + this.root = $(element); + this.userId = userId; + }; + + /** + * Check if the preference is checked (enabled). + * + * @return bool + */ + UserPreference.prototype.isChecked = function() { + return this.root.find('input').prop('checked'); + }; + + /** + * Get the unique key that identifies this user preference. + * + * @method getPreferenceKey + * @return string + */ + UserPreference.prototype.getPreferenceKey = function() { + return this.root.attr('data-preference-key'); + }; + + /** + * Flag the preference as loading. + * + * @method startLoading + */ + UserPreference.prototype.startLoading = function() { + this.root.addClass('loading'); + this.root.find('input').prop('disabled', true); + }; + + /** + * Remove the loading flag for this preference. + * + * @method stopLoading + */ + UserPreference.prototype.stopLoading = function() { + this.root.removeClass('loading'); + this.root.find('input').prop('disabled', false); + }; + + /** + * Check if the preference is loading. + * + * @method isLoading + */ + UserPreference.prototype.isLoading = function() { + return this.root.hasClass('loading'); + }; + + /** + * Generate the request arguments for the save function. + * + * @method getRequestArguments + * @return object + */ + UserPreference.prototype.getRequestArguments = function() { + return { + user: { + preferences: [{ + type: this.getPreferenceKey(), + value: this.isChecked() ? 1 : 0, + }], + } + }; + }; + + /** + * Persist the user preference in the server. + * + * @method save + * @return promise + */ + UserPreference.prototype.save = function() { + if (this.isLoading()) { + return $.Deferred(); + } + + this.startLoading(); + + var request = { + methodname: 'core_user_update_user', + args: this.getRequestArguments(), + }; + + return ajax.call([request])[0] + .fail(notification.exception) + .always(function() { this.stopLoading(); }.bind(this)); + }; + + /** + * Constructor for the DisableAlPreference. This is a special type + * of UserPreference. + * + * Subclasses UserPreference. + * + * @param element jQuery object root element of the processor + * @param int the current user id + * @return object DisableAllPreference + */ + var DisableAllPreference = function(element, userId) { + UserPreference.call(this, element, userId); + }; + + /** + * Clone the UserPreference prototype. + */ + DisableAllPreference.prototype = Object.create(UserPreference.prototype); + + /** + * Return the request arguments for the save function. + * + * Override UserPreference.prototype.getRequestArguments + * + * @method getRequestArguments + * @return object + */ + DisableAllPreference.prototype.getRequestArguments = function() { + return { + user: { + emailstop: this.isChecked() ? 1 : 0, + }, + }; + }; + + /** + * Persist the preference and fire relevant events after the + * successfully saving. + * + * Override UserPreference.prototype.save + * + * @method save + * @return promise + */ + DisableAllPreference.prototype.save = function() { + return UserPreference.prototype.save.call(this).done(function() { + if (this.isChecked()) { + $(document).trigger('messageprefs:disableall'); + } else { + $(document).trigger('messageprefs:enableall'); + } + }.bind(this)); + }; + + /** + * Constructor for the GeneralSettingsController. + * + * @param element jQuery object root element of the processor + * @return object GeneralSettingsController + */ + var GeneralSettingsController = function(element) { + this.root = $(element); + this.userId = this.root.attr('data-user-id'); + + this.root.on('change', function(e) { + var element = $(e.target).closest(SELECTORS.SETTING); + var setting = this.createFromElement(element); + setting.save(); + }.bind(this)); + }; + + /** + * Factory method to return the correct UserPreference instance + * for the given jQuery element. + * + * @method save + * @param object jQuery element + * @return object UserPreference + */ + GeneralSettingsController.prototype.createFromElement = function(element) { + element = $(element); + + if (element.attr('data-preference-key') === "disableall") { + return new DisableAllPreference(element, this.userId); + } else { + return new UserPreference(element, this.userId); + } + }; + + return GeneralSettingsController; +}); diff --git a/message/amd/src/preferences_notifications_list_controller.js b/message/amd/src/preferences_notifications_list_controller.js new file mode 100644 index 00000000000..006a906c254 --- /dev/null +++ b/message/amd/src/preferences_notifications_list_controller.js @@ -0,0 +1,303 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Controls the preferences page + * + * @module message/preferences_notifications_list_controller + * @class preferences_notifications_list_controller + * @package message + * @copyright 2016 Ryan Wyllie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 3.2 + */ +define(['jquery', 'core/ajax', 'core/notification'], function($, ajax, notification) { + var SELECTORS = { + PREFERENCE_ROW: '.preference-row', + PROCESSOR: '[data-processor-name]', + STATE_NONE: '[data-state="none"]', + STATE_BOTH: '[data-state="both"]', + STATE_LOGGED_IN: '[data-state="loggedin"]', + STATE_LOGGED_OFF: '[data-state="loggedoff"]', + STATE_INPUTS: '[data-state] input', + }; + + /** + * Constructor for the Processor. + * + * @param element jQuery object root element of the processor + * @return object Processor + */ + var Processor = function(element) { + this.root = $(element); + }; + + /** + * Get the processor name. + * + * @method getName + * @return string + */ + Processor.prototype.getName = function() { + return this.root.attr('data-processor-name'); + }; + + /** + * Check if the processor is enabled when the user is logged in. + * + * @method isLoggedInEnabled + * @return bool + */ + Processor.prototype.isLoggedInEnabled = function() { + var none = this.root.find(SELECTORS.STATE_NONE).find('input'); + + if (none.prop('checked')) { + return false; + } + + var both = this.root.find(SELECTORS.STATE_BOTH).find('input'); + var loggedIn = this.root.find(SELECTORS.STATE_LOGGED_IN).find('input'); + + return loggedIn.prop('checked') || both.prop('checked'); + }; + + /** + * Check if the processor is enabled when the user is logged out. + * + * @method isLoggedOffEnabled + * @return bool + */ + Processor.prototype.isLoggedOffEnabled = function() { + var none = this.root.find(SELECTORS.STATE_NONE).find('input'); + + if (none.prop('checked')) { + return false; + } + + var both = this.root.find(SELECTORS.STATE_BOTH).find('input'); + var loggedOff = this.root.find(SELECTORS.STATE_LOGGED_OFF).find('input'); + + return loggedOff.prop('checked') || both.prop('checked'); + }; + + /** + * Constructor for the Preference. + * + * @param element jQuery object root element of the preference + * @param int the current user id + * @return object Preference + */ + var Preference = function(element, userId) { + this.root = $(element); + this.userId = userId; + }; + + /** + * Get the unique prefix key that identifies this user preference. + * + * @method getPreferenceKey + * @return string + */ + Preference.prototype.getPreferenceKey = function() { + return this.root.attr('data-preference-key'); + }; + + /** + * Get the unique key for the logged in preference. + * + * @method getLoggedInPreferenceKey + * @return string + */ + Preference.prototype.getLoggedInPreferenceKey = function() { + return this.getPreferenceKey() + '_loggedin'; + }; + + /** + * Get the unique key for the logged off preference. + * + * @method getLoggedOffPreferenceKey + * @return string + */ + Preference.prototype.getLoggedOffPreferenceKey = function() { + return this.getPreferenceKey() + '_loggedoff'; + }; + + /** + * Get the list of Processors available for this preference. + * + * @method getProcessors + * @return array + */ + Preference.prototype.getProcessors = function() { + return this.root.find(SELECTORS.PROCESSOR).map(function(index, element) { + return new Processor($(element)); + }); + }; + + /** + * Flag the preference as loading. + * + * @method startLoading + */ + Preference.prototype.startLoading = function() { + this.root.addClass('loading'); + this.root.find(SELECTORS.STATE_INPUTS).prop('disabled', true); + }; + + /** + * Remove the loading flag for this preference. + * + * @method stopLoading + */ + Preference.prototype.stopLoading = function() { + this.root.removeClass('loading'); + this.root.find(SELECTORS.STATE_INPUTS).prop('disabled', false); + }; + + /** + * Check if the preference is loading. + * + * @method isLoading + */ + Preference.prototype.isLoading = function() { + return this.root.hasClass('loading'); + }; + + /** + * Persist the current state of the processors for this preference. + * + * @method save + * @return promise + */ + Preference.prototype.save = function() { + if (this.isLoading()) { + return $.Deferred(); + } + + this.startLoading(); + + var loggedInValue = ''; + var loggedOffValue = ''; + + this.getProcessors().each(function(index, processor) { + if (processor.isLoggedInEnabled()) { + if (loggedInValue === '') { + loggedInValue = processor.getName(); + } else { + loggedInValue += ',' + processor.getName(); + } + } + + if (processor.isLoggedOffEnabled()) { + if (loggedOffValue === '') { + loggedOffValue = processor.getName(); + } else { + loggedOffValue += ',' + processor.getName(); + } + } + }); + + if (loggedInValue === '') { + loggedInValue = 'none'; + } + + if (loggedOffValue === '') { + loggedOffValue = 'none'; + } + + var args = { + user: { + preferences: [ + { + type: this.getLoggedInPreferenceKey(), + value: loggedInValue, + }, + { + type: this.getLoggedOffPreferenceKey(), + value: loggedOffValue, + }, + ], + } + }; + + var request = { + methodname: 'core_user_update_user', + args: args, + }; + + return ajax.call([request])[0] + .fail(notification.exception) + .always(function() { this.stopLoading(); }.bind(this)); + }; + + /** + * Constructor for the PreferencesController. + * + * @param element jQuery object root element of the preference + * @return object PreferencesController + */ + var PreferencesController = function(element) { + this.root = $(element); + this.userId = this.root.attr('data-user-id'); + + this.root.on('change', function(e) { + if (!this.isDisabled()) { + var preferenceRow = $(e.target).closest(SELECTORS.PREFERENCE_ROW); + var preference = new Preference(preferenceRow, this.userId); + preference.save(); + } + }.bind(this)); + + $(document).on('messageprefs:disableall', function() { + this.setDisabled(); + }.bind(this)); + + $(document).on('messageprefs:enableall', function() { + this.setEnabled(); + }.bind(this)); + }; + + /** + * Check if the preferences are all disabled. + * + * @method isDisabled + * @return bool + */ + PreferencesController.prototype.isDisabled = function() { + return this.root.hasClass('disabled'); + }; + + /** + * Disable all of the preferences. + * + * @method setDisabled + */ + PreferencesController.prototype.setDisabled = function() { + this.root.addClass('disabled'); + this.root.find(SELECTORS.STATE_INPUTS).prop('disabled', true); + }; + + /** + * Enable all of the preferences. + * + * @method setEnabled + */ + PreferencesController.prototype.setEnabled = function() { + this.root.removeClass('disabled'); + this.root.find(SELECTORS.STATE_INPUTS).prop('disabled', false); + }; + + return PreferencesController; +}); diff --git a/message/amd/src/preferences_processors_controller.js b/message/amd/src/preferences_processors_controller.js new file mode 100644 index 00000000000..1594523cd34 --- /dev/null +++ b/message/amd/src/preferences_processors_controller.js @@ -0,0 +1,118 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Controls the processors page + * + * @module message/preferences_processors_controller + * @class preferences_processors_controller + * @package message + * @copyright 2016 Ryan Wyllie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 3.2 + */ +define(['jquery', 'core/ajax', 'core/notification'], function($, ajax, notification) { + var SELECTORS = { + PROCESSOR: '[data-processor-name]', + }; + + /** + * Constructor for the Processor. + * + * @param element jQuery object root element of the preference + * @param int the current user id + * @return object Processor + */ + var Processor = function(element, userId) { + this.root = $(element); + this.userId = userId; + this.name = this.root.attr('data-processor-name'); + }; + + /** + * Flag the processor as loading. + * + * @method startLoading + */ + Processor.prototype.startLoading = function() { + this.root.addClass('loading'); + }; + + /** + * Remove the loading flag for this processor. + * + * @method stopLoading + */ + Processor.prototype.stopLoading = function() { + this.root.removeClass('loading'); + }; + + /** + * Check if this processor is loading. + * + * @method isLoading + * @return bool + */ + Processor.prototype.isLoading = function() { + return this.root.hasClass('loading'); + }; + + /** + * Persist the processor configuration. + * + * @method save + * @return promise + */ + Processor.prototype.save = function() { + if (this.isLoading()) { + return $.Deferred(); + } + + this.startLoading(); + + var data = this.root.find('form').serializeArray(); + var request = { + methodname: 'core_message_message_processor_config_form', + args: { + userid: this.userId, + name: this.name, + formvalues: data, + } + }; + + return ajax.call([request])[0] + .fail(notification.exception) + .always(function() { this.stopLoading(); }.bind(this)); + }; + + /** + * Constructor for the ProcessorsController. + * + * @param element jQuery object root element of the preference + * @return object ProcessorsController + */ + var ProcessorsController = function(element) { + this.root = $(element); + this.userId = this.root.attr('data-user-id'); + + this.root.on('change', function(e) { + var element = $(e.target).closest(SELECTORS.PROCESSOR); + var processor = new Processor(element, this.userId); + processor.save(); + }.bind(this)); + }; + + return ProcessorsController; +}); diff --git a/message/edit.php b/message/edit.php index 51597e3381e..80903d56104 100644 --- a/message/edit.php +++ b/message/edit.php @@ -27,7 +27,6 @@ require_once($CFG->dirroot . '/message/lib.php'); require_once($CFG->dirroot . '/user/lib.php'); $userid = optional_param('id', 0, PARAM_INT); // User id. -$disableall = optional_param('disableall', 0, PARAM_BOOL); //disable all of this user's notifications if (!$userid) { $userid = $USER->id; @@ -54,7 +53,6 @@ $personalcontext = context_user::instance($user->id); $PAGE->set_context($personalcontext); $PAGE->set_pagelayout('admin'); -$PAGE->requires->js_init_call('M.core_message.init_editsettings'); // check access control if ($user->id == $USER->id) { @@ -75,102 +73,6 @@ if ($user->id == $USER->id) { $PAGE->navigation->extend_for_user($user); } -// Fetch message providers -$providers = message_get_providers_for_user($user->id); - -/// Save new preferences if data was submitted - -if (($form = data_submitted()) && confirm_sesskey()) { - $preferences = array(); - - //only update the user's "emailstop" if its actually changed - if ( $user->emailstop != $disableall ) { - $user->emailstop = $disableall; - $DB->set_field('user', 'emailstop', $user->emailstop, array("id"=>$user->id)); - } - - // Turning on emailstop disables the preference checkboxes in the browser. - // Disabled checkboxes may not be submitted with the form making them look (incorrectly) like they've been unchecked. - // Only alter the messaging preferences if emailstop is turned off - if (!$user->emailstop) { - foreach ($providers as $provider) { - $componentproviderbase = $provider->component.'_'.$provider->name; - foreach (array('loggedin', 'loggedoff') as $state) { - $linepref = ''; - $componentproviderstate = $componentproviderbase.'_'.$state; - if (array_key_exists($componentproviderstate, $form)) { - foreach (array_keys($form->{$componentproviderstate}) as $process) { - if ($linepref == ''){ - $linepref = $process; - } else { - $linepref .= ','.$process; - } - } - } - if (empty($linepref)) { - $linepref = 'none'; - } - $preferences['message_provider_'.$provider->component.'_'.$provider->name.'_'.$state] = $linepref; - } - } - } - -/// Set all the processor options as well - $processors = get_message_processors(true); - foreach ($processors as $processor) { - $processor->object->process_form($form, $preferences); - } - - //process general messaging preferences - $preferences['message_blocknoncontacts'] = !empty($form->blocknoncontacts)?1:0; - $preferences['message_beepnewmessage'] = !empty($form->beepnewmessage)?1:0; - - // Save all the new preferences to the database - if (!set_user_preferences($preferences, $user->id)) { - print_error('cannotupdateusermsgpref'); - } - - if (isset($form->mailformat)) { - $user->mailformat = clean_param($form->mailformat, PARAM_INT); - } - user_update_user($user, false, false); - - $redirect = new moodle_url("/user/preferences.php", array('userid' => $userid)); - redirect($redirect); -} - -/// Load preferences -$preferences = new stdClass(); -$preferences->userdefaultemail = $user->email;//may be displayed by the email processor - -/// Get providers preferences -foreach ($providers as $provider) { - foreach (array('loggedin', 'loggedoff') as $state) { - $linepref = get_user_preferences('message_provider_'.$provider->component.'_'.$provider->name.'_'.$state, '', $user->id); - if ($linepref == ''){ - continue; - } - $lineprefarray = explode(',', $linepref); - $preferences->{$provider->component.'_'.$provider->name.'_'.$state} = array(); - foreach ($lineprefarray as $pref) { - $preferences->{$provider->component.'_'.$provider->name.'_'.$state}[$pref] = 1; - } - } -} - -// Load all processors -$processors = get_message_processors(); -/// For every processors put its options on the form (need to get function from processor's lib.php) -foreach ($processors as $processor) { - $processor->object->load_data($preferences, $user->id); -} - -//load general messaging preferences -$preferences->blocknoncontacts = get_user_preferences( 'message_blocknoncontacts', '', $user->id); -$preferences->beepnewmessage = get_user_preferences( 'message_beepnewmessage', '', $user->id); -$preferences->mailformat = $user->mailformat; -$preferences->mailcharset = get_user_preferences( 'mailcharset', '', $user->id); - /// Display page header $strmessaging = get_string('messaging', 'message'); $PAGE->set_title($strmessaging); @@ -178,11 +80,7 @@ $PAGE->set_heading(fullname($user)); // Grab the renderer $renderer = $PAGE->get_renderer('core', 'message'); -// Fetch default (site) preferences -$defaultpreferences = get_message_output_default_preferences(); - -$messagingoptions = $renderer->manage_messagingoptions($processors, $providers, $preferences, $defaultpreferences, - $user->emailstop, $user->id); +$messagingoptions = $renderer->render_user_preferences($user); echo $OUTPUT->header(); echo $messagingoptions; diff --git a/message/externallib.php b/message/externallib.php index edf09953ac0..f1d471694ac 100644 --- a/message/externallib.php +++ b/message/externallib.php @@ -1900,4 +1900,80 @@ class core_message_external extends external_api { ); } + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since 3.2 + */ + public static function message_processor_config_form_parameters() { + return new external_function_parameters( + array( + 'userid' => new external_value(PARAM_INT, 'id of the user, 0 for current user', VALUE_REQUIRED), + 'name' => new external_value(PARAM_TEXT, 'The name of the message processor'), + 'formvalues' => new external_multiple_structure( + new external_single_structure( + array( + 'name' => new external_value(PARAM_TEXT, 'name of the form element', VALUE_REQUIRED), + 'value' => new external_value(PARAM_RAW, 'value of the form element', VALUE_REQUIRED), + ) + ), + 'Config form values', + VALUE_REQUIRED + ), + ) + ); + } + + /** + * Processes a message processor config form. + * + * @param int $userid the user id + * @param string $name the name of the processor + * @param array $formvalues the form values + * @return external_description + * @throws moodle_exception + * @since 3.2 + */ + public static function message_processor_config_form($userid, $name, $formvalues) { + $params = self::validate_parameters( + self::message_processor_config_form_parameters(), + array( + 'userid' => $userid, + 'name' => $name, + 'formvalues' => $formvalues, + ) + ); + + if (empty($params['userid'])) { + $params['userid'] = $USER->id; + } + + $user = core_user::get_user($params['userid'], '*', MUST_EXIST); + core_user::require_active_user($user); + + $processor = get_message_processor($name); + $preferences = []; + $form = new stdClass(); + + foreach ($formvalues as $formvalue) { + $form->$formvalue['name'] = $formvalue['value']; + } + + $processor->process_form($form, $preferences); + + if (!empty($preferences)) { + set_user_preferences($preferences, $userid); + } + } + + /** + * Returns description of method result value + * + * @return external_description + * @since 3.2 + */ + public static function message_processor_config_form_returns() { + return null; + } } diff --git a/message/output/email/message_output_email.php b/message/output/email/message_output_email.php index 2f8195e109c..196e353ba79 100644 --- a/message/output/email/message_output_email.php +++ b/message/output/email/message_output_email.php @@ -116,6 +116,7 @@ class message_output_email extends message_output { $current = $preferences->mailformat; $string .= $OUTPUT->container(html_writer::label(get_string('emailformat'), 'mailformat')); $string .= $OUTPUT->container(html_writer::select($choices, 'mailformat', $current, false, array('id' => 'mailformat'))); + $string .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'userid', 'value' => $USER->id)); if (!empty($CFG->allowusermailcharset)) { $choices = array(); @@ -160,12 +161,21 @@ class message_output_email extends message_output { * @param array $preferences preferences array */ function process_form($form, &$preferences){ + global $CFG; + if (isset($form->email_email)) { $preferences['message_processor_email_email'] = $form->email_email; } if (isset($form->preference_mailcharset)) { $preferences['mailcharset'] = $form->preference_mailcharset; } + if (isset($form->mailformat) && isset($form->userid)) { + require_once($CFG->dirroot.'/user/lib.php'); + + $user = core_user::get_user($form->userid, '*', MUST_EXIST); + $user->mailformat = clean_param($form->mailformat, PARAM_INT); + user_update_user($user, false, false); + } } /** diff --git a/message/renderer.php b/message/renderer.php index 558287c28de..0b55aa216e1 100644 --- a/message/renderer.php +++ b/message/renderer.php @@ -216,186 +216,299 @@ class core_message_renderer extends plugin_renderer_base { } /** - * Display the interface for messaging options + * Get the base key prefix for the given provider. * - * @param array $processors Array of objects containing message processors - * @param array $providers Array of objects containing message providers - * @param array $preferences Array of objects containing current preferences - * @param array $defaultpreferences Array of objects containing site default preferences - * @param bool $notificationsdisabled Indicate if the user's "emailstop" flag is set (shouldn't receive any non-forced notifications) - * @param null|int $userid User id, or null if current user. - * @return string The text to render + * @param stdClass message provider + * @return string */ - public function manage_messagingoptions($processors, $providers, $preferences, $defaultpreferences, - $notificationsdisabled = false, $userid = null) { - global $USER; - if (empty($userid)) { - $userid = $USER->id; + private function get_preference_base($provider) { + return $provider->component.'_'.$provider->name; + } + + /** + * Get the display name for the given provider. + * + * @param stdClass $provider message provider + * @return string + */ + private function get_provider_display_name($provider) { + return get_string('messageprovider:'.$provider->name, $provider->component); + } + + /** + * Get the preferences for the given user. + * + * @param array $processors list of message processors + * @param array $providers list of message providers + * @param stdClass $user user + * @return stdClass + */ + private function get_all_preferences($processors, $providers, $user) { + $preferences = new stdClass(); + $preferences->userdefaultemail = $user->email;//may be displayed by the email processor + + /// Get providers preferences + foreach ($providers as $provider) { + foreach (array('loggedin', 'loggedoff') as $state) { + $linepref = get_user_preferences('message_provider_'.$provider->component.'_'.$provider->name.'_'.$state, '', $user->id); + if ($linepref == ''){ + continue; + } + $lineprefarray = explode(',', $linepref); + $preferences->{$provider->component.'_'.$provider->name.'_'.$state} = array(); + foreach ($lineprefarray as $pref) { + $preferences->{$provider->component.'_'.$provider->name.'_'.$state}[$pref] = 1; + } + } } - // Filter out enabled, available system_configured and user_configured processors only. - $readyprocessors = array_filter($processors, create_function('$a', 'return $a->enabled && $a->configured && $a->object->is_user_configured();')); - // Start the form. We're not using mform here because of our special formatting needs ... - $output = html_writer::start_tag('form', array('method'=>'post', 'class' => 'mform')); - $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey())); + /// For every processors put its options on the form (need to get function from processor's lib.php) + foreach ($processors as $processor) { + $processor->object->load_data($preferences, $user->id); + } - /// Settings table... - $output .= html_writer::start_tag('fieldset', array('id' => 'providers', 'class' => 'clearfix')); - $output .= html_writer::nonempty_tag('legend', get_string('providers_config', 'message'), array('class' => 'ftoggler')); + //load general messaging preferences + $preferences->blocknoncontacts = get_user_preferences( 'message_blocknoncontacts', '', $user->id); + $preferences->beepnewmessage = get_user_preferences( 'message_beepnewmessage', '', $user->id); + $preferences->mailformat = $user->mailformat; + $preferences->mailcharset = get_user_preferences( 'mailcharset', '', $user->id); + return $preferences; + } + + /** + * Check if the given preference is enabled or not. + * + * @param string $name preference name + * @param stdClass $processor the processors for the preference + * @param stdClass $preferences the preferences config + * @return bool + */ + private function is_preference_enabled($name, $processor, $preferences) { + $defaultpreferences = get_message_output_default_preferences(); + + $checked = false; + // See if user has touched this preference + if (isset($preferences->{$name})) { + // User have some preferneces for this state in the database, use them + $checked = isset($preferences->{$name}[$processor->name]); + } else { + // User has not set this preference yet, using site default preferences set by admin + $defaultpreference = 'message_provider_'.$name; + if (isset($defaultpreferences->{$defaultpreference})) { + $checked = (int)in_array($processor->name, explode(',', $defaultpreferences->{$defaultpreference})); + } + } + + return $checked; + } + + /** + * Build the template context for the given processor. + * + * @param stdClass $processor + * @param stdClass $provider + * @param stdClass $preferences the preferences config + * @return array + */ + private function get_processor_context($processor, $provider, $preferences) { + $processorcontext = [ + 'displayname' => get_string('pluginname', 'message_'.$processor->name), + 'name' => $processor->name, + 'locked' => false, + 'radioname' => strtolower(str_replace(" ", "-", $processor->name)), + 'states' => [] + ]; + // determine the default setting + $preferencebase = $this->get_preference_base($provider); + $permitted = MESSAGE_DEFAULT_PERMITTED; + $defaultpreferences = get_message_output_default_preferences(); + $defaultpreference = $processor->name.'_provider_'.$preferencebase.'_permitted'; + if (isset($defaultpreferences->{$defaultpreference})) { + $permitted = $defaultpreferences->{$defaultpreference}; + } + // If settings are disallowed or forced, just display the + // corresponding message, if not use user settings. + if ($permitted == 'disallowed') { + $processorcontext['locked'] = true; + $processorcontext['lockedmessage'] = get_string('disallowed', 'message'); + } else if ($permitted == 'forced') { + $processorcontext['locked'] = true; + $processorcontext['lockedmessage'] = get_string('forced', 'message'); + } else { + $statescontext = [ + 'loggedin' => [ + 'name' => 'loggedin', + 'displayname' => get_string('loggedindescription', 'message'), + 'checked' => $this->is_preference_enabled($preferencebase.'_loggedin', $processor, $preferences), + 'iconurl' => $this->pix_url('i/completion-auto-y')->out(), + ], + 'loggedoff' => [ + 'name' => 'loggedoff', + 'displayname' => get_string('loggedoffdescription', 'message'), + 'checked' => $this->is_preference_enabled($preferencebase.'_loggedoff', $processor, $preferences), + 'iconurl' => $this->pix_url('i/completion-auto-n')->out(), + ], + 'both' => [ + 'name' => 'both', + 'displayname' => get_string('always'), + 'checked' => false, + 'iconurl' => $this->pix_url('i/completion-auto-pass')->out(), + ], + 'none' => [ + 'name' => 'none', + 'displayname' => get_string('never'), + 'checked' => false, + 'iconurl' => $this->pix_url('i/completion-auto-fail')->out(), + ], + ]; + + if ($statescontext['loggedin']['checked'] && $statescontext['loggedoff']['checked']) { + $statescontext['both']['checked'] = true; + $statescontext['loggedin']['checked'] = false; + $statescontext['loggedoff']['checked'] = false; + } else if (!$statescontext['loggedin']['checked'] && !$statescontext['loggedoff']['checked']) { + $statescontext['none']['checked'] = true; + } + + $processorcontext['states'] = array_values($statescontext); + } + + return $processorcontext; + } + + /** + * Build the template context for the given component. + * + * @param string $component the component name + * @param stdClass $processors an array of processors + * @param stdClass $providers and array of providers + * @param stdClass $preferences the preferences config + * @return array + */ + private function get_component_context($component, $processors, $providers, $preferences) { + $defaultpreferences = get_message_output_default_preferences(); + + if ($component != 'moodle') { + $componentname = get_string('pluginname', $component); + } else { + $componentname = get_string('coresystem'); + } + $componentcontext = [ + 'displayname' => $componentname, + 'processornames' => [], + 'notifications' => [], + ]; + + foreach ($processors as $processor) { + $componentcontext['processornames'][] = get_string('pluginname', 'message_'.$processor->name); + } + + foreach ($providers as $provider) { + $preferencebase = $this->get_preference_base($provider); + // If provider component is not same or provider disabled then don't show. + if (($provider->component != $component) || + (!empty($defaultpreferences->{$preferencebase.'_disable'}))) { + continue; + } + + $notificationcontext = [ + 'displayname' => $this->get_provider_display_name($provider), + 'preferencekey' => 'message_provider_'.$preferencebase, + 'processors' => [], + ]; + + foreach ($processors as $processor) { + $notificationcontext['processors'][] = $this->get_processor_context($processor, $provider, $preferences); + } + + $componentcontext['notifications'][] = $notificationcontext; + } + + return $componentcontext; + } + + /** + * Build the template context for the message preferences page. + * + * @param stdClass $processors an array of processors + * @param stdClass $providers and array of providers + * @param stdClass $preferences the preferences config + * @param stdClass $user the current user + * @return array + */ + private function get_preferences_context($processors, $providers, $preferences, $user) { foreach($providers as $provider) { if($provider->component != 'moodle') { $components[] = $provider->component; } } + // Lets arrange by components so that core settings (moodle) appear as the first table. $components = array_unique($components); asort($components); array_unshift($components, 'moodle'); // pop it in front! phew! asort($providers); - $numprocs = count($processors); - // Display the messaging options table(s) + $context = []; + foreach ($components as $component) { - $provideradded = false; - $table = new html_table(); - $table->attributes['class'] = 'generaltable'; - $table->data = array(); - if ($component != 'moodle') { - $componentname = get_string('pluginname', $component); - } else { - $componentname = get_string('coresystem'); - } - $table->head = array($componentname); - foreach ($readyprocessors as $processor) { - $table->head[] = get_string('pluginname', 'message_'.$processor->name); - } - // Populate the table with rows - foreach ($providers as $provider) { - $preferencebase = $provider->component.'_'.$provider->name; - // If provider component is not same or provider disabled then don't show. - if (($provider->component != $component) || - (!empty($defaultpreferences->{$preferencebase.'_disable'}))) { - continue; - } - $provideradded = true; - $headerrow = new html_table_row(); - $providername = get_string('messageprovider:'.$provider->name, $provider->component); - $providercell = new html_table_cell($providername); - $providercell->header = true; - $providercell->colspan = $numprocs; - $providercell->attributes['class'] = 'c0'; - $headerrow->cells = array($providercell); - $table->data[] = $headerrow; - - foreach (array('loggedin', 'loggedoff') as $state) { - $optionrow = new html_table_row(); - $optionname = new html_table_cell(get_string($state.'description', 'message')); - $optionname->attributes['class'] = 'c0'; - $optionrow->cells = array($optionname); - foreach ($readyprocessors as $processor) { - // determine the default setting - $permitted = MESSAGE_DEFAULT_PERMITTED; - $defaultpreference = $processor->name.'_provider_'.$preferencebase.'_permitted'; - if (isset($defaultpreferences->{$defaultpreference})) { - $permitted = $defaultpreferences->{$defaultpreference}; - } - // If settings are disallowed or forced, just display the - // corresponding message, if not use user settings. - if (in_array($permitted, array('disallowed', 'forced'))) { - if ($state == 'loggedoff') { - // skip if we are rendering the second line - continue; - } - $cellcontent = html_writer::nonempty_tag('div', get_string($permitted, 'message'), array('class' => 'dimmed_text')); - $optioncell = new html_table_cell($cellcontent); - $optioncell->rowspan = 2; - $optioncell->attributes['class'] = 'disallowed'; - } else { - // determine user preferences and use them. - $disabled = array(); - $checked = false; - if ($notificationsdisabled) { - $disabled['disabled'] = 1; - } - // See if user has touched this preference - if (isset($preferences->{$preferencebase.'_'.$state})) { - // User have some preferneces for this state in the database, use them - $checked = isset($preferences->{$preferencebase.'_'.$state}[$processor->name]); - } else { - // User has not set this preference yet, using site default preferences set by admin - $defaultpreference = 'message_provider_'.$preferencebase.'_'.$state; - if (isset($defaultpreferences->{$defaultpreference})) { - $checked = (int)in_array($processor->name, explode(',', $defaultpreferences->{$defaultpreference})); - } - } - $elementname = $preferencebase.'_'.$state.'['.$processor->name.']'; - // prepare language bits - $processorname = get_string('pluginname', 'message_'.$processor->name); - $statename = get_string($state, 'message'); - $labelparams = array( - 'provider' => $providername, - 'processor' => $processorname, - 'state' => $statename - ); - $label = get_string('sendingviawhen', 'message', $labelparams); - $cellcontent = html_writer::label($label, $elementname, true, array('class' => 'accesshide')); - $cellcontent .= html_writer::checkbox($elementname, 1, $checked, '', array_merge(array('id' => $elementname, 'class' => 'notificationpreference'), $disabled)); - $optioncell = new html_table_cell($cellcontent); - $optioncell->attributes['class'] = 'mdl-align'; - } - $optionrow->cells[] = $optioncell; - } - $table->data[] = $optionrow; - } - } - // Add settings only if provider added for component. - if ($provideradded) { - $output .= html_writer::start_tag('div', array('class' => 'messagesettingcomponent')); - $output .= html_writer::table($table); - $output .= html_writer::end_tag('div'); - } + $context['components'][] = $this->get_component_context($component, $processors, $providers, $preferences); } - $output .= html_writer::end_tag('fieldset'); + $context['userid'] = $user->id; + $context['disableall'] = $user->emailstop; - foreach ($processors as $processor) { - if (($processorconfigform = $processor->object->config_form($preferences)) && $processor->enabled) { - $output .= html_writer::start_tag('fieldset', array('id' => 'messageprocessor_'.$processor->name, 'class' => 'clearfix')); - $output .= html_writer::nonempty_tag('legend', get_string('pluginname', 'message_'.$processor->name), array('class' => 'ftoggler')); - $output .= html_writer::start_tag('div'); - $output .= $processorconfigform; - $output .= html_writer::end_tag('div'); - $output .= html_writer::end_tag('fieldset'); - } - } - - $output .= html_writer::start_tag('fieldset', array('id' => 'messageprocessor_general', 'class' => 'clearfix')); - $output .= html_writer::nonempty_tag('legend', get_string('generalsettings','admin'), array('class' => 'ftoggler')); - - $output .= html_writer::start_tag('div'); - $output .= html_writer::checkbox('beepnewmessage', 1, $preferences->beepnewmessage, get_string('beepnewmessage', 'message')); - $output .= html_writer::end_tag('div'); - - $output .= html_writer::start_tag('div'); - $output .= html_writer::checkbox('blocknoncontacts', 1, $preferences->blocknoncontacts, get_string('blocknoncontacts', 'message')); - $output .= html_writer::end_tag('div'); - - $disableallcheckbox = html_writer::checkbox('disableall', 1, $notificationsdisabled, get_string('disableall', 'message'), array('class'=>'disableallcheckbox')); - $disableallcheckbox .= $this->output->help_icon('disableall', 'message'); - $output .= html_writer::nonempty_tag('div', $disableallcheckbox, array('class'=>'disableall')); - - $redirect = new moodle_url("/user/preferences.php", array('userid' => $userid)); - $output .= html_writer::end_tag('fieldset'); - $output .= html_writer::start_tag('div', array('class' => 'mdl-align')); - $output .= html_writer::empty_tag('input', array('type' => 'submit', - 'value' => get_string('savechanges'), 'class' => 'form-submit')); - $output .= html_writer::link($redirect, html_writer::empty_tag('input', array('type' => 'button', - 'value' => get_string('cancel'), 'class' => 'btn-cancel'))); - $output .= html_writer::end_tag('div'); - - $output .= html_writer::end_tag('form'); - return $output; + return $context; } + /** + * Display the interface for messaging options + * + * @param object $user instance of a user + * @return string The text to render + */ + public function render_user_preferences($user) { + // Filter out enabled, available system_configured and user_configured processors only. + $readyprocessors = array_filter(get_message_processors(), create_function('$a', 'return $a->enabled && $a->configured && $a->object->is_user_configured();')); + + $providers = message_get_providers_for_user($user->id); + $preferences = $this->get_all_preferences($readyprocessors, $providers, $user); + $preferencescontext = $this->get_preferences_context($readyprocessors, $providers, $preferences, $user); + + $output = $this->render_from_template('message/preferences_notifications_list', $preferencescontext); + + $processorscontext = [ + 'userid' => $user->id, + 'processors' => [], + ]; + + foreach ($readyprocessors as $processor) { + $formhtml = $processor->object->config_form($preferences); + + if (!$formhtml) { + continue; + } + + $processorscontext['processors'][] = [ + 'displayname' => get_string('pluginname', 'message_'.$processor->name), + 'name' => $processor->name, + 'formhtml' => $formhtml, + ]; + } + + $output .= $this->render_from_template('message/preferences_processors', $processorscontext); + + $generalsettingscontext = [ + 'userid' => $user->id, + 'beepnewmessage' => $preferences->beepnewmessage, + 'blocknoncontacts' => $preferences->blocknoncontacts, + 'disableall' => $user->emailstop, + 'disableallhelpicon' => $this->output->help_icon('disableall', 'message'), + ]; + + $output .= $this->render_from_template('message/preferences_general_settings', $generalsettingscontext); + + return $output; + } } diff --git a/message/templates/preferences_general_settings.mustache b/message/templates/preferences_general_settings.mustache new file mode 100644 index 00000000000..48b1f08c9bb --- /dev/null +++ b/message/templates/preferences_general_settings.mustache @@ -0,0 +1,59 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_message/preferences_general_settings + + The message preferences general settings + + Classes required for JS: + * None + + Data attibutes required for JS: + * None + + Context variables required for this template: + * None + + Example context (json): + { } +}} +

{{#str}} generalsettings, admin {{/str}}

+
+ +
+ +
+ +
+{{#js}} +require(['jquery', 'message/preferences_general_settings_controller'], function($, controller) { + new controller($('.general-settings-container')); +}); +{{/js}} diff --git a/message/templates/preferences_notifications_list.mustache b/message/templates/preferences_notifications_list.mustache new file mode 100644 index 00000000000..931524b45d7 --- /dev/null +++ b/message/templates/preferences_notifications_list.mustache @@ -0,0 +1,96 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_message/preferences_notifications_list + + The list of notifications for the message preferences page + + Classes required for JS: + * None + + Data attibutes required for JS: + * None + + Context variables required for this template: + * None + + Example context (json): + { } +}} +

{{#str}} messagepreferences, message {{/str}}

+
+ {{#components}} + + + + + {{#processornames}} + + {{/processornames}} + + + + {{#notifications}} + + + {{#processors}} + + {{/processors}} + + {{/notifications}} + +
{{displayname}}{{.}}
+ {{displayname}} +
{{> message/loading }}
+
+ {{#locked}} +
{{lockedmessage}}
+ {{/locked}} + {{^locked}} +
{{#str}} disabled, question {{/str}}
+
+ {{#states}} + + {{/states}} +
+ {{/locked}} +
+ {{/components}} +
+{{#js}} +require(['jquery', 'theme_bootstrapbase/bootstrap', 'message/preferences_notifications_list_controller'], + function($, bootstrap, controller) { + + $('[data-toggle="tooltip"]').tooltip(); + new controller($('.preferences-container')); +}); +{{/js}} diff --git a/message/templates/preferences_processors.mustache b/message/templates/preferences_processors.mustache new file mode 100644 index 00000000000..491379f0629 --- /dev/null +++ b/message/templates/preferences_processors.mustache @@ -0,0 +1,53 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_message/preferences_processors + + The message processor configuration block for the preferences page + + Classes required for JS: + * None + + Data attibutes required for JS: + * None + + Context variables required for this template: + * None + + Example context (json): + { } +}} +

{{#str}} messageprocessors, message {{/str}}

+
+ {{#processors}} +
+
+
+ {{> message/loading }} +
+

{{displayname}}

+
+ {{{formhtml}}} +
+
+ {{/processors}} +
+{{#js}} +require(['jquery', 'message/preferences_processors_controller'], function($, controller) { + new controller($('.processors-container')); +}); +{{/js}} diff --git a/theme/bootstrapbase/less/moodle/message.less b/theme/bootstrapbase/less/moodle/message.less index 81761d9172e..e3755901e24 100644 --- a/theme/bootstrapbase/less/moodle/message.less +++ b/theme/bootstrapbase/less/moodle/message.less @@ -268,3 +268,149 @@ bottom: 0; } } + +.dir-rtl { + .core_message-messenger-sendmessage { + .message-send { + float: left; + } + } + .preferences-container { + .preference-table { + .preference-row { + .preference-name { + .loading-icon { + float: left; + } + } + } + } + } +} + +.preferences-container { + .preference-table { + border: 1px solid #ddd; + + tr { + td { + &:not(:first-child) { + width: 150px; + text-align: center; + } + &:nth-child(even) { + background-color: #f7f7f7; + } + } + th:nth-child(even) { + background-color: #f7f7f7; + } + } + .preference-row { + .preference-name { + vertical-align: middle; + + .loading-icon { + display: none; + float: right; + + img { + vertical-align: middle; + } + } + } + .disabled-message { + display: none; + text-align: center; + height: 30px; + line-height: 30px; + } + .preference-state { + margin: 0; + padding: 0; + display: inline-block; + vertical-align: middle; + + &:hover { + .preference-state-image-container { + background-color: #e5e5e5; + border-radius: 4px; + } + } + input[type="radio"]:checked + .preference-state-image-container { + background-color: #424242; + border-radius: 4px; + } + .preference-state-image-container { + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; + } + } + &.loading { + .preference-name { + .loading-icon { + display: block; + } + } + } + } + } + + &.disabled { + .preference-table { + .preference-row { + .disabled-message { + display: block; + } + form { + display: none; + } + } + } + } +} +.general-settings-container { + .loading-icon { + display: none; + } + .loading { + .loading-icon { + display: inline-block; + } + } + label { + display: inline-block; + } +} +.processors-container { + .processor-container { + position: relative; + + .loading-container { + display: none; + position: absolute; + width: 100%; + height: 100%; + text-align: center; + background-color: rgba(255, 255, 255, 0.5); + + .vertical-align { + height: 100%; + width: 0%; + display: inline-block; + vertical-align: middle; + } + } + + &.loading { + .loading-container { + display: block; + } + } + } +} +.title-case { + text-transform: capitalize; +} diff --git a/theme/bootstrapbase/style/moodle.css b/theme/bootstrapbase/style/moodle.css index b77cf38c25d..c9fb73ef4c3 100644 --- a/theme/bootstrapbase/style/moodle.css +++ b/theme/bootstrapbase/style/moodle.css @@ -5992,6 +5992,102 @@ a.ygtvspacer:hover { position: absolute; bottom: 0; } +.dir-rtl .core_message-messenger-sendmessage .message-send { + float: left; +} +.dir-rtl .preferences-container .preference-table .preference-row .preference-name .loading-icon { + float: left; +} +.preferences-container .preference-table { + border: 1px solid #ddd; +} +.preferences-container .preference-table tr td:not(:first-child) { + width: 150px; + text-align: center; +} +.preferences-container .preference-table tr td:nth-child(even) { + background-color: #f7f7f7; +} +.preferences-container .preference-table tr th:nth-child(even) { + background-color: #f7f7f7; +} +.preferences-container .preference-table .preference-row .preference-name { + vertical-align: middle; +} +.preferences-container .preference-table .preference-row .preference-name .loading-icon { + display: none; + float: right; +} +.preferences-container .preference-table .preference-row .preference-name .loading-icon img { + vertical-align: middle; +} +.preferences-container .preference-table .preference-row .disabled-message { + display: none; + text-align: center; + height: 30px; + line-height: 30px; +} +.preferences-container .preference-table .preference-row .preference-state { + margin: 0; + padding: 0; + display: inline-block; + vertical-align: middle; +} +.preferences-container .preference-table .preference-row .preference-state:hover .preference-state-image-container { + background-color: #e5e5e5; + border-radius: 4px; +} +.preferences-container .preference-table .preference-row .preference-state input[type="radio"]:checked + .preference-state-image-container { + background-color: #424242; + border-radius: 4px; +} +.preferences-container .preference-table .preference-row .preference-state .preference-state-image-container { + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; +} +.preferences-container .preference-table .preference-row.loading .preference-name .loading-icon { + display: block; +} +.preferences-container.disabled .preference-table .preference-row .disabled-message { + display: block; +} +.preferences-container.disabled .preference-table .preference-row form { + display: none; +} +.general-settings-container .loading-icon { + display: none; +} +.general-settings-container .loading .loading-icon { + display: inline-block; +} +.general-settings-container label { + display: inline-block; +} +.processors-container .processor-container { + position: relative; +} +.processors-container .processor-container .loading-container { + display: none; + position: absolute; + width: 100%; + height: 100%; + text-align: center; + background-color: rgba(255, 255, 255, 0.5); +} +.processors-container .processor-container .loading-container .vertical-align { + height: 100%; + width: 0%; + display: inline-block; + vertical-align: middle; +} +.processors-container .processor-container.loading .loading-container { + display: block; +} +.title-case { + text-transform: capitalize; +} /* Question */ .questionbank h2 { margin-top: 0; diff --git a/user/externallib.php b/user/externallib.php index c5de4703656..9c9431e2da9 100644 --- a/user/externallib.php +++ b/user/externallib.php @@ -22,6 +22,7 @@ * @copyright 2009 Petr Skodak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + */ require_once("$CFG->libdir/externallib.php"); @@ -322,6 +323,148 @@ class core_user_external extends external_api { return null; } + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 3.2 + */ + public static function update_user_parameters() { + return new external_function_parameters( + array( + 'user' => new external_single_structure( + array( + 'username' => + new external_value(core_user::get_property_type('username'), 'Username policy is defined in Moodle security config.', + VALUE_OPTIONAL, '', NULL_NOT_ALLOWED), + 'password' => + new external_value(core_user::get_property_type('password'), 'Plain text password consisting of any characters', VALUE_OPTIONAL, + '', NULL_NOT_ALLOWED), + 'firstname' => + new external_value(core_user::get_property_type('firstname'), 'The first name(s) of the user', VALUE_OPTIONAL, '', + NULL_NOT_ALLOWED), + 'lastname' => + new external_value(core_user::get_property_type('lastname'), 'The family name of the user', VALUE_OPTIONAL), + 'email' => + new external_value(core_user::get_property_type('email'), 'A valid and unique email address', VALUE_OPTIONAL, '', + NULL_NOT_ALLOWED), + 'emailstop' => + new external_value(core_user::get_property_type('emailstop'), 'Enable or disable notifications for this user', VALUE_OPTIONAL, '', + NULL_NOT_ALLOWED), + 'auth' => + new external_value(core_user::get_property_type('auth'), 'Auth plugins include manual, ldap, imap, etc', VALUE_OPTIONAL, '', + NULL_NOT_ALLOWED), + 'idnumber' => + new external_value(core_user::get_property_type('idnumber'), 'An arbitrary ID code number perhaps from the institution', + VALUE_OPTIONAL), + 'lang' => + new external_value(core_user::get_property_type('lang'), 'Language code such as "en", must exist on server', + VALUE_OPTIONAL, '', NULL_NOT_ALLOWED), + 'calendartype' => + new external_value(core_user::get_property_type('calendartype'), 'Calendar type such as "gregorian", must exist on server', + VALUE_OPTIONAL, '', NULL_NOT_ALLOWED), + 'theme' => + new external_value(core_user::get_property_type('theme'), 'Theme name such as "standard", must exist on server', + VALUE_OPTIONAL), + 'timezone' => + new external_value(core_user::get_property_type('timezone'), 'Timezone code such as Australia/Perth, or 99 for default', + VALUE_OPTIONAL), + 'mailformat' => + new external_value(core_user::get_property_type('mailformat'), 'Mail format code is 0 for plain text, 1 for HTML etc', + VALUE_OPTIONAL), + 'description' => + new external_value(core_user::get_property_type('description'), 'User profile description, no HTML', VALUE_OPTIONAL), + 'city' => + new external_value(core_user::get_property_type('city'), 'Home city of the user', VALUE_OPTIONAL), + 'country' => + new external_value(core_user::get_property_type('country'), 'Home country code of the user, such as AU or CZ', VALUE_OPTIONAL), + 'firstnamephonetic' => + new external_value(core_user::get_property_type('firstnamephonetic'), 'The first name(s) phonetically of the user', VALUE_OPTIONAL), + 'lastnamephonetic' => + new external_value(core_user::get_property_type('lastnamephonetic'), 'The family name phonetically of the user', VALUE_OPTIONAL), + 'middlename' => + new external_value(core_user::get_property_type('middlename'), 'The middle name of the user', VALUE_OPTIONAL), + 'alternatename' => + new external_value(core_user::get_property_type('alternatename'), 'The alternate name of the user', VALUE_OPTIONAL), + 'customfields' => new external_multiple_structure( + new external_single_structure( + array( + 'type' => new external_value(PARAM_ALPHANUMEXT, 'The name of the custom field'), + 'value' => new external_value(PARAM_RAW, 'The value of the custom field') + ) + ), 'User custom fields (also known as user profil fields)', VALUE_OPTIONAL), + 'preferences' => new external_multiple_structure( + new external_single_structure( + array( + 'type' => new external_value(PARAM_ALPHANUMEXT, 'The name of the preference'), + 'value' => new external_value(PARAM_RAW, 'The value of the preference') + ) + ), 'User preferences', VALUE_OPTIONAL), + ) + ) + ) + ); + } + + /** + * Update the currently logged in user + * + * @param array $users + * @return null + * @since Moodle 3.2 + */ + public static function update_user($user) { + + global $USER, $CFG, $DB; + require_once($CFG->dirroot."/user/lib.php"); + require_once($CFG->dirroot."/user/profile/lib.php"); // Required for customfields related function. + + $params = self::validate_parameters( + self::update_user_parameters(), + array('user' => $user) + ); + + $user = $params['user']; + $user['id'] = $USER->id; + + $transaction = $DB->start_delegated_transaction(); + + user_update_user($user, true, false); + // 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. + \core\event\user_updated::create_from_userid($user['id'])->trigger(); + + // Preferences. + if (!empty($user['preferences'])) { + foreach ($user['preferences'] as $preference) { + set_user_preference($preference['type'], $preference['value'], $user['id']); + } + } + + $transaction->allow_commit(); + + return null; + } + + /** + * Returns description of method result value + * + * @return null + * @since Moodle 3.2 + */ + public static function update_user_returns() { + return null; + } /** * Returns description of method parameters @@ -351,6 +494,9 @@ class core_user_external extends external_api { 'email' => new external_value(core_user::get_property_type('email'), 'A valid and unique email address', VALUE_OPTIONAL, '', NULL_NOT_ALLOWED), + 'emailstop' => + new external_value(core_user::get_property_type('emailstop'), 'Enable or disable notifications for this user', VALUE_OPTIONAL, '', + NULL_NOT_ALLOWED), 'auth' => new external_value(core_user::get_property_type('auth'), 'Auth plugins include manual, ldap, imap, etc', VALUE_OPTIONAL, '', NULL_NOT_ALLOWED),