diff --git a/config-dist.php b/config-dist.php index f2437cb63d1..9b9e371358b 100644 --- a/config-dist.php +++ b/config-dist.php @@ -589,6 +589,11 @@ $CFG->admin = 'admin'; // // $CFG->pdfexportfont = 'freesans'; // +// Use the following flag to enable messagingallusers and set the default preference +// value for existing users to allow them to be contacted by other site users. +// +// $CFG->keepmessagingallusersenabled = true; +// //========================================================================= // 7. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!! //========================================================================= diff --git a/lang/en/deprecated.txt b/lang/en/deprecated.txt index 873ad79c53c..c1939efae55 100644 --- a/lang/en/deprecated.txt +++ b/lang/en/deprecated.txt @@ -137,3 +137,5 @@ previewhtml,core messagedselecteduserfailed,core eventmessagecontactblocked,core_message eventmessagecontactunblocked,core_message +userisblockingyou,core_message +userisblockingyounoncontact,core_message \ No newline at end of file diff --git a/lang/en/message.php b/lang/en/message.php index 41fb26ea3d1..562723b398c 100644 --- a/lang/en/message.php +++ b/lang/en/message.php @@ -31,6 +31,10 @@ $string['blockcontact'] = 'Block contact'; $string['blockedusers'] = 'Blocked users'; $string['blocknoncontacts'] = 'Prevent non-contacts from messaging me'; $string['canceledit'] = 'Cancel editing messages'; +$string['contactableprivacy'] = 'Accept messages from:'; +$string['contactableprivacy_onlycontacts'] = 'My contacts only'; +$string['contactableprivacy_coursemember'] = 'My contacts and anyone in my courses'; +$string['contactableprivacy_site'] = 'Anyone on the site'; $string['contactblocked'] = 'Contact blocked'; $string['contactrequests'] = 'Contact requests'; $string['contacts'] = 'Contacts'; @@ -181,8 +185,7 @@ $string['unblockcontact'] = 'Unblock contact'; $string['unknownuser'] = 'Unknown user'; $string['unreadnotification'] = 'Unread notification: {$a}'; $string['unreadnewmessage'] = 'New message from {$a}'; -$string['userisblockingyou'] = 'This user has blocked you from sending messages to them'; -$string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.'; +$string['usercantbemessaged'] = 'You can\'t message {$a} due to their message preferences. Try adding them as a contact.'; $string['viewfullnotification'] = 'View full notification'; $string['viewinganotherusersmessagearea'] = 'You are viewing another user\'s message area.'; $string['viewmessageswith'] = 'View messages with {$a}'; @@ -195,3 +198,5 @@ $string['you'] = 'You:'; $string['eventmessagecontactblocked'] = 'Message contact blocked'; $string['eventmessagecontactunblocked'] = 'Message contact unblocked'; $string['messagingdisabled'] = 'Messaging is disabled on this site, emails will be sent instead'; +$string['userisblockingyou'] = 'This user has blocked you from sending messages to them'; +$string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.'; \ No newline at end of file diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index a6087d3da1d..cd3dc8ef36a 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2337,22 +2337,6 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2018091200.00); } - if ($oldversion < 2018091400.01) { - if (!isset($CFG->messagingallusers)) { - // For existing instances, $CFG->messagingallusers would be same value $CFG->messaging has. - if (isset($CFG->messaging)) { - set_config('messagingallusers', $CFG->messaging); - } else { - // When $CFG->messaging is not set, default value for $CFG->messaging should be true, - // so $CFG->messagingallusers value should be true as well. - set_config('messagingallusers', 1); - } - } - - // Main savepoint reached. - upgrade_main_savepoint(true, 2018091400.01); - } - if ($oldversion < 2018091700.01) { // Remove unused setting. unset_config('messaginghidereadnotifications'); @@ -2524,5 +2508,30 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2018092800.03); } + if ($oldversion < 2018101700.01) { + if (empty($CFG->keepmessagingallusersenabled)) { + // When it is not set, $CFG->messagingallusers should be disabled by default. + // When $CFG->messagingallusers = false, the default user preference is MESSAGE_PRIVACY_COURSEMEMBER + // (contacted by users sharing a course). + set_config('messagingallusers', false); + } else { + // When $CFG->keepmessagingallusersenabled is set to true, $CFG->messagingallusers is set to true. + set_config('messagingallusers', true); + + // When $CFG->messagingallusers = true, the default user preference is MESSAGE_PRIVACY_SITE + // (contacted by all users site). So we need to set existing values from 0 (MESSAGE_PRIVACY_COURSEMEMBER) + // to 2 (MESSAGE_PRIVACY_SITE). + $DB->set_field( + 'user_preferences', + 'value', + \core_message\api::MESSAGE_PRIVACY_SITE, + array('name' => 'message_blocknoncontacts', 'value' => 0) + ); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2018101700.01); + } + return true; } diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 9d26a5d1aef..bdb18ab06f9 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -148,6 +148,17 @@ the groupid field. 'itemid' => 2 ]; These files will be then copied to the gradebook file area. +* Allow users to choose who can message them for privacy reasons, with a 'growing circle of contactability': + - Added $CFG->messagingallusers, for enabling messaging to all site users. Default value: 0. + When $CFG->messagingallusers = false users can choose being contacted by only contacts or contacts and users sharing a course with them. + In that case, the default user preference is MESSAGE_PRIVACY_COURSEMEMBER (users sharing a course). + When $CFG->messagingallusers = true users have a new option for the privacy messaging preferences: "Anyone on the site". In that case, + the default user preference is MESSAGE_PRIVACY_SITE (all site users). + - Added $CFG->keepmessagingallusersenabled setting to config.php to force enabling $CFG->messagingallusers during the upgrading process. + Default value: 0. + When $CFG->keepmessagingallusersenabled is set to true, $CFG->messagingallusers will be also set to true to enable messaging site users. + However, when it is empty, $CFG->messagingallusers will be disabled during the upgrading process, so the users will only be able to + message contacts and users sharing a course with them. === 3.5 === diff --git a/message/amd/build/message_preferences.min.js b/message/amd/build/message_preferences.min.js index 436b3bbb185..54bf9178e48 100644 --- a/message/amd/build/message_preferences.min.js +++ b/message/amd/build/message_preferences.min.js @@ -1 +1 @@ -define(["jquery","core/ajax","core/notification","core_message/message_notification_preference","core/custom_interaction_events"],function(a,b,c,d,e){var f={PREFERENCE:"[data-state]",PREFERENCES_CONTAINER:'[data-region="preferences-container"]',BLOCK_NON_CONTACTS:'[data-region="block-non-contacts-container"] [data-block-non-contacts]',BLOCK_NON_CONTACTS_CONTAINER:'[data-region="block-non-contacts-container"]'},g=function(b){this.root=a(b),this.userId=this.root.find(f.PREFERENCES_CONTAINER).attr("data-user-id"),this.registerEventListeners()};return g.prototype.preferencesDisabled=function(){return this.root.find(f.PREFERENCES_CONTAINER).hasClass("disabled")},g.prototype.saveBlockNonContactsStatus=function(){var d=this.root.find(f.BLOCK_NON_CONTACTS),e=this.root.find(f.BLOCK_NON_CONTACTS_CONTAINER),g=d.prop("checked");if(e.hasClass("loading"))return a.Deferred().resolve();e.addClass("loading");var h={methodname:"core_user_update_user_preferences",args:{userid:this.userId,preferences:[{type:d.attr("data-preference-key"),value:g?1:0}]}};return b.call([h])[0].fail(c.exception).always(function(){e.removeClass("loading")})},g.prototype.registerEventListeners=function(){e.define(this.root,[e.events.activate]),this.root.on(e.events.activate,f.BLOCK_NON_CONTACTS,function(){this.saveBlockNonContactsStatus()}.bind(this)),this.root.on("change",function(b){if(!this.preferencesDisabled()){var c=a(b.target).closest(f.PREFERENCES_CONTAINER),e=a(b.target).closest(f.PREFERENCE),g=new d(c,this.userId);e.addClass("loading"),g.save().always(function(){e.removeClass("loading")})}}.bind(this))},g}); \ No newline at end of file +define(["jquery","core/ajax","core/notification","core_message/message_notification_preference","core/custom_interaction_events"],function(a,b,c,d,e){var f={PREFERENCE:"[data-state]",PREFERENCES_CONTAINER:'[data-region="preferences-container"]',CONTACTABLE_PRIVACY_CONTAINER:'[data-region="privacy-setting-container"]'},g=function(b){this.root=a(b),this.userId=this.root.find(f.PREFERENCES_CONTAINER).attr("data-user-id"),this.registerEventListeners()};return g.prototype.preferencesDisabled=function(){return this.root.find(f.PREFERENCES_CONTAINER).hasClass("disabled")},g.prototype.saveContactablePrivacySetting=function(){var d=this.root.find(f.CONTACTABLE_PRIVACY_CONTAINER),e=a("input[type='radio']:checked").val();if(d.hasClass("loading"))return a.Deferred().resolve();d.addClass("loading");var g={methodname:"core_user_update_user_preferences",args:{userid:this.userId,preferences:[{type:d.attr("data-preference-key"),value:e}]}};return b.call([g])[0].fail(c.exception).always(function(){d.removeClass("loading")})},g.prototype.registerEventListeners=function(){e.define(this.root,[e.events.activate]),this.root.on("change",function(b){if("message_blocknoncontacts"==b.target.name)this.saveContactablePrivacySetting();else if(!this.preferencesDisabled()){var c=a(b.target).closest(f.PREFERENCES_CONTAINER),e=a(b.target).closest(f.PREFERENCE),g=new d(c,this.userId);e.addClass("loading"),g.save().always(function(){e.removeClass("loading")})}}.bind(this))},g}); \ No newline at end of file diff --git a/message/amd/src/message_preferences.js b/message/amd/src/message_preferences.js index c8a43722d4e..c5944b62ce2 100644 --- a/message/amd/src/message_preferences.js +++ b/message/amd/src/message_preferences.js @@ -29,8 +29,7 @@ define(['jquery', 'core/ajax', 'core/notification', var SELECTORS = { PREFERENCE: '[data-state]', PREFERENCES_CONTAINER: '[data-region="preferences-container"]', - BLOCK_NON_CONTACTS: '[data-region="block-non-contacts-container"] [data-block-non-contacts]', - BLOCK_NON_CONTACTS_CONTAINER: '[data-region="block-non-contacts-container"]', + CONTACTABLE_PRIVACY_CONTAINER: '[data-region="privacy-setting-container"]', }; /** @@ -56,16 +55,15 @@ define(['jquery', 'core/ajax', 'core/notification', }; /** - * Update the block messages from non-contacts user preference in the DOM and + * Update the contactable privacy user preference in the DOM and * send a request to update on the server. * * @return {Promise} - * @method saveBlockNonContactsStatus + * @method saveContactablePrivacySetting */ - MessagePreferences.prototype.saveBlockNonContactsStatus = function() { - var checkbox = this.root.find(SELECTORS.BLOCK_NON_CONTACTS); - var container = this.root.find(SELECTORS.BLOCK_NON_CONTACTS_CONTAINER); - var ischecked = checkbox.prop('checked'); + MessagePreferences.prototype.saveContactablePrivacySetting = function() { + var container = this.root.find(SELECTORS.CONTACTABLE_PRIVACY_CONTAINER); + var value = $("input[type='radio']:checked").val(); if (container.hasClass('loading')) { return $.Deferred().resolve(); @@ -79,8 +77,8 @@ define(['jquery', 'core/ajax', 'core/notification', userid: this.userId, preferences: [ { - type: checkbox.attr('data-preference-key'), - value: ischecked ? 1 : 0, + type: container.attr('data-preference-key'), + value: value, } ] } @@ -103,20 +101,22 @@ define(['jquery', 'core/ajax', 'core/notification', CustomEvents.events.activate ]); - this.root.on(CustomEvents.events.activate, SELECTORS.BLOCK_NON_CONTACTS, function() { - this.saveBlockNonContactsStatus(); - }.bind(this)); - this.root.on('change', function(e) { - if (!this.preferencesDisabled()) { - var preferencesContainer = $(e.target).closest(SELECTORS.PREFERENCES_CONTAINER); - var preferenceElement = $(e.target).closest(SELECTORS.PREFERENCE); - var messagePreference = new MessageNotificationPreference(preferencesContainer, this.userId); + // Add listener for privacy setting radio buttons change. + if (e.target.name == 'message_blocknoncontacts') { + this.saveContactablePrivacySetting(); + } else { + // Add listener for processor preferences. + if (!this.preferencesDisabled()) { + var preferencesContainer = $(e.target).closest(SELECTORS.PREFERENCES_CONTAINER); + var preferenceElement = $(e.target).closest(SELECTORS.PREFERENCE); + var messagePreference = new MessageNotificationPreference(preferencesContainer, this.userId); - preferenceElement.addClass('loading'); - messagePreference.save().always(function() { - preferenceElement.removeClass('loading'); - }); + preferenceElement.addClass('loading'); + messagePreference.save().always(function() { + preferenceElement.removeClass('loading'); + }); + } } }.bind(this)); }; diff --git a/message/classes/api.php b/message/classes/api.php index 092f47c2990..d9b507edb40 100644 --- a/message/classes/api.php +++ b/message/classes/api.php @@ -46,6 +46,21 @@ class api { */ const MESSAGE_ACTION_DELETED = 2; + /** + * The privacy setting for being messaged by anyone within courses user is member of. + */ + const MESSAGE_PRIVACY_COURSEMEMBER = 0; + + /** + * The privacy setting for being messaged only by contacts. + */ + const MESSAGE_PRIVACY_ONLYCONTACTS = 1; + + /** + * The privacy setting for being messaged by anyone on the site. + */ + const MESSAGE_PRIVACY_SITE = 2; + /** * Handles searching for messages in the message area. * @@ -882,7 +897,7 @@ class api { } // Load general messaging preferences. - $preferences->blocknoncontacts = get_user_preferences('message_blocknoncontacts', '', $user->id); + $preferences->blocknoncontacts = self::get_user_privacy_messaging_preference($user->id); $preferences->mailformat = $user->mailformat; $preferences->mailcharset = get_user_preferences('mailcharset', '', $user->id); @@ -952,6 +967,36 @@ class api { return true; } + /** + * Get the messaging preference for a user. + * If the user has not any messaging privacy preference: + * - When $CFG->messagingallusers = false the default user preference is MESSAGE_PRIVACY_COURSEMEMBER. + * - When $CFG->messagingallusers = true the default user preference is MESSAGE_PRIVACY_SITE. + * + * @param int $userid The user identifier. + * @return int The default messaging preference. + */ + public static function get_user_privacy_messaging_preference(int $userid) : int { + global $CFG; + + // When $CFG->messagingallusers is enabled, default value for the messaging preference will be "Anyone on the site"; + // otherwise, the default value will be "My contacts and anyone in my courses". + if (empty($CFG->messagingallusers)) { + $defaultprefvalue = self::MESSAGE_PRIVACY_COURSEMEMBER; + } else { + $defaultprefvalue = self::MESSAGE_PRIVACY_SITE; + } + $privacypreference = get_user_preferences('message_blocknoncontacts', $defaultprefvalue, $userid); + + // When the $CFG->messagingallusers privacy setting is disabled, MESSAGE_PRIVACY_SITE is + // also disabled, so it has to be replaced to MESSAGE_PRIVACY_COURSEMEMBER. + if (empty($CFG->messagingallusers) && $privacypreference == self::MESSAGE_PRIVACY_SITE) { + $privacypreference = self::MESSAGE_PRIVACY_COURSEMEMBER; + } + + return $privacypreference; + } + /** * Checks if the recipient is allowing messages from users that aren't a * contact. If not then it checks to make sure the sender is in the @@ -962,23 +1007,31 @@ class api { * @return bool true if $sender is blocked, false otherwise. */ public static function is_user_non_contact_blocked($recipient, $sender = null) { - global $USER; + global $USER, $CFG; if (is_null($sender)) { // The message is from the logged in user, unless otherwise specified. $sender = $USER; } - $blockednoncontacts = get_user_preferences('message_blocknoncontacts', '', $recipient->id); - if (!empty($blockednoncontacts)) { - // Confirm the sender is a contact of the recipient. - if (self::is_contact($sender->id, $recipient->id)) { - // All good, the recipient is a contact of the sender. - return false; - } else { - // Oh no, the recipient is not a contact. Looks like we can't send the message. - return true; - } + $privacypreference = self::get_user_privacy_messaging_preference($recipient->id); + switch ($privacypreference) { + case self::MESSAGE_PRIVACY_SITE: + if (!empty($CFG->messagingallusers)) { + // Users can be messaged without being contacts or members of the same course. + break; + } + // When the $CFG->messagingallusers privacy setting is disabled, continue with the next + // case, because MESSAGE_PRIVACY_SITE is replaced to MESSAGE_PRIVACY_COURSEMEMBER. + case self::MESSAGE_PRIVACY_COURSEMEMBER: + // Confirm the sender and the recipient are both members of the same course. + if (enrol_sharing_course($recipient, $sender)) { + // All good, the recipient and the sender are members of the same course. + return false; + } + case self::MESSAGE_PRIVACY_ONLYCONTACTS: + // True if they aren't contacts (they can't send a message because of the privacy settings), false otherwise. + return !self::is_contact($sender->id, $recipient->id); } return false; diff --git a/message/externallib.php b/message/externallib.php index dec1cd573a2..89016ecb451 100644 --- a/message/externallib.php +++ b/message/externallib.php @@ -92,72 +92,36 @@ class core_message_external extends external_api { } list($sqluserids, $sqlparams) = $DB->get_in_or_equal($receivers); $tousers = $DB->get_records_select("user", "id " . $sqluserids . " AND deleted = 0", $sqlparams); - $blocklist = array(); - $contactlist = array(); - $contactsqlparams = array_merge($sqlparams, [$USER->id], [$USER->id], $sqlparams); - $rs = $DB->get_recordset_sql("SELECT * - FROM {message_contacts} - WHERE (userid $sqluserids AND contactid = ?) - OR (userid = ? AND contactid $sqluserids)", $contactsqlparams); - foreach ($rs as $record) { - $useridtouse = $record->userid; - if ($record->userid == $USER->id) { - $useridtouse = $record->contactid; - } - $contactlist[$useridtouse] = true; - } - $rs->close(); - $blocksqlparams = array_merge($sqlparams, [$USER->id]); - $rs = $DB->get_recordset_sql("SELECT * - FROM {message_users_blocked} - WHERE userid $sqluserids - AND blockeduserid = ?", $blocksqlparams); - foreach ($rs as $record) { - $blocklist[$record->userid] = true; - } - $rs->close(); - - $canreadallmessages = has_capability('moodle/site:readallmessages', $context); $resultmessages = array(); foreach ($params['messages'] as $message) { $resultmsg = array(); //the infos about the success of the operation - //we are going to do some checking - //code should match /messages/index.php checks + // We are going to do some checking. + // Code should match /messages/index.php checks. $success = true; - //check the user exists + // Check the user exists. if (empty($tousers[$message['touserid']])) { $success = false; $errormessage = get_string('touserdoesntexist', 'message', $message['touserid']); } - //check that the touser is not blocking the current user - if ($success and !empty($blocklist[$message['touserid']]) and !$canreadallmessages) { + // TODO MDL-31118 performance improvement - edit the function so we can pass an array instead userid + // Check if the recipient can be messaged by the sender. + if ($success && !\core_message\api::can_post_message($tousers[$message['touserid']], $USER)) { $success = false; - $errormessage = get_string('userisblockingyou', 'message'); + $errormessage = get_string('usercantbemessaged', 'message', fullname(\core_user::get_user($message['touserid']))); } - // Check if the user is a contact - //TODO MDL-31118 performance improvement - edit the function so we can pass an array instead userid - $blocknoncontacts = get_user_preferences('message_blocknoncontacts', NULL, $message['touserid']); - // message_blocknoncontacts option is on and current user is not in contact list - if ($success && empty($contactlist[$message['touserid']]) && !empty($blocknoncontacts)) { - // The user isn't a contact and they have selected to block non contacts so this message won't be sent. - $success = false; - $errormessage = get_string('userisblockingyounoncontact', 'message', - fullname(core_user::get_user($message['touserid']))); - } - - //now we can send the message (at least try) + // Now we can send the message (at least try). if ($success) { - //TODO MDL-31118 performance improvement - edit the function so we can pass an array instead one touser object + // TODO MDL-31118 performance improvement - edit the function so we can pass an array instead one touser object. $success = message_post_message($USER, $tousers[$message['touserid']], $message['text'], external_validate_format($message['textformat'])); } - //build the resultmsg + // Build the resultmsg. if (isset($message['clientmsgid'])) { $resultmsg['clientmsgid'] = $message['clientmsgid']; } @@ -571,7 +535,6 @@ class core_message_external extends external_api { /** * Unblock contacts. * - * @deprecated since Moodle 3.6 * @param array $userids array of user IDs. * @param int $userid The id of the user we are unblocking the contacts for * @return null @@ -3154,7 +3117,7 @@ class core_message_external extends external_api { $result = array( 'warnings' => array(), 'preferences' => $notificationlistoutput->export_for_template($renderer), - 'blocknoncontacts' => get_user_preferences('message_blocknoncontacts', '', $user->id) ? true : false, + 'blocknoncontacts' => \core_message\api::get_user_privacy_messaging_preference($user->id), ); return $result; } @@ -3169,7 +3132,7 @@ class core_message_external extends external_api { return new external_function_parameters( array( 'preferences' => self::get_preferences_structure(), - 'blocknoncontacts' => new external_value(PARAM_BOOL, 'Whether to block or not messages from non contacts'), + 'blocknoncontacts' => new external_value(PARAM_INT, 'Privacy messaging setting to define who can message you'), 'warnings' => new external_warnings(), ) ); diff --git a/message/lib.php b/message/lib.php index 1fbe61cbb08..a85e65ad91c 100644 --- a/message/lib.php +++ b/message/lib.php @@ -716,10 +716,26 @@ function core_message_can_edit_message_profile($user) { * @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_blocknoncontacts'] = array( + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + 'default' => 0, + 'choices' => array( + \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, + \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER, + \core_message\api::MESSAGE_PRIVACY_SITE + ), + 'cleancallback' => function ($value) { + global $CFG; + + // When site-wide messaging between users is disabled, MESSAGE_PRIVACY_SITE should be converted. + if (empty($CFG->messagingallusers) && $value === \core_message\api::MESSAGE_PRIVACY_SITE) { + return \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER; + } + return $value; + } + ); $preferences['/^message_provider_([\w\d_]*)_logged(in|off)$/'] = array('isregex' => true, 'type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED, 'default' => 'none', 'permissioncallback' => function ($user, $preferencename) { diff --git a/message/output/popup/tests/behat/message_popover_unread.feature b/message/output/popup/tests/behat/message_popover_unread.feature index 67289a28f81..b27e22e896c 100644 --- a/message/output/popup/tests/behat/message_popover_unread.feature +++ b/message/output/popup/tests/behat/message_popover_unread.feature @@ -6,9 +6,16 @@ Feature: Message popover unread messages Background: Given the following "users" exist: - | username | firstname | lastname | email | + | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | And I log in as "student2" And I send "Test message" message to "Student 1" user And I log out diff --git a/message/renderer.php b/message/renderer.php index 1aac25975b4..17af06dc068 100644 --- a/message/renderer.php +++ b/message/renderer.php @@ -227,6 +227,8 @@ class core_message_renderer extends plugin_renderer_base { * @return string The text to render */ public function render_user_message_preferences($user) { + global $CFG; + // Filter out enabled, available system_configured and user_configured processors only. $readyprocessors = array_filter(get_message_processors(), function($processor) { return $processor->enabled && @@ -243,7 +245,29 @@ class core_message_renderer extends plugin_renderer_base { $notificationlistoutput = new \core_message\output\preferences\message_notification_list($readyprocessors, $providers, $preferences, $user); $context = $notificationlistoutput->export_for_template($this); - $context['blocknoncontacts'] = get_user_preferences('message_blocknoncontacts', '', $user->id) ? true : false; + + // Get the privacy settings options for being messaged. + $privacysetting = \core_message\api::get_user_privacy_messaging_preference($user->id); + $choices = array(); + $choices[] = [ + 'value' => \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, + 'text' => get_string('contactableprivacy_onlycontacts', 'message'), + 'checked' => ($privacysetting == \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS) + ]; + $choices[] = [ + 'value' => \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER, + 'text' => get_string('contactableprivacy_coursemember', 'message'), + 'checked' => ($privacysetting == \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER) + ]; + if (!empty($CFG->messagingallusers)) { + // Add the MESSAGE_PRIVACY_SITE option when site-wide messaging between users is enabled. + $choices[] = [ + 'value' => \core_message\api::MESSAGE_PRIVACY_SITE, + 'text' => get_string('contactableprivacy_site', 'message'), + 'checked' => ($privacysetting == \core_message\api::MESSAGE_PRIVACY_SITE) + ]; + } + $context['privacychoices'] = $choices; return $this->render_from_template('message/message_preferences', $context); } diff --git a/message/templates/message_preferences.mustache b/message/templates/message_preferences.mustache index 43e2ea9e8f5..ebe1c40840c 100644 --- a/message/templates/message_preferences.mustache +++ b/message/templates/message_preferences.mustache @@ -29,6 +29,7 @@ * userid The logged in user id * disableall If the user has disabled notifications * components The list of notification components + * privacychoices The choice options for the contactable privacy setting Example context (json): { @@ -65,21 +66,38 @@ } ] } + ], + "privacychoices": [ + { + "value": 1, + "text": "My contacts only", + "checked": 0 + }, + { + "value": 2, + "text": "Anyone within courses I am a member of", + "checked": 1 + } ] } }}

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

-
- - - {{> core/loading }} -
+
+

{{#str}} contactableprivacy, message {{/str}}

+ {{#privacychoices}} + + +
+ {{/privacychoices}} +

diff --git a/message/tests/api_test.php b/message/tests/api_test.php index f3253e6eb83..8be0f3f6070 100644 --- a/message/tests/api_test.php +++ b/message/tests/api_test.php @@ -109,6 +109,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase { // Create user to add to the admin's block list. $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); $this->assertEquals(0, \core_message\api::count_blocked_users()); @@ -1334,10 +1335,17 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); - // Set as the user 1. + // Set as the first user. $this->setUser($user1); - // They can post to someone else. + // With the default privacy setting, users can't message them. + $this->assertFalse(\core_message\api::can_post_message($user2)); + + // Enrol users to the same course. + $course = $this->getDataGenerator()->create_course(); + $this->getDataGenerator()->enrol_user($user1->id, $course->id); + $this->getDataGenerator()->enrol_user($user2->id, $course->id); + // After enrolling users to the course, they should be able to message them with the default privacy setting. $this->assertTrue(\core_message\api::can_post_message($user2)); } @@ -1363,6 +1371,27 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $this->assertFalse(\core_message\api::can_post_message($user2)); } + /** + * Tests the user can post a message when they are contact. + */ + public function test_can_post_message_when_contact() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + + // Set as the first user. + $this->setUser($user1); + + // Check that we can not send user2 a message. + $this->assertFalse(\core_message\api::can_post_message($user2)); + + // Add users as contacts. + \core_message\api::add_contact($user1->id, $user2->id); + + // Check that the return result is now true. + $this->assertTrue(\core_message\api::can_post_message($user2)); + } + /** * Tests the user can't post a message if they are not a contact and the user * has requested messages only from contacts. @@ -1376,7 +1405,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $this->setUser($user1); // Set the second user's preference to not receive messages from non-contacts. - set_user_preference('message_blocknoncontacts', 1, $user2->id); + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id); // Check that we can not send user 2 a message. $this->assertFalse(\core_message\api::can_post_message($user2)); @@ -1400,6 +1429,77 @@ class core_message_api_testcase extends core_message_messagelib_testcase { $this->assertFalse(\core_message\api::can_post_message($user1, $user2)); } + /** + * Tests the user can post a message when site-wide messaging setting is enabled, + * even if they are not a contact and are not members of the same course. + */ + public function test_can_post_message_site_messaging_setting() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + + // Set as the first user. + $this->setUser($user1); + + // Set the second user's preference to receive messages from everybody. As site-wide messaging setting + // is disabled by default, the value will be changed to MESSAGE_PRIVACY_COURSEMEMBER. + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_SITE, $user2->id); + $this->assertFalse(\core_message\api::can_post_message($user2)); + + // Enable site-wide messagging privacy setting. The user will be able to receive messages from everybody. + set_config('messagingallusers', true); + // Check that we can send user2 a message. + $this->assertTrue(\core_message\api::can_post_message($user2)); + } + + /** + * Tests get_user_privacy_messaging_preference method. + */ + public function test_get_user_privacy_messaging_preference() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + // Enable site-wide messagging privacy setting. The user will be able to receive messages from everybody. + set_config('messagingallusers', true); + + // Set some user preferences. + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_SITE, $user1->id); + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id); + + // Check the returned value for each user. + $this->assertEquals( + \core_message\api::MESSAGE_PRIVACY_SITE, + \core_message\api::get_user_privacy_messaging_preference($user1->id) + ); + $this->assertEquals( + \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, + \core_message\api::get_user_privacy_messaging_preference($user2->id) + ); + $this->assertEquals( + \core_message\api::MESSAGE_PRIVACY_SITE, + \core_message\api::get_user_privacy_messaging_preference($user3->id) + ); + + // Disable site-wide messagging privacy setting. The user will be able to receive messages from members of their course. + set_config('messagingallusers', false); + + // Check the returned value for each user. + $this->assertEquals( + \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER, + \core_message\api::get_user_privacy_messaging_preference($user1->id) + ); + $this->assertEquals( + \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, + \core_message\api::get_user_privacy_messaging_preference($user2->id) + ); + $this->assertEquals( + \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER, + \core_message\api::get_user_privacy_messaging_preference($user3->id) + ); + } + /** * Tests that when blocking messages from non-contacts is enabled that * non-contacts trying to send a message return false. @@ -1412,13 +1512,18 @@ class core_message_api_testcase extends core_message_messagelib_testcase { // Set as the first user. $this->setUser($user1); - // User hasn't sent their preference to block non-contacts, so should return false. + // By default, user only can be messaged by contacts and members of any of his/her courses. + $this->assertTrue(\core_message\api::is_user_non_contact_blocked($user2)); + + // Enable all users privacy messaging and check now the default user's preference has been set to allow receiving + // messages from everybody. + set_config('messagingallusers', true); + // Check that the return result is now false because any site user can contact him/her. $this->assertFalse(\core_message\api::is_user_non_contact_blocked($user2)); // Set the second user's preference to not receive messages from non-contacts. - set_user_preference('message_blocknoncontacts', 1, $user2->id); - - // Check that the return result is now true. + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id); + // Check that the return result is still true (because is even more restricted). $this->assertTrue(\core_message\api::is_user_non_contact_blocked($user2)); // Add the first user as a contact for the second user. @@ -1427,12 +1532,10 @@ class core_message_api_testcase extends core_message_messagelib_testcase { // Check that the return result is now false. $this->assertFalse(\core_message\api::is_user_non_contact_blocked($user2)); - // Set the first user's preference to not receive messages from non-contacts. - set_user_preference('message_blocknoncontacts', 1, $user1->id); - $this->setUser($user2); - // Confirm it is still false. We want to ensure a contact request works both ways - // as it is now an agreement between users. - $this->assertFalse(\core_message\api::is_user_non_contact_blocked($user1)); + // Set the second user's preference to receive messages from course members. + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_COURSEMEMBER, $user2->id); + // Check that the return result is still false (because $user1 is still his/her contact). + $this->assertFalse(\core_message\api::is_user_non_contact_blocked($user2)); } /** @@ -1757,6 +1860,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase { // Get the contacts and the unread message count. $messages = \core_message\api::get_contacts_with_unread_message_count($user2->id); + // Confirm the size is correct. $this->assertCount(2, $messages); ksort($messages); diff --git a/message/tests/behat/delete_all_messages.feature b/message/tests/behat/delete_all_messages.feature index 25d0c08bb5e..c6f354679cb 100644 --- a/message/tests/behat/delete_all_messages.feature +++ b/message/tests/behat/delete_all_messages.feature @@ -6,9 +6,16 @@ Feature: Delete all messages Scenario: Delete all messages Given the following "users" exist: - | username | firstname | lastname | email | + | username | firstname | lastname | email | | user1 | User | 1 | user1@example.com | | user2 | User | 2 | user2@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | user1 | C1 | student | + | user2 | C1 | student | And I log in as "user2" And I send "User 2 to User 1 message 1" message to "User 1" user And I send "User 2 to User 1 message 2" message in the message area diff --git a/message/tests/behat/delete_messages.feature b/message/tests/behat/delete_messages.feature index 600eb435b6d..a97087d13c3 100644 --- a/message/tests/behat/delete_messages.feature +++ b/message/tests/behat/delete_messages.feature @@ -6,9 +6,16 @@ Feature: Delete messages Scenario: Delete messages Given the following "users" exist: - | username | firstname | lastname | email | + | username | firstname | lastname | email | | user1 | User | 1 | user1@example.com | | user2 | User | 2 | user2@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | user1 | C1 | student | + | user2 | C1 | student | And I log in as "user2" And I send "User 2 to User 1 message 1" message to "User 1" user And I send "User 2 to User 1 message 2" message in the message area diff --git a/message/tests/behat/reply_message.feature b/message/tests/behat/reply_message.feature index d6eb761cbeb..a237ac3524d 100644 --- a/message/tests/behat/reply_message.feature +++ b/message/tests/behat/reply_message.feature @@ -9,6 +9,13 @@ Feature: Reply message | username | firstname | lastname | email | | user1 | User | 1 | user1@example.com | | user2 | User | 2 | user2@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | user1 | C1 | student | + | user2 | C1 | student | And I log in as "user2" And I send "User 2 to User 1" message to "User 1" user And I log out diff --git a/message/tests/behat/search_messages.feature b/message/tests/behat/search_messages.feature index 96a2e0d39b9..5c7d3e90963 100644 --- a/message/tests/behat/search_messages.feature +++ b/message/tests/behat/search_messages.feature @@ -6,10 +6,18 @@ Feature: Search messages Background: Given the following "users" exist: - | username | firstname | lastname | email | + | username | firstname | lastname | email | | user1 | User | 1 | user1@example.com | | user2 | User | 2 | user2@example.com | | user3 | User | 3 | user3@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | user1 | C1 | student | + | user2 | C1 | student | + | user3 | C1 | student | And I log in as "user2" And I send "User 2 to User 1" message to "User 1" user And I log out diff --git a/message/tests/behat/view_messages.feature b/message/tests/behat/view_messages.feature index d4ad844aad1..f979bd90b8b 100644 --- a/message/tests/behat/view_messages.feature +++ b/message/tests/behat/view_messages.feature @@ -6,10 +6,18 @@ Feature: View messages Scenario: View messages from multiple users Given the following "users" exist: - | username | firstname | lastname | email | + | username | firstname | lastname | email | | user1 | User | 1 | user1@example.com | | user2 | User | 2 | user2@example.com | | user3 | User | 3 | user3@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | user1 | C1 | student | + | user2 | C1 | student | + | user3 | C1 | student | And I log in as "user2" And I send "User 2 to User 1" message to "User 1" user And I log out diff --git a/message/tests/externallib_test.php b/message/tests/externallib_test.php index 16d38817f27..aa7f61f8d52 100644 --- a/message/tests/externallib_test.php +++ b/message/tests/externallib_test.php @@ -115,6 +115,18 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $sentmessages = core_message_external::send_instant_messages($messages); $sentmessages = external_api::clean_returnvalue(core_message_external::send_instant_messages_returns(), $sentmessages); + $this->assertEquals( + get_string('usercantbemessaged', 'message', fullname(\core_user::get_user($message1['touserid']))), + array_pop($sentmessages)['errormessage'] + ); + + // Add the user1 as a contact. + \core_message\api::add_contact($user1->id, $user2->id); + + // Send message again. Now it should work properly. + $sentmessages = core_message_external::send_instant_messages($messages); + // We need to execute the return values cleaning process to simulate the web service server. + $sentmessages = external_api::clean_returnvalue(core_message_external::send_instant_messages_returns(), $sentmessages); $sentmessage = reset($sentmessages); @@ -165,7 +177,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $sentmessage = reset($sentmessages); - $this->assertEquals(get_string('userisblockingyou', 'message'), $sentmessage['errormessage']); + $this->assertEquals(get_string('usercantbemessaged', 'message', fullname($user2)), $sentmessage['errormessage']); $this->assertEquals(0, $DB->count_records('messages')); } @@ -187,7 +199,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $this->setUser($user1); // Set the user preference so user 2 does not accept messages from non-contacts. - set_user_preference('message_blocknoncontacts', 1, $user2); + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2); // Create test message data. $message1 = array(); @@ -201,7 +213,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $sentmessage = reset($sentmessages); - $this->assertEquals(get_string('userisblockingyounoncontact', 'message', fullname($user2)), $sentmessage['errormessage']); + $this->assertEquals(get_string('usercantbemessaged', 'message', fullname($user2)), $sentmessage['errormessage']); $this->assertEquals(0, $DB->count_records('messages')); } @@ -223,7 +235,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $this->setUser($user1); // Set the user preference so user 2 does not accept messages from non-contacts. - set_user_preference('message_blocknoncontacts', 1, $user2); + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2); \core_message\api::add_contact($user1->id, $user2->id); @@ -3681,10 +3693,13 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $user = self::getDataGenerator()->create_user(); $this->setUser($user); + // Enable site-wide messagging privacy setting. The user will be able to receive messages from everybody. + set_config('messagingallusers', true); + // Set a couple of preferences to test. set_user_preference('message_provider_moodle_instantmessage_loggedin', 'email', $user); set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user); - set_user_preference('message_blocknoncontacts', 1, $user); + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_SITE, $user); $prefs = core_message_external::get_user_message_preferences(); $prefs = external_api::clean_returnvalue(core_message_external::get_user_message_preferences_returns(), $prefs); @@ -3692,7 +3707,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { // Check components. $this->assertCount(1, $prefs['preferences']['components']); - $this->assertTrue($prefs['blocknoncontacts']); + $this->assertEquals(\core_message\api::MESSAGE_PRIVACY_SITE, $prefs['blocknoncontacts']); // Check some preferences that we previously set. $found = false; diff --git a/message/tests/privacy_provider_test.php b/message/tests/privacy_provider_test.php index 3500c79c306..27e2232d133 100644 --- a/message/tests/privacy_provider_test.php +++ b/message/tests/privacy_provider_test.php @@ -160,7 +160,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide // Set some message user preferences. set_user_preference('message_provider_moodle_instantmessage_loggedin', 'airnotifier', $USER->id); set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'popup', $USER->id); - set_user_preference('message_blocknoncontacts', 1, $USER->id); + set_user_preference('message_blocknoncontacts', \core_message\api::MESSAGE_PRIVACY_ONLYCONTACTS, $USER->id); set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'inbound', $user->id); // Set an unrelated preference. diff --git a/message/upgrade.txt b/message/upgrade.txt index c543dc8f2b4..b623af2ae8b 100644 --- a/message/upgrade.txt +++ b/message/upgrade.txt @@ -36,6 +36,8 @@ information provided here is intended especially for developers. - core_message_external::unblock_contacts(), please use core_message_external::unblock_user() instead. - core_message_external::create_contacts(), please use core_message_external::create_contact_request() instead. - core_message_external::delete_conversation(), please use core_message_external::delete_conversations_by_id() instead. +* The following function has been added for getting the privacy messaging preference: + - get_user_privacy_messaging_preference() === 3.5 === diff --git a/user/tests/behat/delete_users.feature b/user/tests/behat/delete_users.feature index bc05131135e..00ae3655fdb 100644 --- a/user/tests/behat/delete_users.feature +++ b/user/tests/behat/delete_users.feature @@ -6,11 +6,20 @@ Feature: Deleting users Background: Given the following "users" exist: - | username | firstname | lastname | email | - | user1 | User | One | one@example.com | - | user2 | User | Two | two@example.com | - | user3 | User | Three | three@example.com | - | user4 | User | Four | four@example.com | + | username | firstname | lastname | email | + | user1 | User | One | one@example.com | + | user2 | User | Two | two@example.com | + | user3 | User | Three | three@example.com | + | user4 | User | Four | four@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | user1 | C1 | student | + | user2 | C1 | student | + | user3 | C1 | student | + | user4 | C1 | student | @javascript Scenario: Deleting one user at a time diff --git a/version.php b/version.php index 2b6955fc885..6ed63147562 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2018101700.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2018101700.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.