MDL-63214 message: Add privacy preferences to filter who can message
authorSara Arjona <sara@moodle.com>
Wed, 5 Sep 2018 13:04:35 +0000 (15:04 +0200)
committerSara Arjona <sara@moodle.com>
Wed, 17 Oct 2018 09:52:57 +0000 (11:52 +0200)
Add new messaging profile preferences to allow users to choose who
can message them:
- By default, users can select between 'My contacts only' and 'My
contacts and anyone in my courses'
- When $CFG->messagingallusers is enabled, a new option is displayed
also: 'Anyone on the site'. For users who have selected this option,
if $CFG->messagingallusers is disabled, the behaviour will be changed
to 'My contacts and anyone in my courses'

21 files changed:
lang/en/deprecated.txt
lang/en/message.php
lib/db/upgrade.php
message/amd/build/message_preferences.min.js
message/amd/src/message_preferences.js
message/classes/api.php
message/externallib.php
message/lib.php
message/output/popup/tests/behat/message_popover_unread.feature
message/renderer.php
message/templates/message_preferences.mustache
message/tests/api_test.php
message/tests/behat/delete_all_messages.feature
message/tests/behat/delete_messages.feature
message/tests/behat/reply_message.feature
message/tests/behat/search_messages.feature
message/tests/behat/view_messages.feature
message/tests/externallib_test.php
message/tests/privacy_provider_test.php
message/upgrade.txt
user/tests/behat/delete_users.feature

index 873ad79..c1939ef 100644 (file)
@@ -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
index 41fb26e..562723b 100644 (file)
@@ -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
index a2cc158..7a97669 100644 (file)
@@ -2516,9 +2516,17 @@ function xmldb_main_upgrade($oldversion) {
             set_config('messagingallusers', false);
         } else {
             // When $CFG->keepmessagingallusersenabled is set to true, $CFG->messagingallusers is set to true.
-            // When $CFG->messagingallusers = true, the default user preference is MESSAGE_PRIVACY_SITE
-            // (contacted by all users site).
             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.
index 436b3bb..54bf917 100644 (file)
Binary files a/message/amd/build/message_preferences.min.js and b/message/amd/build/message_preferences.min.js differ
index c8a4372..c5944b6 100644 (file)
@@ -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));
     };
index 8bc93a7..51d6418 100644 (file)
@@ -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.
      *
@@ -857,7 +872,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);
 
@@ -927,6 +942,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
@@ -937,23 +982,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;
index f678c2f..7e8bb5d 100644 (file)
@@ -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
@@ -3068,7 +3031,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;
     }
@@ -3083,7 +3046,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(),
             )
         );
index 1fbe61c..a85e65a 100644 (file)
@@ -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) {
index 67289a2..b27e22e 100644 (file)
@@ -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
index 1aac259..17af06d 100644 (file)
@@ -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);
     }
index 43e2ea9..ebe1c40 100644 (file)
@@ -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):
     {
                     }
                 ]
             }
+        ],
+        "privacychoices": [
+            {
+                "value": 1,
+                "text": "My contacts only",
+                "checked": 0
+            },
+            {
+                "value": 2,
+                "text": "Anyone within courses I am a member of",
+                "checked": 1
+            }
         ]
     }
 }}
 <div class="preferences-page-container" data-region="preferences-page-container">
     <h2>{{#str}} messagepreferences, message {{/str}}</h2>
-    <div class="checkbox-container" data-region="block-non-contacts-container">
-        <input id="block-non-contacts"
-                type="checkbox"
-                data-user-id="{{userid}}"
-                data-block-non-contacts
-                data-preference-key="message_blocknoncontacts"
-                {{#blocknoncontacts}}checked{{/blocknoncontacts}} />
-        <label for="block-non-contacts">{{#str}} blocknoncontacts, message {{/str}}</label>
-        {{> core/loading }}
-    </div>
+    <div class="privacy-setting-container"
+         data-user-id="{{userid}}"
+         data-region="privacy-setting-container"
+         data-preference-key="message_blocknoncontacts">
+        <p>{{#str}} contactableprivacy, message {{/str}}</p>
+       {{#privacychoices}}
+        <input id="action-selection-option-{{value}}"
+               type="radio"
+               name="message_blocknoncontacts"
+               value="{{value}}"
+               {{#checked}}checked="checked"{{/checked}}/>
+        <label for="action-selection-option-{{value}}">{{text}}</label>
+        <br>
+       {{/privacychoices}}
+    </div><br>
     <div class="preferences-container {{#disableall}}disabled{{/disableall}}"
         data-user-id="{{userid}}"
         data-region="preferences-container">
index 0278d67..e93d026 100644 (file)
@@ -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());
 
@@ -1273,10 +1274,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));
     }
 
@@ -1302,6 +1310,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.
@@ -1315,7 +1344,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));
@@ -1339,6 +1368,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.
@@ -1351,13 +1451,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.
@@ -1366,12 +1471,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));
     }
 
     /**
@@ -1696,6 +1799,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);
index 25d0c08..c6f3546 100644 (file)
@@ -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
index 600eb43..a97087d 100644 (file)
@@ -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
index d6eb761..a237ac3 100644 (file)
@@ -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
index 96a2e0d..5c7d3e9 100644 (file)
@@ -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
index d4ad844..f979bd9 100644 (file)
@@ -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
index 784ad14..90f5e11 100644 (file)
@@ -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);
 
@@ -3507,10 +3519,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);
@@ -3518,7 +3533,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;
index 3500c79..27e2232 100644 (file)
@@ -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.
index 2ada652..2e05bbd 100644 (file)
@@ -32,6 +32,8 @@ information provided here is intended especially for developers.
   - core_message_external::block_contacts, please use core_message_external::block_user instead.
   - 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.
+* The following function has been added for getting the privacy messaging preference:
+  - get_user_privacy_messaging_preference()
 
 === 3.5 ===
 
index bc05131..00ae365 100644 (file)
@@ -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