Merge branch 'MDL-61257-master' of git://github.com/mihailges/moodle
authorJun Pataleta <jun@moodle.com>
Tue, 27 Mar 2018 04:59:16 +0000 (12:59 +0800)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 28 Mar 2018 16:16:06 +0000 (18:16 +0200)
76 files changed:
admin/settings/plugins.php
grade/export/lib.php
lang/en/message.php
lib/adminlib.php
lib/classes/event/message_deleted.php
lib/classes/event/message_sent.php
lib/classes/event/message_viewed.php
lib/classes/event/notification_sent.php [new file with mode: 0644]
lib/classes/event/notification_viewed.php [new file with mode: 0644]
lib/classes/message/manager.php
lib/classes/plugin_manager.php
lib/classes/task/messaging_cleanup_task.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/dml/mariadb_native_moodle_database.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js
lib/editor/atto/plugins/table/yui/src/button/js/button.js
lib/messagelib.php
lib/moodlelib.php
lib/phpunit/classes/message_sink.php
lib/phpunit/classes/phpmailer_sink.php
lib/phpunit/classes/util.php
lib/tests/message_test.php
lib/tests/messagelib_test.php
lib/upgrade.txt
message/classes/api.php
message/classes/helper.php
message/classes/search/base_message.php
message/classes/search/message_received.php
message/classes/search/message_sent.php
message/externallib.php
message/index.php
message/lib.php
message/output/popup/amd/build/notification_repository.min.js
message/output/popup/amd/src/notification_repository.js
message/output/popup/classes/api.php
message/output/popup/classes/output/popup_notification.php
message/output/popup/db/install.xml [deleted file]
message/output/popup/db/upgrade.php
message/output/popup/externallib.php
message/output/popup/message_output_popup.php
message/output/popup/tests/base.php
message/output/popup/tests/behat/message_popover_unread.feature
message/output/popup/version.php
message/tests/api_test.php
message/tests/events_test.php
message/tests/externallib_test.php
message/tests/messagelib_test.php
message/tests/search_received_test.php
message/tests/search_sent_test.php
message/upgrade.txt
mod/assign/tests/locallib_test.php
mod/feedback/item/multichoicerated/lib.php
mod/feedback/lang/en/feedback.php
mod/lti/lang/en/lti.php
mod/lti/locallib.php
plagiarism/classes/privacy/plagiarism_provider.php
report/security/locallib.php
search/engine/simpledb/classes/engine.php [new file with mode: 0644]
search/engine/simpledb/db/install.php [new file with mode: 0644]
search/engine/simpledb/db/install.xml [new file with mode: 0644]
search/engine/simpledb/db/uninstall.php [new file with mode: 0644]
search/engine/simpledb/lang/en/search_simpledb.php [new file with mode: 0644]
search/engine/simpledb/tests/engine_test.php [new file with mode: 0644]
search/engine/simpledb/version.php [moved from message/output/popup/db/events.php with 68% similarity]
search/engine/solr/lang/en/search_solr.php
search/engine/solr/tests/engine_test.php
version.php

index c55b1f1..9a2202f 100644 (file)
@@ -556,7 +556,7 @@ if ($hassiteconfig) {
     // Search engine selection.
     $temp->add(new admin_setting_heading('searchengineheading', new lang_string('searchengine', 'admin'), ''));
     $temp->add(new admin_setting_configselect('searchengine',
-                                new lang_string('selectsearchengine', 'admin'), '', 'solr', $engines));
+                                new lang_string('selectsearchengine', 'admin'), '', 'simpledb', $engines));
     $temp->add(new admin_setting_heading('searchoptionsheading', new lang_string('searchoptions', 'admin'), ''));
     $temp->add(new admin_setting_configcheckbox('searchindexwhendisabled',
             new lang_string('searchindexwhendisabled', 'admin'), new lang_string('searchindexwhendisabled_desc', 'admin'),
index 755fa8a..6dabe7a 100644 (file)
@@ -270,7 +270,7 @@ abstract class grade_export {
         if ($grade_item->itemtype == 'mod') {
             $column->name = get_string('modulename', $grade_item->itemmodule).get_string('labelsep', 'langconfig').$grade_item->get_name();
         } else {
-            $column->name = $grade_item->get_name();
+            $column->name = $grade_item->get_name(true);
         }
 
         // We can't have feedback and display type at the same time.
index 91eac47..a97ebbb 100644 (file)
@@ -45,6 +45,8 @@ $string['emailtagline'] = 'This is a copy of a message sent to you at "{$a->site
 $string['enabled'] = 'Enabled';
 $string['errorcallingprocessor'] = 'Error calling defined output';
 $string['errortranslatingdefault'] = 'Error translating default setting provided by plugin, using system defaults instead.';
+$string['eventnotificationviewed'] = 'Notification viewed';
+$string['eventnotificationsent'] = 'Notification sent';
 $string['eventmessagecontactadded'] = 'Message contact added';
 $string['eventmessagecontactblocked'] = 'Message contact blocked';
 $string['eventmessagecontactremoved'] = 'Message contact removed';
index 30f756a..b14741e 100644 (file)
@@ -2960,6 +2960,7 @@ class admin_setting_configselect extends admin_setting {
     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
         // Look for optgroup and single options.
         if (is_array($choices)) {
+            $this->choices = [];
             foreach ($choices as $key => $val) {
                 if (is_array($val)) {
                     $this->optgroups[$key] = $val;
index 7d5087e..b991853 100644 (file)
@@ -32,7 +32,6 @@ defined('MOODLE_INTERNAL') || die();
  * @property-read array $other {
  *      Extra information about event.
  *
- *      - string $messagetable: the table we marked the message as deleted from (message/message_read).
  *      - int messageid: the id of the message.
  *      - int useridfrom: the id of the user who received the message.
  *      - int useridto: the id of the user who sent the message.
@@ -51,11 +50,11 @@ class message_deleted extends base {
      * @param int $userfromid the user who the message was from.
      * @param int $usertoid the user who the message was sent to.
      * @param int $userdeleted the user who deleted it.
-     * @param string $messagetable the table we are marking the message as deleted in.
      * @param int $messageid the id of the message that was deleted.
+     * @param int $muaid The id in the message_user_actions table
      * @return message_deleted
      */
-    public static function create_from_ids($userfromid, $usertoid, $userdeleted, $messagetable, $messageid) {
+    public static function create_from_ids($userfromid, $usertoid, $userdeleted, $messageid, $muaid) {
         // Check who was deleting the message.
         if ($userdeleted == $userfromid) {
             $relateduserid = $usertoid;
@@ -66,11 +65,11 @@ class message_deleted extends base {
         // We set the userid to the user who deleted the message, nothing to do
         // with whether or not they sent or received the message.
         $event = self::create(array(
+            'objectid' => $muaid,
             'userid' => $userdeleted,
             'context' => \context_system::instance(),
             'relateduserid' => $relateduserid,
             'other' => array(
-                'messagetable' => $messagetable,
                 'messageid' => $messageid,
                 'useridfrom' => $userfromid,
                 'useridto' => $usertoid
@@ -84,7 +83,8 @@ class message_deleted extends base {
      * Init method.
      */
     protected function init() {
-        $this->data['crud'] = 'u';
+        $this->data['objecttable'] = 'message_user_actions';
+        $this->data['crud'] = 'c';
         $this->data['edulevel'] = self::LEVEL_OTHER;
     }
 
@@ -126,10 +126,6 @@ class message_deleted extends base {
             throw new \coding_exception('The \'relateduserid\' must be set.');
         }
 
-        if (!isset($this->other['messagetable'])) {
-            throw new \coding_exception('The \'messagetable\' value must be set in other.');
-        }
-
         if (!isset($this->other['messageid'])) {
             throw new \coding_exception('The \'messageid\' value must be set in other.');
         }
@@ -143,11 +139,13 @@ class message_deleted extends base {
         }
     }
 
+    public static function get_objectid_mapping() {
+        return array('db' => 'message_user_actions', 'restore' => base::NOT_MAPPED);
+    }
+
     public static function get_other_mapping() {
         // Messages are not backed up, so no need to map them on restore.
         $othermapped = array();
-        // The messageid table varies so it cannot be mapped.
-        $othermapped['messageid'] = array('db' => base::NOT_MAPPED, 'restore' => base::NOT_MAPPED);
         $othermapped['useridfrom'] = array('db' => 'user', 'restore' => base::NOT_MAPPED);
         $othermapped['useridto'] = array('db' => 'user', 'restore' => base::NOT_MAPPED);
         return $othermapped;
index e330919..9b3a8c5 100644 (file)
@@ -32,7 +32,6 @@ defined('MOODLE_INTERNAL') || die();
  * @property-read array $other {
  *      Extra information about event.
  *
- *      - int messageid: the id of the message.
  *      - int courseid: the id of the related course.
  * }
  *
@@ -69,14 +68,11 @@ class message_sent extends base {
         }
 
         $event = self::create(array(
+            'objectid' => $messageid,
             'userid' => $userfromid,
             'context' => \context_system::instance(),
             'relateduserid' => $usertoid,
             'other' => array(
-                // In earlier versions it can either be the id in the 'message_read' or 'message' table.
-                // Now it is always the id from 'message' table. Please note that the record is still moved
-                // to the 'message_read' table later when message marked as read.
-                'messageid' => $messageid,
                 'courseid' => $courseid
             )
         ));
@@ -88,6 +84,7 @@ class message_sent extends base {
      * Init method.
      */
     protected function init() {
+        $this->data['objecttable'] = 'messages';
         $this->data['crud'] = 'c';
         $this->data['edulevel'] = self::LEVEL_OTHER;
     }
@@ -133,8 +130,9 @@ class message_sent extends base {
         // The add_to_log function was only ever called when we sent a message from one user to another. We do not want
         // to return the legacy log data if we are sending a system message, so check that the userid is valid.
         if (\core_user::is_real_user($this->userid)) {
+            $messageid = $this->other['messageid'] ?? $this->objectid; // For BC we may have 'messageid' in other.
             return array(SITEID, 'message', 'write', 'index.php?user=' . $this->userid . '&id=' . $this->relateduserid .
-                '&history=1#m' . $this->other['messageid'], $this->userid);
+                '&history=1#m' . $messageid, $this->userid);
         }
 
         return null;
@@ -153,26 +151,18 @@ class message_sent extends base {
             throw new \coding_exception('The \'relateduserid\' must be set.');
         }
 
-        if (!isset($this->other['messageid'])) {
-            throw new \coding_exception('The \'messageid\' value must be set in other.');
-        }
-
         if (!isset($this->other['courseid'])) {
             throw new \coding_exception('The \'courseid\' value must be set in other.');
         }
     }
 
     public static function get_objectid_mapping() {
-        // Messages are not backed up, so no need to map them.
-        return false;
+        return array('db' => 'messages', 'restore' => base::NOT_MAPPED);
     }
 
     public static function get_other_mapping() {
-        // Messages are not backed up, so no need to map them on restore.
         $othermapped = array();
-        // The messages table could vary for older events - so cannot be mapped.
-        $othermapped['messageid'] = array('db' => base::NOT_MAPPED, 'restore' => base::NOT_MAPPED);
-        $othermapped['courseid'] = array('db' => base::NOT_MAPPED, 'restore' => base::NOT_MAPPED);
+        $othermapped['courseid'] = array('db' => 'course', 'restore' => base::NOT_MAPPED);
         return $othermapped;
     }
 }
index 8e12947..35ae1ff 100644 (file)
@@ -46,7 +46,7 @@ class message_viewed extends base {
      * Init method.
      */
     protected function init() {
-        $this->data['objecttable'] = 'message_read';
+        $this->data['objecttable'] = 'message_user_actions';
         $this->data['crud'] = 'c';
         $this->data['edulevel'] = self::LEVEL_OTHER;
     }
@@ -97,15 +97,13 @@ class message_viewed extends base {
     }
 
     public static function get_objectid_mapping() {
-        // Messages are not backed up, so no need to map them.
-        return array('db' => 'message_read', 'restore' => base::NOT_MAPPED);
+        return array('db' => 'message_user_actions', 'restore' => base::NOT_MAPPED);
     }
 
     public static function get_other_mapping() {
         // Messages are not backed up, so no need to map them on restore.
         $othermapped = array();
-        // The messages table could vary for older events - so cannot be mapped.
-        $othermapped['messageid'] = array('db' => base::NOT_MAPPED, 'restore' => base::NOT_MAPPED);
+        $othermapped['messageid'] = array('db' => 'messages', 'restore' => base::NOT_MAPPED);
         return $othermapped;
     }
 }
diff --git a/lib/classes/event/notification_sent.php b/lib/classes/event/notification_sent.php
new file mode 100644 (file)
index 0000000..1c2080c
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Notification sent event.
+ *
+ * @package    core
+ * @copyright  2018 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Notification sent event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int courseid: the id of the related course.
+ * }
+ *
+ * @package    core
+ * @copyright  2018 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class notification_sent extends base {
+
+    /**
+     * Create event using ids.
+     *
+     * @param int $userfromid
+     * @param int $usertoid
+     * @param int $notificationid
+     * @param int $courseid course id the event is related with - SITEID if no relation exists.
+     * @return notification_sent
+     */
+    public static function create_from_ids($userfromid, $usertoid, $notificationid, $courseid) {
+        // We may be sending a notification from the 'noreply' address, which means we are not actually sending a
+        // notification from a valid user. In this case, we will set the userid to 0.
+        // Check if the userid is valid.
+        if (!\core_user::is_real_user($userfromid)) {
+            $userfromid = 0;
+        }
+
+        $event = self::create(
+            [
+                'objectid' => $notificationid,
+                'userid' => $userfromid,
+                'context' => \context_system::instance(),
+                'relateduserid' => $usertoid,
+                'other' => [
+                    'courseid' => $courseid
+                ]
+            ]
+        );
+
+        return $event;
+    }
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'notifications';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventnotificationsent', 'message');
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/message/output/popup/notifications.php', array('notificationid' => $this->objectid));
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        // Check if we are sending from a valid user.
+        if (\core_user::is_real_user($this->userid)) {
+            return "The user with id '$this->userid' sent a notification to the user with id '$this->relateduserid'.";
+        }
+
+        return "A notification was sent by the system to the user with id '$this->relateduserid'.";
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+
+        if (!isset($this->other['courseid'])) {
+            throw new \coding_exception('The \'courseid\' value must be set in other.');
+        }
+    }
+
+    public static function get_objectid_mapping() {
+        return array('db' => 'notifications', 'restore' => base::NOT_MAPPED);
+    }
+
+    public static function get_other_mapping() {
+        $othermapped = array();
+        $othermapped['courseid'] = array('db' => 'course', 'restore' => base::NOT_MAPPED);
+        return $othermapped;
+    }
+}
diff --git a/lib/classes/event/notification_viewed.php b/lib/classes/event/notification_viewed.php
new file mode 100644 (file)
index 0000000..f33c333
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Notification viewed event.
+ *
+ * @package    core
+ * @copyright  2018 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Notification viewed event class.
+ *
+ * @package    core
+ * @copyright  2018 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class notification_viewed extends base {
+
+    /**
+     * Create event using ids.
+     *
+     * @param int $userfromid
+     * @param int $usertoid
+     * @param int $notificationid
+     * @return notification_viewed
+     */
+    public static function create_from_ids($userfromid, $usertoid, $notificationid) {
+        // We may be sending a notification from the 'noreply' address, which means we are not actually sending a
+        // notification from a valid user. In this case, we will set the userid to 0.
+        // Check if the userid is valid.
+        if (!\core_user::is_real_user($userfromid)) {
+            $userfromid = 0;
+        }
+
+        // Get the context for the user who received the notification.
+        $context = \context_user::instance($usertoid, IGNORE_MISSING);
+        // If the user no longer exists the context value will be false, in this case use the system context.
+        if ($context === false) {
+            $context = \context_system::instance();
+        }
+
+        $event = self::create(
+            [
+                'objectid' => $notificationid,
+                'userid' => $usertoid, // Using the user who read the notification as they are the ones performing the action.
+                'context' => $context,
+                'relateduserid' => $userfromid
+            ]
+        );
+
+        return $event;
+    }
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'notifications';
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventnotificationviewed', 'message');
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/message/output/popup/notifications.php', array('notificationid' => $this->objectid));
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' read a notification from the user with id '$this->relateduserid'.";
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+    }
+
+    public static function get_objectid_mapping() {
+        return array('db' => 'notifications', 'restore' => base::NOT_MAPPED);
+    }
+}
index d08489d..c05e14d 100644 (file)
@@ -54,7 +54,7 @@ class manager {
      * @param \core\message\message $eventdata fully prepared event data for processors
      * @param \stdClass $savemessage the message saved in 'message' table
      * @param array $processorlist list of processors for target user
-     * @return int $messageid the id from 'message' or 'message_read' table (false is not returned)
+     * @return int $messageid the id from 'messages' (false is not returned)
      */
     public static function send_message($eventdata, \stdClass $savemessage, array $processorlist) {
         global $CFG;
@@ -77,27 +77,35 @@ class manager {
         require_once($CFG->dirroot.'/message/lib.php'); // This is most probably already included from messagelib.php file.
 
         if (empty($processorlist)) {
-            // Trigger event for sending a message - we need to do this before marking as read!
-            \core\event\message_sent::create_from_ids(
-                $eventdata->userfrom->id,
-                $eventdata->userto->id,
-                $savemessage->id,
-                $eventdata->courseid
+            // Trigger event for sending a message or notification - we need to do this before marking as read!
+            if ($eventdata->notification) {
+                \core\event\notification_sent::create_from_ids(
+                    $eventdata->userfrom->id,
+                    $eventdata->userto->id,
+                    $savemessage->id,
+                    $eventdata->courseid
+                )->trigger();
+            } else { // Must be a message.
+                \core\event\message_sent::create_from_ids(
+                    $eventdata->userfrom->id,
+                    $eventdata->userto->id,
+                    $savemessage->id,
+                    $eventdata->courseid
                 )->trigger();
+            }
 
-            if ($savemessage->notification or empty($CFG->messaging)) {
+            if ($eventdata->notification or empty($CFG->messaging)) {
                 // If they have deselected all processors and its a notification mark it read. The user doesn't want to be bothered.
                 // The same goes if the messaging is completely disabled.
-                // We cannot insert directly to the message_read table because we want to get all events in proper order!
-                $messageid = message_mark_message_read($savemessage, time(), true);
-
-            } else {
-                // Just add it to the list of unread messages, there is no way it could be delivered to them,
-                // but they can read it via the messaging UI later.
-                $messageid = $savemessage->id;
+                if ($eventdata->notification) {
+                    $savemessage->timeread = null;
+                    \core_message\api::mark_notification_as_read($savemessage);
+                } else {
+                    \core_message\api::mark_message_as_read($eventdata->userto->id, $savemessage);
+                }
             }
 
-            return $messageid;
+            return $savemessage->id;
         }
 
         // Let the manager do the sending or buffering when db transaction in progress.
@@ -133,7 +141,6 @@ class manager {
             return $savemessage->id;
         }
 
-        $failed = false;
         foreach ($processorlist as $procname) {
             // Let new messaging class add custom content based on the processor.
             $proceventdata = ($eventdata instanceof message) ? $eventdata->get_eventobject_for_processor($procname) : $eventdata;
@@ -142,40 +149,38 @@ class manager {
             $processor = \core_message\api::get_processed_processor_object($stdproc);
             if (!$processor->object->send_message($proceventdata)) {
                 debugging('Error calling message processor ' . $procname);
-                $failed = true;
-                // Previously the $messageid = false here was overridden
-                // by other processors and message_mark_message_read() below.
             }
         }
 
-        // Trigger event for sending a message - must be done before marking as read.
-        \core\event\message_sent::create_from_ids(
-            $eventdata->userfrom->id,
-            $eventdata->userto->id,
-            $savemessage->id,
-            $eventdata->courseid
+        // Trigger event for sending a message or notification - we need to do this before marking as read!
+        if ($eventdata->notification) {
+            \core\event\notification_sent::create_from_ids(
+                $eventdata->userfrom->id,
+                $eventdata->userto->id,
+                $savemessage->id,
+                $eventdata->courseid
             )->trigger();
+        } else { // Must be a message.
+            \core\event\message_sent::create_from_ids(
+                $eventdata->userfrom->id,
+                $eventdata->userto->id,
+                $savemessage->id,
+                $eventdata->courseid
+            )->trigger();
+        }
 
         if (empty($CFG->messaging)) {
-            // If messaging is disabled and they previously had forum notifications handled by the popup processor
-            // or any processor that puts a row in message_working then the notification will remain forever
-            // unread. To prevent this mark the message read if messaging is disabled.
-            $messageid = message_mark_message_read($savemessage, time());
-
-        } else if ($failed) {
-            // Something failed, better keep it as unread then.
-            $messageid = $savemessage->id;
-
-        } else if ($DB->count_records('message_working', array('unreadmessageid' => $savemessage->id)) == 0) {
-            // If there is no more processors that want to process this we can move message to message_read.
-            $messageid = message_mark_message_read($savemessage, time(), true);
-
-        } else {
-            // Some processor is still working on the data, let's keep it unread.
-            $messageid = $savemessage->id;
+            // If they have deselected all processors and its a notification mark it read. The user doesn't want to be bothered.
+            // The same goes if the messaging is completely disabled.
+            if ($eventdata->notification) {
+                $savemessage->timeread = null;
+                \core_message\api::mark_notification_as_read($savemessage);
+            } else {
+                \core_message\api::mark_message_as_read($eventdata->userto->id, $savemessage);
+            }
         }
 
-        return $messageid;
+        return $savemessage->id;
     }
 
     /**
index 7af9689..fca4ec8 100644 (file)
@@ -1884,7 +1884,7 @@ class core_plugin_manager {
             ),
 
             'search' => array(
-                'solr'
+                'simpledb', 'solr'
             ),
 
             'scormreport' => array(
index 4debce3..6d1a2c4 100644 (file)
@@ -50,7 +50,7 @@ class messaging_cleanup_task extends scheduled_task {
         if (!empty($CFG->messagingdeletereadnotificationsdelay)) {
             $notificationdeletetime = $timenow - $CFG->messagingdeletereadnotificationsdelay;
             $params = array('notificationdeletetime' => $notificationdeletetime);
-            $DB->delete_records_select('message_read', 'notification=1 AND timeread<:notificationdeletetime', $params);
+            $DB->delete_records_select('notifications', 'timeread < :notificationdeletetime', $params);
         }
 
     }
index 97df485..abd5c93 100644 (file)
         <INDEX NAME="useridto_timeusertodeleted_notification" UNIQUE="false" FIELDS="useridto, timeusertodeleted, notification"/>
       </INDEXES>
     </TABLE>
+    <TABLE NAME="messages" COMMENT="Stores all messages">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="useridfrom" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="conversationid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="subject" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="fullmessage" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="fullmessageformat" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="fullmessagehtml" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="smallmessage" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="useridfrom" TYPE="foreign" FIELDS="useridfrom" REFTABLE="user" REFFIELDS="id"/>
+        <KEY NAME="conversationid" TYPE="foreign" FIELDS="conversationid" REFTABLE="message_conversations" REFFIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="conversationid_timecreated" UNIQUE="false" FIELDS="conversationid, timecreated"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="message_conversations" COMMENT="Stores all message conversations">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="convhash" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="convhash" UNIQUE="true" FIELDS="convhash"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="message_conversation_members" COMMENT="Stores all members in a conversations">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="conversationid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="conversationid" TYPE="foreign" FIELDS="conversationid" REFTABLE="message_conversations" REFFIELDS="id"/>
+        <KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="message_user_actions" COMMENT="Stores all per-user actions on individual messages">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="messageid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="action" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
+        <KEY NAME="messageid" TYPE="foreign" FIELDS="messageid" REFTABLE="messages" REFFIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="userid_messageid_action" UNIQUE="true" FIELDS="userid, messageid, action"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="notifications" COMMENT="Stores all notifications">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="useridfrom" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="useridto" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="subject" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The message subject"/>
+        <FIELD NAME="fullmessage" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="fullmessageformat" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="fullmessagehtml" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="smallmessage" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="eventtype" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="contexturl" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="contexturlname" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="timeread" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="useridto" TYPE="foreign" FIELDS="useridto" REFTABLE="user" REFFIELDS="id"/>
+      </KEYS>
+    </TABLE>
     <TABLE NAME="message_contacts" COMMENT="Maintains lists of relationships between users">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
         <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="primary key of the table, please edit me"/>
       </KEYS>
     </TABLE>
-    <TABLE NAME="message_working" COMMENT="Lists all the messages and processors that need to be processed">
-      <FIELDS>
-        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" COMMENT="id of the table, please edit me"/>
-        <FIELD NAME="unreadmessageid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="message that still needs some processing (on message table)"/>
-        <FIELD NAME="processorid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The processor with processes the message"/>
-      </FIELDS>
-      <KEYS>
-        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="primary key of the table, please edit me"/>
-      </KEYS>
-      <INDEXES>
-        <INDEX NAME="unreadmessageid_idx" UNIQUE="false" FIELDS="unreadmessageid" COMMENT="Index on unreadmessage id"/>
-      </INDEXES>
-    </TABLE>
     <TABLE NAME="files" COMMENT="description of files, content is stored in sha1 file pool">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
index 35ba7f9..55943d6 100644 (file)
@@ -990,6 +990,15 @@ $functions = array(
         'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
         'ajax' => true,
     ),
+    'core_message_mark_notification_read' => array(
+        'classname' => 'core_message_external',
+        'methodname' => 'mark_notification_read',
+        'classpath' => 'message/externallib.php',
+        'description' => 'Mark a single notification as read, trigger notification_viewed event.',
+        'type' => 'write',
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+        'ajax' => true,
+    ),
     'core_message_message_processor_config_form' => array(
         'classname' => 'core_message_external',
         'methodname' => 'message_processor_config_form',
index 78ebbee..6d26f71 100644 (file)
@@ -2020,5 +2020,184 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2018022800.03);
     }
 
+    if ($oldversion < 2018032200.01) {
+        // Define table 'messages' to be created.
+        $table = new xmldb_table('messages');
+
+        // Adding fields to table 'messages'.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('useridfrom', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('conversationid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('subject', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('fullmessage', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('fullmessageformat', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, 0);
+        $table->add_field('fullmessagehtml', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('smallmessage', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table 'messages'.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('useridfrom', XMLDB_KEY_FOREIGN, array('useridfrom'), 'user', array('id'));
+        $table->add_key('conversationid', XMLDB_KEY_FOREIGN, array('conversationid'), 'message_conversations', array('id'));
+
+        // Conditionally launch create table for 'messages'.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table 'message_conversations' to be created.
+        $table = new xmldb_table('message_conversations');
+
+        // Adding fields to table 'message_conversations'.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table 'message_conversations'.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        // Conditionally launch create table for 'message_conversations'.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table 'message_conversation_members' to be created.
+        $table = new xmldb_table('message_conversation_members');
+
+        // Adding fields to table 'message_conversation_members'.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('conversationid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table 'message_conversation_members'.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('conversationid', XMLDB_KEY_FOREIGN, array('conversationid'), 'message_conversations', array('id'));
+        $table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
+
+        // Conditionally launch create table for 'message_conversation_members'.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table 'message_user_actions' to be created.
+        $table = new xmldb_table('message_user_actions');
+
+        // Adding fields to table 'message_user_actions'.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('messageid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('action', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table 'message_user_actions'.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
+        $table->add_key('messageid', XMLDB_KEY_FOREIGN, array('messageid'), 'messages', array('id'));
+
+        // Conditionally launch create table for 'message_user_actions'.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table 'notifications' to be created.
+        $table = new xmldb_table('notifications');
+
+        // Adding fields to table 'notifications'.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('useridfrom', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('useridto', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('subject', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('fullmessage', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('fullmessageformat', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, 0);
+        $table->add_field('fullmessagehtml', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('smallmessage', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+        $table->add_field('eventtype', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+        $table->add_field('contexturl', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('contexturlname', XMLDB_TYPE_TEXT, null, null, null, null, null);
+        $table->add_field('timeread', XMLDB_TYPE_INTEGER, '10', null, false, null, null);
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table 'notifications'.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('useridto', XMLDB_KEY_FOREIGN, array('useridto'), 'user', array('id'));
+
+        // Conditionally launch create table for 'notifications'.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2018032200.01);
+    }
+
+    if ($oldversion < 2018032200.04) {
+        // Define table 'message_conversations' to be updated.
+        $table = new xmldb_table('message_conversations');
+        $field = new xmldb_field('convhash', XMLDB_TYPE_CHAR, '40', null, XMLDB_NOTNULL, null, null, 'id');
+
+        // Conditionally launch add field 'convhash'.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Conditionally launch add index.
+        $index = new xmldb_index('convhash', XMLDB_INDEX_UNIQUE, array('convhash'));
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2018032200.04);
+    }
+
+    if ($oldversion < 2018032200.05) {
+        // Drop table that is no longer needed.
+        $table = new xmldb_table('message_working');
+        if ($dbman->table_exists($table)) {
+            $dbman->drop_table($table);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2018032200.05);
+    }
+
+    if ($oldversion < 2018032200.06) {
+        // Define table 'message_user_actions' to add an index to.
+        $table = new xmldb_table('message_user_actions');
+
+        // Conditionally launch add index.
+        $index = new xmldb_index('userid_messageid_action', XMLDB_INDEX_UNIQUE, array('userid, messageid, action'));
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2018032200.06);
+    }
+
+    if ($oldversion < 2018032200.07) {
+        // Define table 'messages' to add an index to.
+        $table = new xmldb_table('messages');
+
+        // Conditionally launch add index.
+        $index = new xmldb_index('conversationid_timecreated', XMLDB_INDEX_NOTUNIQUE, array('conversationid, timecreated'));
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2018032200.07);
+    }
+
+    if ($oldversion < 2018032700.00) {
+
+        // Update default search engine to search_simpledb if global search is disabled and there is no solr index defined.
+        if (empty($CFG->enableglobalsearch) && empty(get_config('search_solr', 'indexname'))) {
+            set_config('searchengine', 'simpledb');
+        }
+        upgrade_main_savepoint(true, 2018032700.00);
+    }
+
     return true;
 }
index 132eb55..d479b71 100644 (file)
@@ -4709,29 +4709,10 @@ function message_contact_link($userid, $linktype='add', $return=false, $script=n
 }
 
 /**
- * Get the users recent event notifications
- *
  * @deprecated since Moodle 3.2
- * @param object $user the current user
- * @param int $limitfrom can be used for paging
- * @param int $limitto can be used for paging
- * @return array
  */
 function message_get_recent_notifications($user, $limitfrom=0, $limitto=100) {
-    debugging('message_get_recent_notifications() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    global $DB;
-
-    $userfields = user_picture::fields('u', array('lastaccess'));
-    $sql = "SELECT mr.id AS message_read_id, $userfields, mr.notification, mr.smallmessage, mr.fullmessage, mr.fullmessagehtml, mr.fullmessageformat, mr.timecreated as timecreated, mr.contexturl, mr.contexturlname
-              FROM {message_read} mr
-                   JOIN {user} u ON u.id=mr.useridfrom
-             WHERE mr.useridto = :userid1 AND u.deleted = '0' AND mr.notification = :notification
-             ORDER BY mr.timecreated DESC";
-    $params = array('userid1' => $user->id, 'notification' => 1);
-
-    $notifications =  $DB->get_records_sql($sql, $params, $limitfrom, $limitto);
-    return $notifications;
+    throw new coding_exception('message_get_recent_notifications() can not be used any more.', DEBUG_DEVELOPER);
 }
 
 /**
@@ -4801,162 +4782,10 @@ function message_history_link($userid1, $userid2, $return=false, $keywords='', $
 }
 
 /**
- * Search a user's messages
- *
- * Returns a list of posts found using an array of search terms
- * eg   word  +word -word
- *
  * @deprecated since Moodle 3.2
- * @param array $searchterms an array of search terms (strings)
- * @param bool $fromme include messages from the user?
- * @param bool $tome include messages to the user?
- * @param mixed $courseid SITEID for admins searching all messages. Other behaviour not yet implemented
- * @param int $userid the user ID of the current user
- * @return mixed An array of messages or false if no matching messages were found
  */
 function message_search($searchterms, $fromme=true, $tome=true, $courseid='none', $userid=0) {
-    debugging('message_search() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    global $CFG, $USER, $DB;
-
-    // If user is searching all messages check they are allowed to before doing anything else.
-    if ($courseid == SITEID && !has_capability('moodle/site:readallmessages', context_system::instance())) {
-        print_error('accessdenied','admin');
-    }
-
-    // If no userid sent then assume current user.
-    if ($userid == 0) $userid = $USER->id;
-
-    // Some differences in SQL syntax.
-    if ($DB->sql_regex_supported()) {
-        $REGEXP    = $DB->sql_regex(true);
-        $NOTREGEXP = $DB->sql_regex(false);
-    }
-
-    $searchcond = array();
-    $params = array();
-    $i = 0;
-
-    // Preprocess search terms to check whether we have at least 1 eligible search term.
-    // If we do we can drop words around it like 'a'.
-    $dropshortwords = false;
-    foreach ($searchterms as $searchterm) {
-        if (strlen($searchterm) >= 2) {
-            $dropshortwords = true;
-        }
-    }
-
-    foreach ($searchterms as $searchterm) {
-        $i++;
-
-        $NOT = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle.
-
-        if ($dropshortwords && strlen($searchterm) < 2) {
-            continue;
-        }
-        // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE search.
-        if (!$DB->sql_regex_supported()) {
-            if (substr($searchterm, 0, 1) == '-') {
-                $NOT = true;
-            }
-            $searchterm = trim($searchterm, '+-');
-        }
-
-        if (substr($searchterm,0,1) == "+") {
-            $searchterm = substr($searchterm,1);
-            $searchterm = preg_quote($searchterm, '|');
-            $searchcond[] = "m.fullmessage $REGEXP :ss$i";
-            $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)";
-
-        } else if (substr($searchterm,0,1) == "-") {
-            $searchterm = substr($searchterm,1);
-            $searchterm = preg_quote($searchterm, '|');
-            $searchcond[] = "m.fullmessage $NOTREGEXP :ss$i";
-            $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)";
-
-        } else {
-            $searchcond[] = $DB->sql_like("m.fullmessage", ":ss$i", false, true, $NOT);
-            $params['ss'.$i] = "%$searchterm%";
-        }
-    }
-
-    if (empty($searchcond)) {
-        $searchcond = " ".$DB->sql_like('m.fullmessage', ':ss1', false);
-        $params['ss1'] = "%";
-    } else {
-        $searchcond = implode(" AND ", $searchcond);
-    }
-
-    // There are several possibilities
-    // 1. courseid = SITEID : The admin is searching messages by all users
-    // 2. courseid = ??     : A teacher is searching messages by users in
-    //                        one of their courses - currently disabled
-    // 3. courseid = none   : User is searching their own messages;
-    //    a.  Messages from user
-    //    b.  Messages to user
-    //    c.  Messages to and from user
-
-    if ($fromme && $tome) {
-        $searchcond .= " AND ((useridto = :useridto AND timeusertodeleted = 0) OR
-            (useridfrom = :useridfrom AND timeuserfromdeleted = 0))";
-        $params['useridto'] = $userid;
-        $params['useridfrom'] = $userid;
-    } else if ($fromme) {
-        $searchcond .= " AND (useridfrom = :useridfrom AND timeuserfromdeleted = 0)";
-        $params['useridfrom'] = $userid;
-    } else if ($tome) {
-        $searchcond .= " AND (useridto = :useridto AND timeusertodeleted = 0)";
-        $params['useridto'] = $userid;
-    }
-    if ($courseid == SITEID) { // Admin is searching all messages.
-        $m_read   = $DB->get_records_sql("SELECT m.id, m.useridto, m.useridfrom, m.smallmessage, m.fullmessage, m.timecreated
-                                            FROM {message_read} m
-                                           WHERE $searchcond", $params, 0, MESSAGE_SEARCH_MAX_RESULTS);
-        $m_unread = $DB->get_records_sql("SELECT m.id, m.useridto, m.useridfrom, m.smallmessage, m.fullmessage, m.timecreated
-                                            FROM {message} m
-                                           WHERE $searchcond", $params, 0, MESSAGE_SEARCH_MAX_RESULTS);
-
-    } else if ($courseid !== 'none') {
-        // This has not been implemented due to security concerns.
-        $m_read   = array();
-        $m_unread = array();
-
-    } else {
-
-        if ($fromme and $tome) {
-            $searchcond .= " AND (m.useridfrom=:userid1 OR m.useridto=:userid2)";
-            $params['userid1'] = $userid;
-            $params['userid2'] = $userid;
-
-        } else if ($fromme) {
-            $searchcond .= " AND m.useridfrom=:userid";
-            $params['userid'] = $userid;
-
-        } else if ($tome) {
-            $searchcond .= " AND m.useridto=:userid";
-            $params['userid'] = $userid;
-        }
-
-        $m_read   = $DB->get_records_sql("SELECT m.id, m.useridto, m.useridfrom, m.smallmessage, m.fullmessage, m.timecreated
-                                            FROM {message_read} m
-                                           WHERE $searchcond", $params, 0, MESSAGE_SEARCH_MAX_RESULTS);
-        $m_unread = $DB->get_records_sql("SELECT m.id, m.useridto, m.useridfrom, m.smallmessage, m.fullmessage, m.timecreated
-                                            FROM {message} m
-                                           WHERE $searchcond", $params, 0, MESSAGE_SEARCH_MAX_RESULTS);
-
-    }
-
-    /// The keys may be duplicated in $m_read and $m_unread so we can't
-    /// do a simple concatenation
-    $messages = array();
-    foreach ($m_read as $m) {
-        $messages[] = $m;
-    }
-    foreach ($m_unread as $m) {
-        $messages[] = $m;
-    }
-
-    return (empty($messages)) ? false : $messages;
+    throw new coding_exception('message_search() can not be used any more.', DEBUG_DEVELOPER);
 }
 
 /**
@@ -5070,63 +4899,10 @@ function message_get_fragment($message, $keywords) {
 }
 
 /**
- * Retrieve the messages between two users
- *
  * @deprecated since Moodle 3.2
- * @param object $user1 the current user
- * @param object $user2 the other user
- * @param int $limitnum the maximum number of messages to retrieve
- * @param bool $viewingnewmessages are we currently viewing new messages?
  */
 function message_get_history($user1, $user2, $limitnum=0, $viewingnewmessages=false) {
-    debugging('message_get_history() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    global $DB, $CFG;
-
-    $messages = array();
-
-    //we want messages sorted oldest to newest but if getting a subset of messages we need to sort
-    //desc to get the last $limitnum messages then flip the order in php
-    $sort = 'asc';
-    if ($limitnum>0) {
-        $sort = 'desc';
-    }
-
-    $notificationswhere = null;
-    //we have just moved new messages to read. If theyre here to see new messages dont hide notifications
-    if (!$viewingnewmessages && $CFG->messaginghidereadnotifications) {
-        $notificationswhere = 'AND notification=0';
-    }
-
-    //prevent notifications of your own actions appearing in your own message history
-    $ownnotificationwhere = ' AND NOT (useridfrom=? AND notification=1)';
-
-    $sql = "((useridto = ? AND useridfrom = ? AND timeusertodeleted = 0) OR
-        (useridto = ? AND useridfrom = ? AND timeuserfromdeleted = 0))";
-    if ($messages_read = $DB->get_records_select('message_read', $sql . $notificationswhere . $ownnotificationwhere,
-        array($user1->id, $user2->id, $user2->id, $user1->id, $user1->id),
-        "timecreated $sort", '*', 0, $limitnum)) {
-        foreach ($messages_read as $message) {
-            $messages[] = $message;
-        }
-    }
-    if ($messages_new = $DB->get_records_select('message', $sql . $ownnotificationwhere,
-        array($user1->id, $user2->id, $user2->id, $user1->id, $user1->id),
-        "timecreated $sort", '*', 0, $limitnum)) {
-        foreach ($messages_new as $message) {
-            $messages[] = $message;
-        }
-    }
-
-    $result = core_collator::asort_objects_by_property($messages, 'timecreated', core_collator::SORT_NUMERIC);
-
-    //if we only want the last $limitnum messages
-    $messagecount = count($messages);
-    if ($limitnum > 0 && $messagecount > $limitnum) {
-        $messages = array_slice($messages, $messagecount - $limitnum, $limitnum, true);
-    }
-
-    return $messages;
+    throw new coding_exception('message_get_history() can not be used any more.', DEBUG_DEVELOPER);
 }
 
 /**
@@ -5197,9 +4973,9 @@ function message_get_contact_block_link($incontactlist, $isblocked, $contact, $s
  */
 function message_mark_messages_read($touserid, $fromuserid) {
     debugging('message_mark_messages_read() is deprecated and is no longer used, please use
-        \core_message\api::mark_all_read_for_user() instead.', DEBUG_DEVELOPER);
+        \core_message\api::mark_all_messages_as_read() instead.', DEBUG_DEVELOPER);
 
-    \core_message\api::mark_all_read_for_user($touserid, $fromuserid);
+    \core_message\api::mark_all_messages_as_read($touserid, $fromuserid);
 }
 
 /**
@@ -6035,144 +5811,11 @@ function prevent_form_autofill_password() {
 }
 
 /**
- * Get the users recent conversations meaning all the people they've recently
- * sent or received a message from plus the most recent message sent to or received from each other user
- *
  * @deprecated since Moodle 3.3 MDL-57370
- * @param object|int $userorid the current user or user id
- * @param int $limitfrom can be used for paging
- * @param int $limitto can be used for paging
- * @return array
  */
 function message_get_recent_conversations($userorid, $limitfrom = 0, $limitto = 100) {
-    global $DB;
-
-    debugging('message_get_recent_conversations() is deprecated. Please use \core_message\api::get_conversations() instead.', DEBUG_DEVELOPER);
-
-    if (is_object($userorid)) {
-        $user = $userorid;
-    } else {
-        $userid = $userorid;
-        $user = new stdClass();
-        $user->id = $userid;
-    }
-
-    $userfields = user_picture::fields('otheruser', array('lastaccess'));
-
-    // This query retrieves the most recent message received from or sent to
-    // seach other user.
-    //
-    // If two messages have the same timecreated, we take the one with the
-    // larger id.
-    //
-    // There is a separate query for read and unread messages as they are stored
-    // in different tables. They were originally retrieved in one query but it
-    // was so large that it was difficult to be confident in its correctness.
-    $uniquefield = $DB->sql_concat('message.useridfrom', "'-'", 'message.useridto');
-    $sql = "SELECT $uniquefield, $userfields,
-                   message.id as mid, message.notification, message.useridfrom, message.useridto,
-                   message.smallmessage, message.fullmessage, message.fullmessagehtml,
-                   message.fullmessageformat, message.timecreated,
-                   contact.id as contactlistid, contact.blocked
-              FROM {message_read} message
-              JOIN (
-                        SELECT MAX(id) AS messageid,
-                               matchedmessage.useridto,
-                               matchedmessage.useridfrom
-                         FROM {message_read} matchedmessage
-                   INNER JOIN (
-                               SELECT MAX(recentmessages.timecreated) timecreated,
-                                      recentmessages.useridfrom,
-                                      recentmessages.useridto
-                                 FROM {message_read} recentmessages
-                                WHERE (
-                                      (recentmessages.useridfrom = :userid1 AND recentmessages.timeuserfromdeleted = 0) OR
-                                      (recentmessages.useridto = :userid2   AND recentmessages.timeusertodeleted = 0)
-                                      )
-                             GROUP BY recentmessages.useridfrom, recentmessages.useridto
-                              ) recent ON matchedmessage.useridto     = recent.useridto
-                           AND matchedmessage.useridfrom   = recent.useridfrom
-                           AND matchedmessage.timecreated  = recent.timecreated
-                           WHERE (
-                                 (matchedmessage.useridfrom = :userid6 AND matchedmessage.timeuserfromdeleted = 0) OR
-                                 (matchedmessage.useridto = :userid7   AND matchedmessage.timeusertodeleted = 0)
-                                 )
-                      GROUP BY matchedmessage.useridto, matchedmessage.useridfrom
-                   ) messagesubset ON messagesubset.messageid = message.id
-              JOIN {user} otheruser ON (message.useridfrom = :userid4 AND message.useridto = otheruser.id)
-                OR (message.useridto   = :userid5 AND message.useridfrom   = otheruser.id)
-         LEFT JOIN {message_contacts} contact ON contact.userid  = :userid3 AND contact.contactid = otheruser.id
-             WHERE otheruser.deleted = 0 AND message.notification = 0
-          ORDER BY message.timecreated DESC";
-    $params = array(
-        'userid1' => $user->id,
-        'userid2' => $user->id,
-        'userid3' => $user->id,
-        'userid4' => $user->id,
-        'userid5' => $user->id,
-        'userid6' => $user->id,
-        'userid7' => $user->id
-    );
-    $read = $DB->get_records_sql($sql, $params, $limitfrom, $limitto);
-
-    // We want to get the messages that have not been read. These are stored in the 'message' table. It is the
-    // exact same query as the one above, except for the table we are querying. So, simply replace references to
-    // the 'message_read' table with the 'message' table.
-    $sql = str_replace('{message_read}', '{message}', $sql);
-    $unread = $DB->get_records_sql($sql, $params, $limitfrom, $limitto);
-
-    $unreadcountssql = 'SELECT useridfrom, count(*) as count
-                          FROM {message}
-                         WHERE useridto = :userid
-                           AND timeusertodeleted = 0
-                           AND notification = 0
-                      GROUP BY useridfrom';
-    $unreadcounts = $DB->get_records_sql($unreadcountssql, array('userid' => $user->id));
-
-    // Union the 2 result sets together looking for the message with the most
-    // recent timecreated for each other user.
-    // $conversation->id (the array key) is the other user's ID.
-    $conversations = array();
-    $conversation_arrays = array($unread, $read);
-    foreach ($conversation_arrays as $conversation_array) {
-        foreach ($conversation_array as $conversation) {
-            // Only consider it unread if $user has unread messages.
-            if (isset($unreadcounts[$conversation->useridfrom])) {
-                $conversation->isread = 0;
-                $conversation->unreadcount = $unreadcounts[$conversation->useridfrom]->count;
-            } else {
-                $conversation->isread = 1;
-            }
-
-            if (!isset($conversations[$conversation->id])) {
-                $conversations[$conversation->id] = $conversation;
-            } else {
-                $current = $conversations[$conversation->id];
-                // We need to maintain the isread and unreadcount values from existing
-                // parts of the conversation if we're replacing it.
-                $conversation->isread = ($conversation->isread && $current->isread);
-                if (isset($current->unreadcount) && !isset($conversation->unreadcount)) {
-                    $conversation->unreadcount = $current->unreadcount;
-                }
-
-                if ($current->timecreated < $conversation->timecreated) {
-                    $conversations[$conversation->id] = $conversation;
-                } else if ($current->timecreated == $conversation->timecreated) {
-                    if ($current->mid < $conversation->mid) {
-                        $conversations[$conversation->id] = $conversation;
-                    }
-                }
-            }
-        }
-    }
-
-    // Sort the conversations by $conversation->timecreated, newest to oldest
-    // There may be multiple conversations with the same timecreated
-    // The conversations array contains both read and unread messages (different tables) so sorting by ID won't work
-    $result = core_collator::asort_objects_by_property($conversations, 'timecreated', core_collator::SORT_NUMERIC);
-    $conversations = array_reverse($conversations);
-
-    return $conversations;
+    throw new coding_exception('message_get_recent_conversations() can not be used any more. ' .
+        'Please use \core_message\api::get_conversations() instead.', DEBUG_DEVELOPER);
 }
 
 /**
@@ -6577,3 +6220,185 @@ function question_is_only_toplevel_category_in_context($categoryid) {
 
     return question_is_only_child_of_top_category_in_context($categoryid);
 }
+
+/**
+ * Moves messages from a particular user from the message table (unread messages) to message_read
+ * This is typically only used when a user is deleted
+ *
+ * @param object $userid User id
+ * @return boolean success
+ * @deprecated since Moodle 3.5
+ */
+function message_move_userfrom_unread2read($userid) {
+    debugging('message_move_userfrom_unread2read() is deprecated and is no longer used.', DEBUG_DEVELOPER);
+
+    global $DB;
+
+    // Move all unread messages from message table to message_read.
+    if ($messages = $DB->get_records_select('message', 'useridfrom = ?', array($userid), 'timecreated')) {
+        foreach ($messages as $message) {
+            message_mark_message_read($message, 0); // Set timeread to 0 as the message was never read.
+        }
+    }
+    return true;
+}
+
+/**
+ * Retrieve users blocked by $user1
+ *
+ * @param object $user1 the user whose messages are being viewed
+ * @param object $user2 the user $user1 is talking to. If they are being blocked
+ *                      they will have a variable called 'isblocked' added to their user object
+ * @return array the users blocked by $user1
+ * @deprecated since Moodle 3.5
+ */
+function message_get_blocked_users($user1=null, $user2=null) {
+    debugging('message_get_blocked_users() is deprecated, please use \core_message\api::get_blocked_users() instead.',
+        DEBUG_DEVELOPER);
+
+    global $USER;
+
+    if (empty($user1)) {
+        $user1 = new stdClass();
+        $user1->id = $USER->id;
+    }
+
+    return \core_message\api::get_blocked_users($user1->id);
+}
+
+/**
+ * Retrieve $user1's contacts (online, offline and strangers)
+ *
+ * @param object $user1 the user whose messages are being viewed
+ * @param object $user2 the user $user1 is talking to. If they are a contact
+ *                      they will have a variable called 'iscontact' added to their user object
+ * @return array containing 3 arrays. array($onlinecontacts, $offlinecontacts, $strangers)
+ * @deprecated since Moodle 3.5
+ */
+function message_get_contacts($user1=null, $user2=null) {
+    debugging('message_get_contacts() is deprecated and is no longer used.', DEBUG_DEVELOPER);
+
+    global $DB, $CFG, $USER;
+
+    if (empty($user1)) {
+        $user1 = $USER;
+    }
+
+    if (!empty($user2)) {
+        $user2->iscontact = false;
+    }
+
+    $timetoshowusers = 300; // Seconds default.
+    if (isset($CFG->block_online_users_timetosee)) {
+        $timetoshowusers = $CFG->block_online_users_timetosee * 60;
+    }
+
+    // Rime which a user is counting as being active since.
+    $timefrom = time() - $timetoshowusers;
+
+    // People in our contactlist who are online.
+    $onlinecontacts  = array();
+    // People in our contactlist who are offline.
+    $offlinecontacts = array();
+    // People who are not in our contactlist but have sent us a message.
+    $strangers       = array();
+
+    // Get all in our contact list who are not blocked in our and count messages we have waiting from each of them.
+    $rs = \core_message\api::get_contacts_with_unread_message_count($user1->id);
+    foreach ($rs as $rd) {
+        if ($rd->lastaccess >= $timefrom) {
+            // They have been active recently, so are counted online.
+            $onlinecontacts[] = $rd;
+
+        } else {
+            $offlinecontacts[] = $rd;
+        }
+
+        if (!empty($user2) && $user2->id == $rd->id) {
+            $user2->iscontact = true;
+        }
+    }
+
+    // Get messages from anyone who isn't in our contact list and count the number of messages we have from each of them.
+    $rs = \core_message\api::get_non_contacts_with_unread_message_count($user1->id);
+    // Add user id as array index, so supportuser and noreply user don't get duplicated (if they are real users).
+    foreach ($rs as $rd) {
+        $strangers[$rd->id] = $rd;
+    }
+
+    // Add noreply user and support user to the list, if they don't exist.
+    $supportuser = core_user::get_support_user();
+    if (!isset($strangers[$supportuser->id]) && !$supportuser->deleted) {
+        $supportuser->messagecount = message_count_unread_messages($USER, $supportuser);
+        if ($supportuser->messagecount > 0) {
+            $strangers[$supportuser->id] = $supportuser;
+        }
+    }
+
+    $noreplyuser = core_user::get_noreply_user();
+    if (!isset($strangers[$noreplyuser->id]) && !$noreplyuser->deleted) {
+        $noreplyuser->messagecount = message_count_unread_messages($USER, $noreplyuser);
+        if ($noreplyuser->messagecount > 0) {
+            $strangers[$noreplyuser->id] = $noreplyuser;
+        }
+    }
+
+    return array($onlinecontacts, $offlinecontacts, $strangers);
+}
+
+/**
+ * Mark a single message as read
+ *
+ * @param stdClass $message An object with an object property ie $message->id which is an id in the message table
+ * @param int $timeread the timestamp for when the message should be marked read. Usually time().
+ * @param bool $messageworkingempty Is the message_working table already confirmed empty for this message?
+ * @return int the ID of the message in the messags table
+ * @deprecated since Moodle 3.5
+ */
+function message_mark_message_read($message, $timeread, $messageworkingempty=false) {
+    debugging('message_mark_message_read() is deprecated, please use \core_message\api::mark_message_as_read()
+        or \core_message\api::mark_notification_as_read().', DEBUG_DEVELOPER);
+
+    if (!empty($message->notification)) {
+        \core_message\api::mark_notification_as_read($message, $timeread);
+    } else {
+        \core_message\api::mark_message_as_read($message->useridto, $message, $timeread);
+    }
+
+    return $message->id;
+}
+
+
+/**
+ * Checks if a user can delete a message.
+ *
+ * @param stdClass $message the message to delete
+ * @param string $userid the user id of who we want to delete the message for (this may be done by the admin
+ *  but will still seem as if it was by the user)
+ * @return bool Returns true if a user can delete the message, false otherwise.
+ * @deprecated since Moodle 3.5
+ */
+function message_can_delete_message($message, $userid) {
+    debugging('message_can_delete_message() is deprecated, please use \core_message\api::can_delete_message() instead.',
+        DEBUG_DEVELOPER);
+
+    return \core_message\api::can_delete_message($userid, $message->id);
+}
+
+/**
+ * Deletes a message.
+ *
+ * This function does not verify any permissions.
+ *
+ * @param stdClass $message the message to delete
+ * @param string $userid the user id of who we want to delete the message for (this may be done by the admin
+ *  but will still seem as if it was by the user)
+ * @return bool
+ * @deprecated since Moodle 3.5
+ */
+function message_delete_message($message, $userid) {
+    debugging('message_delete_message() is deprecated, please use \core_message\api::delete_message() instead.',
+        DEBUG_DEVELOPER);
+
+    return \core_message\api::delete_message($userid, $message->id);
+}
index 48debee..c333222 100644 (file)
@@ -107,4 +107,18 @@ class mariadb_native_moodle_database extends mysqli_native_moodle_database {
         }
         return true;
     }
+
+    /**
+     * Does this mariadb instance support fulltext indexes?
+     *
+     * @return bool
+     */
+    public function is_fulltext_search_supported() {
+        $info = $this->get_server_info();
+
+        if (version_compare($info['version'], '10.0.5', '>=')) {
+            return true;
+        }
+        return false;
+    }
 }
index a16b933..77d0b98 100644 (file)
@@ -2687,4 +2687,14 @@ abstract class moodle_database {
     public function perf_get_queries_time() {
         return $this->queriestime;
     }
+
+    /**
+     * Whether the database is able to support full-text search or not.
+     *
+     * @return bool
+     */
+    public function is_fulltext_search_supported() {
+        // No support unless specified.
+        return false;
+    }
 }
index 05c6c43..015d3a6 100644 (file)
@@ -1989,4 +1989,18 @@ class mysqli_native_moodle_database extends moodle_database {
             $this->change_database_structure("ALTER TABLE {$prefix}$tablename $rowformat");
         }
     }
+
+    /**
+     * Does this mysql instance support fulltext indexes?
+     *
+     * @return bool
+     */
+    public function is_fulltext_search_supported() {
+        $info = $this->get_server_info();
+
+        if (version_compare($info['version'], '5.6.4', '>=')) {
+            return true;
+        }
+        return false;
+    }
 }
index ae75501..97d9490 100644 (file)
@@ -1497,4 +1497,13 @@ class pgsql_native_moodle_database extends moodle_database {
     private function trim_quotes($str) {
         return trim(trim($str), "'\"");
     }
+
+    /**
+     * Postgresql supports full-text search indexes.
+     *
+     * @return bool
+     */
+    public function is_fulltext_search_supported() {
+        return true;
+    }
 }
index d1d27f1..9174f70 100644 (file)
@@ -1586,4 +1586,26 @@ class sqlsrv_native_moodle_database extends moodle_database {
         $result = sqlsrv_rollback($this->sqlsrv);
         $this->query_end($result);
     }
+
+    /**
+     * Is fulltext search enabled?.
+     *
+     * @return bool
+     */
+    public function is_fulltext_search_supported() {
+        global $CFG;
+
+        $sql = "SELECT FULLTEXTSERVICEPROPERTY('IsFullTextInstalled')";
+        $this->query_start($sql, null, SQL_QUERY_AUX);
+        $result = sqlsrv_query($this->sqlsrv, $sql);
+        $this->query_end($result);
+        if ($result) {
+            if ($row = sqlsrv_fetch_array($result)) {
+                $property = (bool)reset($row);
+            }
+        }
+        $this->free_result($result);
+
+        return !empty($property);
+    }
 }
index a7d073c..c8db3c5 100644 (file)
Binary files a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js and b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js differ
index 015f2f1..5b9e915 100644 (file)
Binary files a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js and b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js differ
index a7d073c..c8db3c5 100644 (file)
Binary files a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js and b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js differ
index 8d3705b..f33bc87 100644 (file)
@@ -315,7 +315,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
      * @return {boolean} whether or not the parameter node exists within the editor.
      */
     _stopAtContentEditableFilter: function(node) {
-        this.editor.contains(node);
+        return this.editor.contains(node);
     },
 
     /**
@@ -409,12 +409,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
     _getSuitableTableCell: function() {
         var targetcell = null,
             host = this.get('host');
+        var stopAtContentEditableFilter = Y.bind(this._stopAtContentEditableFilter, this);
 
         host.getSelectedNodes().some(function(node) {
-            if (node.ancestor('td, th, caption', true, this._stopAtContentEditableFilter)) {
+            if (node.ancestor('td, th, caption', true, stopAtContentEditableFilter)) {
                 targetcell = node;
 
-                var caption = node.ancestor('caption', true, this._stopAtContentEditableFilter);
+                var caption = node.ancestor('caption', true, stopAtContentEditableFilter);
                 if (caption) {
                     var table = caption.get('parentNode');
                     if (table) {
index d575cbc..0b5d300 100644 (file)
@@ -127,33 +127,53 @@ function message_send($eventdata) {
         $userstate = 'loggedoff';
     }
 
-    // Create the message object
-    $savemessage = new stdClass();
-    $savemessage->courseid          = $eventdata->courseid;
-    $savemessage->useridfrom        = $eventdata->userfrom->id;
-    $savemessage->useridto          = $eventdata->userto->id;
-    $savemessage->subject           = $eventdata->subject;
-    $savemessage->fullmessage       = $eventdata->fullmessage;
-    $savemessage->fullmessageformat = $eventdata->fullmessageformat;
-    $savemessage->fullmessagehtml   = $eventdata->fullmessagehtml;
-    $savemessage->smallmessage      = $eventdata->smallmessage;
-    $savemessage->notification      = $eventdata->notification;
-    $savemessage->eventtype         = $eventdata->name;
-    $savemessage->component         = $eventdata->component;
-
-    if (!empty($eventdata->contexturl)) {
-        $savemessage->contexturl = (string)$eventdata->contexturl;
-    } else {
-        $savemessage->contexturl = null;
-    }
+    // Check if we are creating a notification or message.
+    if ($eventdata->notification) {
+        $table = 'notifications';
+
+        $tabledata = new stdClass();
+        $tabledata->useridfrom = $eventdata->userfrom->id;
+        $tabledata->useridto = $eventdata->userto->id;
+        $tabledata->subject = $eventdata->subject;
+        $tabledata->fullmessage = $eventdata->fullmessage;
+        $tabledata->fullmessageformat = $eventdata->fullmessageformat;
+        $tabledata->fullmessagehtml = $eventdata->fullmessagehtml;
+        $tabledata->smallmessage = $eventdata->smallmessage;
+        $tabledata->eventtype = $eventdata->name;
+        $tabledata->component = $eventdata->component;
+
+        if (!empty($eventdata->contexturl)) {
+            $tabledata->contexturl = (string)$eventdata->contexturl;
+        } else {
+            $tabledata->contexturl = null;
+        }
 
-    if (!empty($eventdata->contexturlname)) {
-        $savemessage->contexturlname = (string)$eventdata->contexturlname;
+        if (!empty($eventdata->contexturlname)) {
+            $tabledata->contexturlname = (string)$eventdata->contexturlname;
+        } else {
+            $tabledata->contexturlname = null;
+        }
     } else {
-        $savemessage->contexturlname = null;
+        $table = 'messages';
+
+        if (!$conversationid = \core_message\api::get_conversation_between_users([$eventdata->userfrom->id,
+                $eventdata->userto->id])) {
+            $conversationid = \core_message\api::create_conversation_between_users([$eventdata->userfrom->id,
+                $eventdata->userto->id]);
+        }
+
+        $tabledata = new stdClass();
+        $tabledata->courseid = $eventdata->courseid;
+        $tabledata->useridfrom = $eventdata->userfrom->id;
+        $tabledata->conversationid = $conversationid;
+        $tabledata->subject = $eventdata->subject;
+        $tabledata->fullmessage = $eventdata->fullmessage;
+        $tabledata->fullmessageformat = $eventdata->fullmessageformat;
+        $tabledata->fullmessagehtml = $eventdata->fullmessagehtml;
+        $tabledata->smallmessage = $eventdata->smallmessage;
     }
 
-    $savemessage->timecreated = time();
+    $tabledata->timecreated = time();
 
     if (PHPUNIT_TEST and class_exists('phpunit_util')) {
         // Add some more tests to make sure the normal code can actually work.
@@ -173,9 +193,21 @@ function message_send($eventdata) {
         unset($messageproviders);
         // Now ask phpunit if it wants to catch this message.
         if (phpunit_util::is_redirecting_messages()) {
-            $savemessage->timeread = time();
-            $messageid = $DB->insert_record('message_read', $savemessage);
-            $message = $DB->get_record('message_read', array('id'=>$messageid));
+            $messageid = $DB->insert_record($table, $tabledata);
+            $message = $DB->get_record($table, array('id' => $messageid));
+
+            // Add the useridto attribute for BC.
+            $message->useridto = $eventdata->userto->id;
+
+            // Mark the message/notification as read.
+            if ($eventdata->notification) {
+                \core_message\api::mark_notification_as_read($message);
+            } else {
+                \core_message\api::mark_message_as_read($eventdata->userto->id, $message);
+            }
+
+            // Unit tests need this detail.
+            $message->notification = $eventdata->notification;
             phpunit_util::message_sent($message);
             return $messageid;
         }
@@ -183,7 +215,7 @@ function message_send($eventdata) {
 
     // Fetch enabled processors.
     // If we are dealing with a message some processors may want to handle it regardless of user and site settings.
-    if (empty($savemessage->notification)) {
+    if (!$eventdata->notification) {
         $processors = array_filter(get_message_processors(false), function($processor) {
             if ($processor->object->force_process_messages()) {
                 return true;
@@ -226,7 +258,7 @@ function message_send($eventdata) {
         }
 
         // Populate the list of processors we will be using
-        if (empty($savemessage->notification) && $processor->object->force_process_messages()) {
+        if (!$eventdata->notification && $processor->object->force_process_messages()) {
             $processorlist[] = $processor->name;
         } else if ($permitted == 'forced' && $userisconfigured) {
             // An admin is forcing users to use this message processor. Use this processor unconditionally.
@@ -248,20 +280,20 @@ function message_send($eventdata) {
     }
 
     // Only cache messages, not notifications.
-    if (empty($savemessage->notification)) {
+    if (!$eventdata->notification) {
         // Cache the timecreated value of the last message between these two users.
         $cache = cache::make('core', 'message_time_last_message_between_users');
-        $key = \core_message\helper::get_last_message_time_created_cache_key($savemessage->useridfrom,
-            $savemessage->useridto);
-        $cache->set($key, $savemessage->timecreated);
+        $key = \core_message\helper::get_last_message_time_created_cache_key($eventdata->userfrom->id,
+            $eventdata->userto->id);
+        $cache->set($key, $tabledata->timecreated);
     }
 
     // Store unread message just in case we get a fatal error any time later.
-    $savemessage->id = $DB->insert_record('message', $savemessage);
-    $eventdata->savedmessageid = $savemessage->id;
+    $tabledata->id = $DB->insert_record($table, $tabledata);
+    $eventdata->savedmessageid = $tabledata->id;
 
     // Let the manager do the sending or buffering when db transaction in progress.
-    return \core\message\manager::send_message($eventdata, $savemessage, $processorlist);
+    return \core\message\manager::send_message($eventdata, $tabledata, $processorlist);
 }
 
 
index a8259ae..978720a 100644 (file)
@@ -4071,9 +4071,6 @@ function delete_user(stdClass $user) {
     // Delete all grades - backup is kept in grade_grades_history table.
     grade_user_delete($user->id);
 
-    // Move unread messages from this user to read.
-    message_move_userfrom_unread2read($user->id);
-
     // TODO: remove from cohorts using standard API here.
 
     // Remove user tags.
index ffbf83e..383780c 100644 (file)
@@ -33,7 +33,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class phpunit_message_sink {
-    /** @var array of records from message_read table */
+    /** @var array of records from messages table */
     protected $messages = array();
 
     /**
@@ -48,7 +48,7 @@ class phpunit_message_sink {
     /**
      * To be called from phpunit_util only!
      *
-     * @param stdClass $message record from message_read table
+     * @param stdClass $message record from messages table
      */
     public function add_message($message) {
         /* Number messages from 0. */
@@ -58,7 +58,7 @@ class phpunit_message_sink {
     /**
      * Returns all redirected messages.
      *
-     * The instances are records form the message_read table.
+     * The instances are records from the messages table.
      * The array indexes are numbered from 0 and the order is matching
      * the creation of events.
      *
index bb00478..df0eb01 100644 (file)
@@ -50,7 +50,7 @@ class phpunit_phpmailer_sink {
     /**
      * To be called from phpunit_util only!
      *
-     * @param stdClass $message record from message_read table
+     * @param stdClass $message record from messages table
      */
     public function add_message($message) {
         /* Number messages from 0. */
@@ -60,7 +60,7 @@ class phpunit_phpmailer_sink {
     /**
      * Returns all redirected messages.
      *
-     * The instances are records form the message_read table.
+     * The instances are records from the messages table.
      * The array indexes are numbered from 0 and the order is matching
      * the creation of events.
      *
index d5cfd94..3252bf6 100644 (file)
@@ -714,7 +714,7 @@ class phpunit_util extends testing_util {
     /**
      * To be called from messagelib.php only!
      *
-     * @param stdClass $message record from message_read table
+     * @param stdClass $message record from messages table
      * @return bool true means send message, false means message "sent" to sink.
      */
     public static function message_sent($message) {
@@ -765,7 +765,7 @@ class phpunit_util extends testing_util {
     /**
      * To be called from messagelib.php only!
      *
-     * @param stdClass $message record from message_read table
+     * @param stdClass $message record from messages table
      * @return bool true means send message, false means message "sent" to sink.
      */
     public static function phpmailer_sent($message) {
index 42c4cad..3f1d3de 100644 (file)
@@ -167,7 +167,7 @@ class core_message_testcase extends advanced_testcase {
         $emails = $sink->get_messages();
         $this->assertCount(1, $emails);
         $email = reset($emails);
-        $recordexists = $DB->record_exists('message', array('id' => $messageid));
+        $recordexists = $DB->record_exists('messages', array('id' => $messageid));
         $this->assertSame(true, $recordexists);
         $this->assertSame($user1->email, $email->from);
         $this->assertSame($user2->email, $email->to);
@@ -207,7 +207,7 @@ class core_message_testcase extends advanced_testcase {
         $emails = $sink->get_messages();
         $this->assertCount(1, $emails);
         $email = reset($emails);
-        $recordexists = $DB->record_exists('message', array('id' => $messageid));
+        $recordexists = $DB->record_exists('messages', array('id' => $messageid));
         $this->assertSame(true, $recordexists);
         $this->assertSame($user1->email, $email->from);
         $this->assertSame($user2->email, $email->to);
index 051f61c..ed6e8ab 100644 (file)
@@ -218,14 +218,15 @@ class core_messagelib_testcase extends advanced_testcase {
         $this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
         $this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
         $this->assertEquals($message->notification, $savedmessage->notification);
-        $this->assertNull($savedmessage->contexturl);
-        $this->assertNull($savedmessage->contexturlname);
         $this->assertTimeCurrent($savedmessage->timecreated);
-        $record = $DB->get_record('message_read', array('id' => $savedmessage->id), '*', MUST_EXIST);
+        $record = $DB->get_record('messages', array('id' => $savedmessage->id), '*', MUST_EXIST);
+        unset($savedmessage->useridto);
+        unset($savedmessage->notification);
         $this->assertEquals($record, $savedmessage);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message', array()));
-        $DB->delete_records('message_read', array());
+        $this->assertTrue($DB->record_exists('message_user_actions', array('userid' => $user2->id, 'messageid' => $messageid,
+            'action' => \core_message\api::MESSAGE_ACTION_READ)));
+        $DB->delete_records('messages', array());
 
         $message = new \core\message\message();
         $message->courseid = 1;
@@ -239,8 +240,7 @@ class core_messagelib_testcase extends advanced_testcase {
         $message->fullmessagehtml = '<p>message body</p>';
         $message->smallmessage = 'small message';
         $message->notification = '0';
-        $message->contexturl = new moodle_url('/');
-        $message->contexturlname = 'front';
+
         $sink = $this->redirectMessages();
         $messageid = message_send($message);
         $savedmessages = $sink->get_messages();
@@ -255,14 +255,15 @@ class core_messagelib_testcase extends advanced_testcase {
         $this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
         $this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
         $this->assertEquals($message->notification, $savedmessage->notification);
-        $this->assertEquals($message->contexturl->out(), $savedmessage->contexturl);
-        $this->assertEquals($message->contexturlname, $savedmessage->contexturlname);
         $this->assertTimeCurrent($savedmessage->timecreated);
-        $record = $DB->get_record('message_read', array('id' => $savedmessage->id), '*', MUST_EXIST);
+        $record = $DB->get_record('messages', array('id' => $savedmessage->id), '*', MUST_EXIST);
+        unset($savedmessage->useridto);
+        unset($savedmessage->notification);
         $this->assertEquals($record, $savedmessage);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message', array()));
-        $DB->delete_records('message_read', array());
+        $this->assertTrue($DB->record_exists('message_user_actions', array('userid' => $user2->id, 'messageid' => $messageid,
+            'action' => \core_message\api::MESSAGE_ACTION_READ)));
+        $DB->delete_records('messages', array());
 
         // Test phpunit problem detection.
 
@@ -297,8 +298,7 @@ class core_messagelib_testcase extends advanced_testcase {
         }
         $this->assertCount(0, $sink->get_messages());
         $sink->close();
-        $this->assertFalse($DB->record_exists('message', array()));
-        $this->assertFalse($DB->record_exists('message_read', array()));
+        $this->assertFalse($DB->record_exists('messages', array()));
 
         // Invalid users.
 
@@ -420,10 +420,11 @@ class core_messagelib_testcase extends advanced_testcase {
         $messageid = message_send($message);
         $emails = $sink->get_messages();
         $this->assertCount(0, $emails);
-        $savedmessage = $DB->get_record('message', array('id' => $messageid), '*', MUST_EXIST);
+        $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message_read', array()));
-        $DB->delete_records('message', array());
+        $this->assertFalse($DB->record_exists('message_user_actions', array()));
+        $DB->delete_records('messages', array());
+        $DB->delete_records('message_user_actions', array());
         $events = $eventsink->get_events();
         $this->assertCount(1, $events);
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
@@ -447,10 +448,12 @@ class core_messagelib_testcase extends advanced_testcase {
         $messageid = message_send($message);
         $emails = $sink->get_messages();
         $this->assertCount(0, $emails);
-        $savedmessage = $DB->get_record('message_read', array('id' => $messageid), '*', MUST_EXIST);
+        $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message', array()));
-        $DB->delete_records('message_read', array());
+        $this->assertTrue($DB->record_exists('message_user_actions', array('userid' => $user2->id, 'messageid' => $messageid,
+            'action' => \core_message\api::MESSAGE_ACTION_READ)));
+        $DB->delete_records('messages', array());
+        $DB->delete_records('message_user_actions', array());
         $events = $eventsink->get_events();
         $this->assertCount(2, $events);
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
@@ -475,14 +478,14 @@ class core_messagelib_testcase extends advanced_testcase {
         $messageid = message_send($message);
         $emails = $sink->get_messages();
         $this->assertCount(0, $emails);
-        $savedmessage = $DB->get_record('message_read', array('id' => $messageid), '*', MUST_EXIST);
+        $savedmessage = $DB->get_record('notifications', array('id' => $messageid), '*', MUST_EXIST);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message', array()));
-        $DB->delete_records('message_read', array());
+        $this->assertFalse($DB->record_exists('messages', array()));
+        $DB->delete_records('notifications', array());
         $events = $eventsink->get_events();
         $this->assertCount(2, $events);
-        $this->assertInstanceOf('\core\event\message_sent', $events[0]);
-        $this->assertInstanceOf('\core\event\message_viewed', $events[1]);
+        $this->assertInstanceOf('\core\event\notification_sent', $events[0]);
+        $this->assertInstanceOf('\core\event\notification_viewed', $events[1]);
         $eventsink->clear();
 
         // Will always use the pop-up processor.
@@ -507,10 +510,11 @@ class core_messagelib_testcase extends advanced_testcase {
         $messageid = message_send($message);
         $emails = $sink->get_messages();
         $this->assertCount(0, $emails);
-        $savedmessage = $DB->get_record('message', array('id' => $messageid), '*', MUST_EXIST);
+        $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message_read', array()));
-        $DB->delete_records('message', array());
+        $this->assertFalse($DB->record_exists('message_user_actions', array()));
+        $DB->delete_records('messages', array());
+        $DB->delete_records('message_user_actions', array());
         $events = $eventsink->get_events();
         $this->assertCount(1, $events);
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
@@ -537,15 +541,15 @@ class core_messagelib_testcase extends advanced_testcase {
         $emails = $sink->get_messages();
         $this->assertCount(1, $emails);
         $email = reset($emails);
-        $savedmessage = $DB->get_record('message', array('id' => $messageid), '*', MUST_EXIST);
+        $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
         $this->assertSame($user1->email, $email->from);
         $this->assertSame($user2->email, $email->to);
         $this->assertSame($message->subject, $email->subject);
         $this->assertNotEmpty($email->header);
         $this->assertNotEmpty($email->body);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message_read', array()));
-        $DB->delete_records('message_read', array());
+        $this->assertFalse($DB->record_exists('message_user_actions', array()));
+        $DB->delete_records('message_user_actions', array());
         $events = $eventsink->get_events();
         $this->assertCount(1, $events);
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
@@ -570,16 +574,16 @@ class core_messagelib_testcase extends advanced_testcase {
         $emails = $sink->get_messages();
         $this->assertCount(1, $emails);
         $email = reset($emails);
-        $savedmessage = $DB->get_record('message', array('id' => $messageid), '*', MUST_EXIST);
-        $working = $DB->get_record('message_working', array('unreadmessageid' => $messageid), '*', MUST_EXIST);
+        $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
         $this->assertSame($user1->email, $email->from);
         $this->assertSame($user2->email, $email->to);
         $this->assertSame($message->subject, $email->subject);
         $this->assertNotEmpty($email->header);
         $this->assertNotEmpty($email->body);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message_read', array()));
-        $DB->delete_records('message', array());
+        $this->assertFalse($DB->record_exists('message_user_actions', array()));
+        $DB->delete_records('messages', array());
+        $DB->delete_records('message_user_actions', array());
         $events = $eventsink->get_events();
         $this->assertCount(1, $events);
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
@@ -603,11 +607,10 @@ class core_messagelib_testcase extends advanced_testcase {
         $messageid = message_send($message);
         $emails = $sink->get_messages();
         $this->assertCount(0, $emails);
-        $savedmessage = $DB->get_record('message', array('id' => $messageid), '*', MUST_EXIST);
-        $working = $DB->get_record('message_working', array('unreadmessageid' => $messageid), '*', MUST_EXIST);
+        $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message_read', array()));
-        $DB->delete_records('message', array());
+        $this->assertFalse($DB->record_exists('message_user_actions', array()));
+        $DB->delete_records('messages', array());
         $events = $eventsink->get_events();
         $this->assertCount(1, $events);
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
@@ -641,10 +644,10 @@ class core_messagelib_testcase extends advanced_testcase {
         $messageid = message_send($message);
         $emails = $sink->get_messages();
         $this->assertCount(0, $emails);
-        $savedmessage = $DB->get_record('message', array('id' => $messageid), '*', MUST_EXIST);
+        $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message_read', array()));
-        $DB->delete_records('message', array());
+        $this->assertFalse($DB->record_exists('message_user_actions', array()));
+        $DB->delete_records('messages', array());
         $events = $eventsink->get_events();
         $this->assertCount(0, $events);
         $eventsink->clear();
@@ -674,9 +677,9 @@ class core_messagelib_testcase extends advanced_testcase {
         $messageid = message_send($message);
         $emails = $sink->get_messages();
         $this->assertCount(0, $emails);
-        $savedmessage = $DB->get_record('message', array('id' => $messageid), '*', MUST_EXIST);
+        $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
         $sink->clear();
-        $this->assertFalse($DB->record_exists('message_read', array()));
+        $this->assertFalse($DB->record_exists('message_user_actions', array()));
         $events = $eventsink->get_events();
         $this->assertCount(1, $events);
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
@@ -689,8 +692,8 @@ class core_messagelib_testcase extends advanced_testcase {
         $transaction = $DB->start_delegated_transaction();
         message_send($message);
         message_send($message);
-        $this->assertCount(3, $DB->get_records('message'));
-        $this->assertFalse($DB->record_exists('message_read', array()));
+        $this->assertCount(3, $DB->get_records('messages'));
+        $this->assertFalse($DB->record_exists('message_user_actions', array()));
         $events = $eventsink->get_events();
         $this->assertCount(0, $events);
         $transaction->allow_commit();
@@ -699,14 +702,13 @@ class core_messagelib_testcase extends advanced_testcase {
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
         $this->assertInstanceOf('\core\event\message_sent', $events[1]);
         $eventsink->clear();
-        $DB->delete_records('message', array());
-        $DB->delete_records('message_read', array());
+        $DB->delete_records('messages', array());
 
         $transaction = $DB->start_delegated_transaction();
         message_send($message);
         message_send($message);
-        $this->assertCount(2, $DB->get_records('message'));
-        $this->assertCount(0, $DB->get_records('message_read'));
+        $this->assertCount(2, $DB->get_records('messages'));
+        $this->assertCount(0, $DB->get_records('message_user_actions'));
         $events = $eventsink->get_events();
         $this->assertCount(0, $events);
         try {
@@ -716,16 +718,14 @@ class core_messagelib_testcase extends advanced_testcase {
         }
         $events = $eventsink->get_events();
         $this->assertCount(0, $events);
-        $this->assertCount(0, $DB->get_records('message'));
-        $this->assertCount(0, $DB->get_records('message_read'));
+        $this->assertCount(0, $DB->get_records('messages'));
         message_send($message);
-        $this->assertCount(1, $DB->get_records('message'));
-        $this->assertCount(0, $DB->get_records('message_read'));
+        $this->assertCount(1, $DB->get_records('messages'));
+        $this->assertCount(0, $DB->get_records('message_user_actions'));
         $events = $eventsink->get_events();
         $this->assertCount(1, $events);
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
         $sink->clear();
-        $DB->delete_records('message_read', array());
     }
 
     public function test_rollback() {
index 174efd9..c569cf6 100644 (file)
@@ -22,6 +22,8 @@ information provided here is intended especially for developers.
 * Scripts can define a constant NO_SITEPOLICY_CHECK and set it to true before requiring the main config.php file. It
   will make the require_login() skipping the test for the user's policyagreed status. This is useful for plugins that
   act as a site policy handler.
+* There is a new is_fulltext_search_supported() DML function. The default implementation returns false. This function
+  is used by 'Simple search' global search engine to determine if the database full-text search capabilities can be used.
 
 === 3.4 ===
 
index 2065529..25246a0 100644 (file)
@@ -36,6 +36,16 @@ require_once($CFG->dirroot . '/lib/messagelib.php');
  */
 class api {
 
+    /**
+     * The action for reading a message.
+     */
+    const MESSAGE_ACTION_READ = 1;
+
+    /**
+     * The action for deleting a message.
+     */
+    const MESSAGE_ACTION_DELETED = 2;
+
     /**
      * Handles searching for messages in the message area.
      *
@@ -52,47 +62,33 @@ class api {
         $ufields = \user_picture::fields('u', array('lastaccess'), 'userfrom_id', 'userfrom_');
         $ufields2 = \user_picture::fields('u2', array('lastaccess'), 'userto_id', 'userto_');
 
-        // Get all the messages for the user.
-        $sql = "SELECT m.id, m.useridfrom, m.useridto, m.subject, m.fullmessage, m.fullmessagehtml, m.fullmessageformat,
-                       m.smallmessage, m.notification, m.timecreated, 0 as isread, $ufields, mc.blocked as userfrom_blocked,
-                       $ufields2, mc2.blocked as userto_blocked
-                  FROM {message} m
-                  JOIN {user} u
-                    ON m.useridfrom = u.id
-             LEFT JOIN {message_contacts} mc
-                    ON (mc.contactid = u.id AND mc.userid = ?)
-                  JOIN {user} u2
-                    ON m.useridto = u2.id
-             LEFT JOIN {message_contacts} mc2
-                    ON (mc2.contactid = u2.id AND mc2.userid = ?)
-                 WHERE ((useridto = ? AND timeusertodeleted = 0)
-                    OR (useridfrom = ? AND timeuserfromdeleted = 0))
-                   AND notification = 0
-                   AND u.deleted = 0
-                   AND u2.deleted = 0
-                   AND " . $DB->sql_like('smallmessage', '?', false) . "
-             UNION ALL
-                SELECT mr.id, mr.useridfrom, mr.useridto, mr.subject, mr.fullmessage, mr.fullmessagehtml, mr.fullmessageformat,
-                       mr.smallmessage, mr.notification, mr.timecreated, 1 as isread, $ufields, mc.blocked as userfrom_blocked,
-                       $ufields2, mc2.blocked as userto_blocked
-                  FROM {message_read} mr
-                  JOIN {user} u
-                    ON mr.useridfrom = u.id
-             LEFT JOIN {message_contacts} mc
-                    ON (mc.contactid = u.id AND mc.userid = ?)
-                  JOIN {user} u2
-                    ON mr.useridto = u2.id
-             LEFT JOIN {message_contacts} mc2
-                    ON (mc2.contactid = u2.id AND mc2.userid = ?)
-                 WHERE ((useridto = ? AND timeusertodeleted = 0)
-                    OR (useridfrom = ? AND timeuserfromdeleted = 0))
-                   AND notification = 0
+        $sql = "SELECT m.id, m.useridfrom, mcm.userid as useridto, m.subject, m.fullmessage, m.fullmessagehtml, m.fullmessageformat,
+                       m.smallmessage, m.timecreated, 0 as isread, $ufields, mcont.blocked as userfrom_blocked, $ufields2,
+                       mcont2.blocked as userto_blocked
+                  FROM {messages} m
+            INNER JOIN {user} u
+                    ON u.id = m.useridfrom
+            INNER JOIN {message_conversations} mc
+                    ON mc.id = m.conversationid
+            INNER JOIN {message_conversation_members} mcm
+                    ON mcm.conversationid = m.conversationid
+            INNER JOIN {user} u2
+                    ON u2.id = mcm.userid
+             LEFT JOIN {message_contacts} mcont
+                    ON (mcont.contactid = u.id AND mcont.userid = ?)
+             LEFT JOIN {message_contacts} mcont2
+                    ON (mcont2.contactid = u2.id AND mcont2.userid = ?)
+             LEFT JOIN {message_user_actions} mua
+                    ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?)
+                 WHERE (m.useridfrom = ? OR mcm.userid = ?)
+                   AND m.useridfrom != mcm.userid
                    AND u.deleted = 0
                    AND u2.deleted = 0
+                   AND mua.id is NULL
                    AND " . $DB->sql_like('smallmessage', '?', false) . "
               ORDER BY timecreated DESC";
-        $params = array($userid, $userid, $userid, $userid, '%' . $search . '%',
-                        $userid, $userid, $userid, $userid, '%' . $search . '%');
+
+        $params = array($userid, $userid, $userid, self::MESSAGE_ACTION_DELETED, $userid, $userid, '%' . $search . '%');
 
         // Convert the messages into searchable contacts with their last message being the message that was searched.
         $conversations = array();
@@ -266,173 +262,48 @@ class api {
     public static function get_conversations($userid, $limitfrom = 0, $limitnum = 20) {
         global $DB;
 
-        // The case statement is used to make sure the same key is generated
-        // whether a user sent or received a message (it's the same conversation).
-        // E.g. If there is a message from user 1 to user 2 and then from user 2 to user 1 the result set
-        // will group those into a single record, since 1 -> 2 and 2 -> 1 is the same conversation.
-        $case1 = $DB->sql_concat('useridfrom', "'-'", 'useridto');
-        $case2 = $DB->sql_concat('useridto', "'-'", 'useridfrom');
-        $convocase = "CASE WHEN useridfrom > useridto
-                        THEN $case1
-                        ELSE $case2 END";
-        $convosig = "$convocase AS convo_signature";
-
-        // This is a snippet to join the message tables and filter out any messages the user has deleted
-        // and ignore notifications. The fields are specified by name so that the union works on MySQL.
-        $allmessages = "SELECT
-                            id, useridfrom, useridto, subject, fullmessage, fullmessageformat,
-                            fullmessagehtml, smallmessage, notification, contexturl,
-                            contexturlname, timecreated, timeuserfromdeleted, timeusertodeleted,
-                            component, eventtype, 0 as timeread
-                        FROM {message}
-                        WHERE
-                            (useridto = ? AND timeusertodeleted = 0 AND notification = 0)
-                        UNION ALL
-                        SELECT
-                            id, useridfrom, useridto, subject, fullmessage, fullmessageformat,
-                            fullmessagehtml, smallmessage, notification, contexturl,
-                            contexturlname, timecreated, timeuserfromdeleted, timeusertodeleted,
-                            component, eventtype, 0 as timeread
-                        FROM {message}
-                        WHERE
-                            (useridfrom = ? AND timeuserfromdeleted = 0 AND notification = 0)
-                        UNION ALL
-                        SELECT
-                            id, useridfrom, useridto, subject, fullmessage, fullmessageformat,
-                            fullmessagehtml, smallmessage, notification, contexturl,
-                            contexturlname, timecreated, timeuserfromdeleted, timeusertodeleted,
-                            component, eventtype, timeread
-                        FROM {message_read}
-                        WHERE
-                            (useridto = ? AND timeusertodeleted = 0 AND notification = 0)
-                        UNION ALL
-                        SELECT
-                            id, useridfrom, useridto, subject, fullmessage, fullmessageformat,
-                            fullmessagehtml, smallmessage, notification, contexturl,
-                            contexturlname, timecreated, timeuserfromdeleted, timeusertodeleted,
-                            component, eventtype, timeread
-                        FROM {message_read}
-                        WHERE
-                            (useridfrom = ? AND timeuserfromdeleted = 0 AND notification = 0)";
-        $allmessagesparams = [$userid, $userid, $userid, $userid];
-
-        // Create a transaction to protect against concurrency issues.
-        $transaction = $DB->start_delegated_transaction();
-
-        // First we need to get the list of conversations from the database ordered by the conversation
-        // with the most recent message first.
-        //
-        // This query will join the two message tables and then group the results by the combination
-        // of useridfrom and useridto (the 'convo_signature').
-        $conversationssql = "SELECT $convosig, max(timecreated) as timecreated
-                             FROM ($allmessages) x
-                             GROUP BY $convocase
-                             ORDER BY timecreated DESC, max(id) DESC";
-        $conversationrecords = $DB->get_records_sql($conversationssql, $allmessagesparams, $limitfrom, $limitnum);
-
-        // This user has no conversations so we can return early here.
-        if (empty($conversationrecords)) {
-            $transaction->allow_commit();
-            return [];
-        }
+        // Get the last message from each conversation that the user belongs to.
+        $sql = "SELECT m.id, m.conversationid, m.useridfrom, mcm2.userid as useridto, m.smallmessage, m.timecreated
+                  FROM {messages} m
+            INNER JOIN (
+                          SELECT MAX(m.id) AS messageid
+                            FROM {messages} m
+                      INNER JOIN (
+                                      SELECT m.conversationid, MAX(m.timecreated) as maxtime
+                                        FROM {messages} m
+                                  INNER JOIN {message_conversation_members} mcm
+                                          ON mcm.conversationid = m.conversationid
+                                   LEFT JOIN {message_user_actions} mua
+                                          ON (mua.messageid = m.id AND mua.userid = :userid AND mua.action = :action)
+                                       WHERE mua.id is NULL
+                                         AND mcm.userid = :userid2
+                                    GROUP BY m.conversationid
+                                 ) maxmessage
+                               ON maxmessage.maxtime = m.timecreated AND maxmessage.conversationid = m.conversationid
+                         GROUP BY m.conversationid
+                       ) lastmessage
+                    ON lastmessage.messageid = m.id
+            INNER JOIN {message_conversation_members} mcm
+                    ON mcm.conversationid = m.conversationid
+            INNER JOIN {message_conversation_members} mcm2
+                    ON mcm2.conversationid = m.conversationid
+                 WHERE mcm.userid = m.useridfrom
+                   AND mcm.id != mcm2.id
+              ORDER BY m.timecreated DESC";
+        $messageset = $DB->get_recordset_sql($sql, ['userid' => $userid, 'action' => self::MESSAGE_ACTION_DELETED,
+            'userid2' => $userid], $limitfrom, $limitnum);
 
-        // Next we need to get the max id of the messages sent at the latest time for each conversation.
-        // This needs to be a separate query to above because there is no guarantee that the message with
-        // the highest id will also have the highest timecreated value (in fact that is fairly likely due
-        // to the split between the message tables).
-        //
-        // E.g. if we just added max(id) to the conversation query above and ran it on data like:
-        // id, userfrom, userto, timecreated
-        //  1,        1,      2,           2
-        //  2,        2,      1,           1
-        //
-        // Then the result of the query would be:
-        // convo_signature, timecreated, id
-        //             2-1,           2,  2
-        //
-        // That would be incorrect since the message with id 2 actually has a lower timecreated. Hence why
-        // the two queries need to be split.
-        //
-        // The same result could also be achieved with an inner join in a single query however we're specifically
-        // avoiding multiple joins in the messaging queries because of the size of the messaging tables.
-        $whereclauses = [];
-        $createdtimes = [];
-        foreach ($conversationrecords as $convoid => $record) {
-            $whereclauses[] = "($convocase = '$convoid' AND timecreated = {$record->timecreated})";
-            $createdtimes[] = $record->timecreated;
-        }
-        $messageidwhere = implode(' OR ', $whereclauses);
-        list($timecreatedsql, $timecreatedparams) = $DB->get_in_or_equal($createdtimes);
-
-        $allmessagestimecreated = "SELECT id, useridfrom, useridto, timecreated
-                        FROM {message}
-                        WHERE
-                            (useridto = ? AND timeusertodeleted = 0 AND notification = 0)
-                            AND timecreated $timecreatedsql
-                        UNION ALL
-                        SELECT id, useridfrom, useridto, timecreated
-                        FROM {message}
-                        WHERE
-                            (useridfrom = ? AND timeuserfromdeleted = 0 AND notification = 0)
-                            AND timecreated $timecreatedsql
-                        UNION ALL
-                        SELECT id, useridfrom, useridto, timecreated
-                        FROM {message_read}
-                        WHERE
-                            (useridto = ? AND timeusertodeleted = 0 AND notification = 0)
-                            AND timecreated $timecreatedsql
-                        UNION ALL
-                        SELECT id, useridfrom, useridto, timecreated
-                        FROM {message_read}
-                        WHERE
-                            (useridfrom = ? AND timeuserfromdeleted = 0 AND notification = 0)
-                            AND timecreated $timecreatedsql";
-        $messageidsql = "SELECT $convosig, max(id) as id, timecreated
-                         FROM ($allmessagestimecreated) x
-                         WHERE $messageidwhere
-                         GROUP BY $convocase, timecreated";
-        $messageidparams = array_merge([$userid], $timecreatedparams, [$userid], $timecreatedparams,
-                [$userid], $timecreatedparams, [$userid], $timecreatedparams);
-        $messageidrecords = $DB->get_records_sql($messageidsql, $messageidparams);
-
-        // Ok, let's recap. We've pulled a descending ordered list of conversations by latest time created
-        // for the given user. For each of those conversations we've grabbed the max id for messages
-        // created at that time.
-        //
-        // So at this point we have the list of ids for the most recent message in each of the user's most
-        // recent conversations. Now we need to pull all of the message and user data for each message id.
-        $whereclauses = [];
-        foreach ($messageidrecords as $record) {
-            $whereclauses[] = "(id = {$record->id} AND timecreated = {$record->timecreated})";
-        }
-        $messagewhere = implode(' OR ', $whereclauses);
-        $messagesunionsql = "SELECT
-                                id, useridfrom, useridto, smallmessage, 0 as timeread
-                            FROM {message}
-                            WHERE
-                                {$messagewhere}
-                            UNION ALL
-                            SELECT
-                                id, useridfrom, useridto, smallmessage, timeread
-                            FROM {message_read}
-                            WHERE
-                                {$messagewhere}";
-        $messagesql = "SELECT $convosig, m.smallmessage, m.id, m.useridto, m.useridfrom, m.timeread
-                       FROM ($messagesunionsql) m";
-
-        // We need to handle the case where the $messageids contains two ids from the same conversation
-        // (which can happen because there can be id clashes between the read and unread tables). In
-        // this case we will prioritise the unread message.
-        $messageset = $DB->get_recordset_sql($messagesql, $allmessagesparams);
         $messages = [];
         foreach ($messageset as $message) {
-            $id = $message->convo_signature;
-            if (!isset($messages[$id]) || empty($message->timeread)) {
-                $messages[$id] = $message;
-            }
+            $messages[$message->id] = $message;
         }
         $messageset->close();
 
+        // If there are no messages return early.
+        if (empty($messages)) {
+            return [];
+        }
+
         // We need to pull out the list of other users that are part of each of these conversations. This
         // needs to be done in a separate query to avoid doing a join on the messages tables and the user
         // tables because on large sites these tables are massive which results in extremely slow
@@ -441,77 +312,64 @@ class api {
             return ($message->useridfrom == $userid) ? $message->useridto : $message->useridfrom;
         }, array_values($messages));
 
+        // Ok, let's get the other members in the conversations.
         list($useridsql, $usersparams) = $DB->get_in_or_equal($otheruserids);
-        $userfields = \user_picture::fields('', array('lastaccess'));
+        $userfields = \user_picture::fields('u', array('lastaccess'));
         $userssql = "SELECT $userfields
-                     FROM {user}
-                     WHERE id $useridsql
-                       AND deleted = 0";
+                       FROM {user} u
+                      WHERE id $useridsql
+                        AND deleted = 0";
         $otherusers = $DB->get_records_sql($userssql, $usersparams);
 
-        // Similar to the above use case, we need to pull the contact information and again this has
-        // specifically been separated into another query to avoid having to do joins on the message
-        // tables.
+        // If there are no other users (user may have been deleted), then do not continue.
+        if (empty($otherusers)) {
+            return [];
+        }
+
         $contactssql = "SELECT contactid, blocked
-                        FROM {message_contacts}
-                        WHERE userid = ? AND contactid $useridsql";
-        $contacts = $DB->get_records_sql($contactssql, array_merge([$userid], $otheruserids));
+                          FROM {message_contacts}
+                         WHERE userid = ?
+                           AND contactid $useridsql";
+        $contacts = $DB->get_records_sql($contactssql, array_merge([$userid], $usersparams));
 
         // Finally, let's get the unread messages count for this user so that we can add them
-        // to the conversation.
-        $unreadcountssql = 'SELECT useridfrom, count(*) as count
-                            FROM {message}
-                            WHERE useridto = ?
-                                AND timeusertodeleted = 0
-                                AND notification = 0
-                            GROUP BY useridfrom';
-        $unreadcounts = $DB->get_records_sql($unreadcountssql, [$userid]);
-
-        // We can close off the transaction now.
-        $transaction->allow_commit();
-
-        // Now we need to order the messages back into the same order of the conversations.
-        $orderedconvosigs = array_keys($conversationrecords);
-        usort($messages, function($a, $b) use ($orderedconvosigs) {
-            $aindex = array_search($a->convo_signature, $orderedconvosigs);
-            $bindex = array_search($b->convo_signature, $orderedconvosigs);
-
-            return ($aindex < $bindex) ? -1 : 1;
-        });
-
-        // Preload the contexts before we construct the conversation to prevent the
-        // create_contact helper from needing to query the DB so often.
-        $ctxselect = \context_helper::get_preload_record_columns_sql('ctx');
-        $sql = "SELECT {$ctxselect}
-                FROM {context} ctx
-                WHERE ctx.contextlevel = ? AND
-                ctx.instanceid {$useridsql}";
-        $contexts = [];
-        $contexts = $DB->get_records_sql($sql, array_merge([CONTEXT_USER], $usersparams));
-        foreach ($contexts as $context) {
-            \context_helper::preload_from_record($context);
-        }
-
+        // to the conversation. Remember we need to ignore the messages the user sent.
+        $unreadcountssql = 'SELECT m.useridfrom, count(m.id) as count
+                              FROM {messages} m
+                        INNER JOIN {message_conversations} mc
+                                ON mc.id = m.conversationid
+                        INNER JOIN {message_conversation_members} mcm
+                                ON m.conversationid = mcm.conversationid
+                         LEFT JOIN {message_user_actions} mua
+                                ON (mua.messageid = m.id AND mua.userid = ? AND
+                                   (mua.action = ? OR mua.action = ?))
+                             WHERE mcm.userid = ?
+                               AND m.useridfrom != ?
+                               AND mua.id is NULL
+                          GROUP BY useridfrom';
+        $unreadcounts = $DB->get_records_sql($unreadcountssql, [$userid, self::MESSAGE_ACTION_READ, self::MESSAGE_ACTION_DELETED,
+            $userid, $userid]);
+
+        // Get rid of the table prefix.
+        $userfields = str_replace('u.', '', $userfields);
         $userproperties = explode(',', $userfields);
         $arrconversations = array();
-        // The last step now is to bring all of the data we've gathered together to create
-        // a conversation (or contact, as the API is named...).
         foreach ($messages as $message) {
             $conversation = new \stdClass();
             $otheruserid = ($message->useridfrom == $userid) ? $message->useridto : $message->useridfrom;
             $otheruser = isset($otherusers[$otheruserid]) ? $otherusers[$otheruserid] : null;
             $contact = isset($contacts[$otheruserid]) ? $contacts[$otheruserid] : null;
 
+            // It's possible the other user was deleted, so, skip.
+            if (is_null($otheruser)) {
+                continue;
+            }
+
             // Add the other user's information to the conversation, if we have one.
             foreach ($userproperties as $prop) {
                 $conversation->$prop = ($otheruser) ? $otheruser->$prop : null;
             }
 
-            // Do not process a conversation with a deleted user.
-            if (empty($conversation->id)) {
-                continue;
-            }
-
             // Add the contact's information, if we have one.
             $conversation->blocked = ($contact) ? $contact->blocked : null;
 
@@ -562,6 +420,73 @@ class api {
         return $arrcontacts;
     }
 
+    /**
+     * Returns the an array of the users the given user is in a conversation
+     * with who are a contact and the number of unread messages.
+     *
+     * @param int $userid The user id
+     * @param int $limitfrom
+     * @param int $limitnum
+     * @return array
+     */
+    public static function get_contacts_with_unread_message_count($userid, $limitfrom = 0, $limitnum = 0) {
+        global $DB;
+
+        $userfields = \user_picture::fields('u', array('lastaccess'));
+        $unreadcountssql = "SELECT $userfields, count(m.id) as messagecount
+                              FROM {message_contacts} mc
+                        INNER JOIN {user} u
+                                ON u.id = mc.contactid
+                         LEFT JOIN {messages} m
+                                ON m.useridfrom = mc.contactid
+                         LEFT JOIN {message_conversation_members} mcm
+                                ON mcm.conversationid = m.conversationid AND mcm.userid = ? AND mcm.userid != m.useridfrom
+                         LEFT JOIN {message_user_actions} mua
+                                ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?)
+                             WHERE mua.id is NULL
+                               AND mc.userid = ?
+                               AND mc.blocked = 0
+                               AND u.deleted = 0
+                          GROUP BY $userfields";
+
+        return $DB->get_records_sql($unreadcountssql, [$userid, $userid, self::MESSAGE_ACTION_READ,
+            $userid, $userid], $limitfrom, $limitnum);
+    }
+
+    /**
+     * Returns the an array of the users the given user is in a conversation
+     * with who are not a contact and the number of unread messages.
+     *
+     * @param int $userid The user id
+     * @param int $limitfrom
+     * @param int $limitnum
+     * @return array
+     */
+    public static function get_non_contacts_with_unread_message_count($userid, $limitfrom = 0, $limitnum = 0) {
+        global $DB;
+
+        $userfields = \user_picture::fields('u', array('lastaccess'));
+        $unreadcountssql = "SELECT $userfields, count(m.id) as messagecount
+                              FROM {user} u
+                        INNER JOIN {messages} m
+                                ON m.useridfrom = u.id
+                        INNER JOIN {message_conversation_members} mcm
+                                ON mcm.conversationid = m.conversationid
+                         LEFT JOIN {message_user_actions} mua
+                                ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?)
+                         LEFT JOIN {message_contacts} mc
+                                ON (mc.userid = ? AND mc.contactid = u.id)
+                             WHERE mcm.userid = ?
+                               AND mcm.userid != m.useridfrom
+                               AND mua.id is NULL
+                               AND mc.id is NULL
+                               AND u.deleted = 0
+                          GROUP BY $userfields";
+
+        return $DB->get_records_sql($unreadcountssql, [$userid, self::MESSAGE_ACTION_READ, $userid, $userid],
+            $limitfrom, $limitnum);
+    }
+
     /**
      * Returns the messages to display in the message area.
      *
@@ -710,54 +635,43 @@ class api {
      * @return bool
      */
     public static function delete_conversation($userid, $otheruserid) {
-        global $DB;
+        global $DB, $USER;
 
-        // We need to update the tables to mark all messages as deleted from and to the other user. This seems worse than it
-        // is, that's because our DB structure splits messages into two tables (great idea, huh?) which causes code like this.
-        // This won't be a particularly heavily used function (at least I hope not), so let's hope MDL-36941 gets worked on
-        // soon for the sake of any developers' sanity when dealing with the messaging system.
-        $now = time();
-        $sql = "UPDATE {message}
-                   SET timeuserfromdeleted = :time
-                 WHERE useridfrom = :userid
-                   AND useridto = :otheruserid
-                   AND notification = 0";
-        $DB->execute($sql, array('time' => $now, 'userid' => $userid, 'otheruserid' => $otheruserid));
-
-        $sql = "UPDATE {message}
-                   SET timeusertodeleted = :time
-                 WHERE useridto = :userid
-                   AND useridfrom = :otheruserid
-                   AND notification = 0";
-        $DB->execute($sql, array('time' => $now, 'userid' => $userid, 'otheruserid' => $otheruserid));
-
-        $sql = "UPDATE {message_read}
-                   SET timeuserfromdeleted = :time
-                 WHERE useridfrom = :userid
-                   AND useridto = :otheruserid
-                   AND notification = 0";
-        $DB->execute($sql, array('time' => $now, 'userid' => $userid, 'otheruserid' => $otheruserid));
-
-        $sql = "UPDATE {message_read}
-                   SET timeusertodeleted = :time
-                 WHERE useridto = :userid
-                   AND useridfrom = :otheruserid
-                   AND notification = 0";
-        $DB->execute($sql, array('time' => $now, 'userid' => $userid, 'otheruserid' => $otheruserid));
-
-        // Now we need to trigger events for these.
-        if ($messages = helper::get_messages($userid, $otheruserid, $now)) {
-            // Loop through and trigger a deleted event.
-            foreach ($messages as $message) {
-                $messagetable = 'message';
-                if (!empty($message->timeread)) {
-                    $messagetable = 'message_read';
-                }
+        $conversationid = self::get_conversation_between_users([$userid, $otheruserid]);
 
-                // Trigger event for deleting the message.
-                \core\event\message_deleted::create_from_ids($message->useridfrom, $message->useridto,
-                    $userid, $messagetable, $message->id)->trigger();
+        // If there is no conversation, there is nothing to do.
+        if (!$conversationid) {
+            return true;
+        }
+
+        // Get all messages belonging to this conversation that have not already been deleted by this user.
+        $sql = "SELECT m.*
+                 FROM {messages} m
+           INNER JOIN {message_conversations} mc
+                   ON m.conversationid = mc.id
+            LEFT JOIN {message_user_actions} mua
+                   ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?)
+                WHERE mua.id is NULL
+                  AND mc.id = ?
+             ORDER BY m.timecreated ASC";
+        $messages = $DB->get_records_sql($sql, [$userid, self::MESSAGE_ACTION_DELETED, $conversationid]);
+
+        // Ok, mark these as deleted.
+        foreach ($messages as $message) {
+            $mua = new \stdClass();
+            $mua->userid = $userid;
+            $mua->messageid = $message->id;
+            $mua->action = self::MESSAGE_ACTION_DELETED;
+            $mua->timecreated = time();
+            $mua->id = $DB->insert_record('message_user_actions', $mua);
+
+            if ($message->useridfrom == $userid) {
+                $useridto = $otheruserid;
+            } else {
+                $useridto = $userid;
             }
+            \core\event\message_deleted::create_from_ids($message->useridfrom, $useridto,
+                $USER->id, $message->id, $mua->id)->trigger();
         }
 
         return true;
@@ -777,11 +691,87 @@ class api {
             $user = $USER;
         }
 
-        return $DB->count_records_select(
-            'message',
-            'useridto = ? AND timeusertodeleted = 0 AND notification = 0',
-            [$user->id],
-            "COUNT(DISTINCT(useridfrom))");
+        $sql = "SELECT COUNT(DISTINCT(m.conversationid))
+                  FROM {messages} m
+            INNER JOIN {message_conversations} mc
+                    ON m.conversationid = mc.id
+            INNER JOIN {message_conversation_members} mcm
+                    ON mc.id = mcm.conversationid
+             LEFT JOIN {message_user_actions} mua
+                    ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?)
+                 WHERE mcm.userid = ?
+                   AND mcm.userid != m.useridfrom
+                   AND mua.id is NULL";
+
+        return $DB->count_records_sql($sql, [$user->id, self::MESSAGE_ACTION_READ, $user->id]);
+    }
+
+    /**
+     * Marks all messages being sent to a user in a particular conversation.
+     *
+     * If $conversationdid is null then it marks all messages as read sent to $userid.
+     *
+     * @param int $userid
+     * @param int|null $conversationid The conversation the messages belong to mark as read, if null mark all
+     */
+    public static function mark_all_messages_as_read($userid, $conversationid = null) {
+        global $DB;
+
+        $messagesql = "SELECT m.*
+                         FROM {messages} m
+                   INNER JOIN {message_conversations} mc
+                           ON mc.id = m.conversationid
+                   INNER JOIN {message_conversation_members} mcm
+                           ON mcm.conversationid = mc.id
+                    LEFT JOIN {message_user_actions} mua
+                           ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?)
+                        WHERE mua.id is NULL
+                          AND mcm.userid = ?
+                          AND m.useridfrom != ?";
+        $messageparams = [];
+        $messageparams[] = $userid;
+        $messageparams[] = self::MESSAGE_ACTION_READ;
+        $messageparams[] = $userid;
+        $messageparams[] = $userid;
+        if (!is_null($conversationid)) {
+            $messagesql .= " AND mc.id = ?";
+            $messageparams[] = $conversationid;
+        }
+
+        $messages = $DB->get_recordset_sql($messagesql, $messageparams);
+        foreach ($messages as $message) {
+            self::mark_message_as_read($userid, $message);
+        }
+        $messages->close();
+    }
+
+    /**
+     * Marks all notifications being sent from one user to another user as read.
+     *
+     * If the from user is null then it marks all notifications as read sent to the to user.
+     *
+     * @param int $touserid the id of the message recipient
+     * @param int|null $fromuserid the id of the message sender, null if all messages
+     * @return void
+     */
+    public static function mark_all_notifications_as_read($touserid, $fromuserid = null) {
+        global $DB;
+
+        $notificationsql = "SELECT n.*
+                              FROM {notifications} n
+                             WHERE useridto = ?
+                               AND timeread is NULL";
+        $notificationsparams = [$touserid];
+        if (!empty($fromuserid)) {
+            $notificationsql .= " AND useridfrom = ?";
+            $notificationsparams[] = $fromuserid;
+        }
+
+        $notifications = $DB->get_recordset_sql($notificationsql, $notificationsparams);
+        foreach ($notifications as $notification) {
+            self::mark_notification_as_read($notification);
+        }
+        $notifications->close();
     }
 
     /**
@@ -789,39 +779,42 @@ class api {
      *
      * Can be filtered by type.
      *
+     * @deprecated since 3.5
      * @param int $touserid the id of the message recipient
      * @param int $fromuserid the id of the message sender
      * @param string $type filter the messages by type, either MESSAGE_TYPE_NOTIFICATION, MESSAGE_TYPE_MESSAGE or '' for all.
      * @return void
      */
     public static function mark_all_read_for_user($touserid, $fromuserid = 0, $type = '') {
-        global $DB;
+        debugging('\core_message\api::mark_all_read_for_user is deprecated. Please either use ' .
+            '\core_message\api::mark_all_notifications_read_for_user or \core_message\api::mark_all_messages_read_for_user',
+            DEBUG_DEVELOPER);
 
-        $params = array();
-
-        if (!empty($touserid)) {
-            $params['useridto'] = $touserid;
-        }
+        $type = strtolower($type);
 
+        $conversationid = null;
+        $ignoremessages = false;
         if (!empty($fromuserid)) {
-            $params['useridfrom'] = $fromuserid;
+            $conversationid = self::get_conversation_between_users([$touserid, $fromuserid]);
+            if (!$conversationid) { // If there is no conversation between the users then there are no messages to mark.
+                $ignoremessages = true;
+            }
         }
 
         if (!empty($type)) {
-            if (strtolower($type) == MESSAGE_TYPE_NOTIFICATION) {
-                $params['notification'] = 1;
-            } else if (strtolower($type) == MESSAGE_TYPE_MESSAGE) {
-                $params['notification'] = 0;
+            if ($type == MESSAGE_TYPE_NOTIFICATION) {
+                self::mark_all_notifications_as_read($touserid, $fromuserid);
+            } else if ($type == MESSAGE_TYPE_MESSAGE) {
+                if (!$ignoremessages) {
+                    self::mark_all_messages_as_read($touserid, $conversationid);
+                }
+            }
+        } else { // We want both.
+            self::mark_all_notifications_as_read($touserid, $fromuserid);
+            if (!$ignoremessages) {
+                self::mark_all_messages_as_read($touserid, $conversationid);
             }
         }
-
-        $messages = $DB->get_recordset('message', $params);
-
-        foreach ($messages as $message) {
-            message_mark_message_read($message, time());
-        }
-
-        $messages->close();
     }
 
     /**
@@ -1076,4 +1069,224 @@ class api {
         }
         return $processor;
     }
+
+    /**
+     * Retrieve users blocked by $user1
+     *
+     * @param int $userid The user id of the user whos blocked users we are returning
+     * @return array the users blocked
+     */
+    public static function get_blocked_users($userid) {
+        global $DB;
+
+        $userfields = \user_picture::fields('u', array('lastaccess'));
+        $blockeduserssql = "SELECT $userfields
+                              FROM {message_contacts} mc
+                        INNER JOIN {user} u
+                                ON u.id = mc.contactid
+                             WHERE u.deleted = 0
+                               AND mc.userid = ?
+                               AND mc.blocked = 1
+                          GROUP BY $userfields
+                          ORDER BY u.firstname ASC";
+        return $DB->get_records_sql($blockeduserssql, [$userid]);
+    }
+
+    /**
+     * Mark a single message as read.
+     *
+     * @param int $userid The user id who marked the message as read
+     * @param \stdClass $message The message
+     * @param int|null $timeread The time the message was marked as read, if null will default to time()
+     */
+    public static function mark_message_as_read($userid, $message, $timeread = null) {
+        global $DB;
+
+        if (is_null($timeread)) {
+            $timeread = time();
+        }
+
+        $mua = new \stdClass();
+        $mua->userid = $userid;
+        $mua->messageid = $message->id;
+        $mua->action = self::MESSAGE_ACTION_READ;
+        $mua->timecreated = $timeread;
+        $mua->id = $DB->insert_record('message_user_actions', $mua);
+
+        // Get the context for the user who received the message.
+        $context = \context_user::instance($userid, IGNORE_MISSING);
+        // If the user no longer exists the context value will be false, in this case use the system context.
+        if ($context === false) {
+            $context = \context_system::instance();
+        }
+
+        // Trigger event for reading a message.
+        $event = \core\event\message_viewed::create(array(
+            'objectid' => $mua->id,
+            'userid' => $userid, // Using the user who read the message as they are the ones performing the action.
+            'context' => $context,
+            'relateduserid' => $message->useridfrom,
+            'other' => array(
+                'messageid' => $message->id
+            )
+        ));
+        $event->trigger();
+    }
+
+    /**
+     * Mark a single notification as read.
+     *
+     * @param \stdClass $notification The notification
+     * @param int|null $timeread The time the message was marked as read, if null will default to time()
+     */
+    public static function mark_notification_as_read($notification, $timeread = null) {
+        global $DB;
+
+        if (is_null($timeread)) {
+            $timeread = time();
+        }
+
+        if (is_null($notification->timeread)) {
+            $updatenotification = new \stdClass();
+            $updatenotification->id = $notification->id;
+            $updatenotification->timeread = $timeread;
+
+            $DB->update_record('notifications', $updatenotification);
+
+            // Trigger event for reading a notification.
+            \core\event\notification_viewed::create_from_ids(
+                $notification->useridfrom,
+                $notification->useridto,
+                $notification->id
+            )->trigger();
+        }
+    }
+
+    /**
+     * Checks if a user can delete a message.
+     *
+     * @param int $userid the user id of who we want to delete the message for (this may be done by the admin
+     *  but will still seem as if it was by the user)
+     * @param int $messageid The message id
+     * @return bool Returns true if a user can delete the message, false otherwise.
+     */
+    public static function can_delete_message($userid, $messageid) {
+        global $DB, $USER;
+
+        $sql = "SELECT m.id, m.useridfrom, mcm.userid as useridto
+                  FROM {messages} m
+            INNER JOIN {message_conversations} mc
+                    ON m.conversationid = mc.id
+            INNER JOIN {message_conversation_members} mcm
+                    ON mcm.conversationid = mc.id
+                 WHERE mcm.userid != m.useridfrom
+                   AND m.id = ?";
+        $message = $DB->get_record_sql($sql, [$messageid], MUST_EXIST);
+
+        if ($message->useridfrom == $userid) {
+            $userdeleting = 'useridfrom';
+        } else if ($message->useridto == $userid) {
+            $userdeleting = 'useridto';
+        } else {
+            return false;
+        }
+
+        $systemcontext = \context_system::instance();
+
+        // Let's check if the user is allowed to delete this message.
+        if (has_capability('moodle/site:deleteanymessage', $systemcontext) ||
+            ((has_capability('moodle/site:deleteownmessage', $systemcontext) &&
+                $USER->id == $message->$userdeleting))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Deletes a message.
+     *
+     * This function does not verify any permissions.
+     *
+     * @param int $userid the user id of who we want to delete the message for (this may be done by the admin
+     *  but will still seem as if it was by the user)
+     * @param int $messageid The message id
+     * @return bool
+     */
+    public static function delete_message($userid, $messageid) {
+        global $DB;
+
+        $sql = "SELECT m.id, m.useridfrom, mcm.userid as useridto
+                  FROM {messages} m
+            INNER JOIN {message_conversations} mc
+                    ON m.conversationid = mc.id
+            INNER JOIN {message_conversation_members} mcm
+                    ON mcm.conversationid = mc.id
+                 WHERE mcm.userid != m.useridfrom
+                   AND m.id = ?";
+        $message = $DB->get_record_sql($sql, [$messageid], MUST_EXIST);
+
+        // Check if the user has already deleted this message.
+        if (!$DB->record_exists('message_user_actions', ['userid' => $userid,
+                'messageid' => $messageid, 'action' => self::MESSAGE_ACTION_DELETED])) {
+            $mua = new \stdClass();
+            $mua->userid = $userid;
+            $mua->messageid = $messageid;
+            $mua->action = self::MESSAGE_ACTION_DELETED;
+            $mua->timecreated = time();
+            $mua->id = $DB->insert_record('message_user_actions', $mua);
+
+            // Trigger event for deleting a message.
+            \core\event\message_deleted::create_from_ids($message->useridfrom, $message->useridto,
+                $userid, $message->id, $mua->id)->trigger();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the conversation between two users.
+     *
+     * @param array $userids
+     * @return int|bool The id of the conversation, false if not found
+     */
+    public static function get_conversation_between_users(array $userids) {
+        global $DB;
+
+        $hash = helper::get_conversation_hash($userids);
+
+        if ($conversation = $DB->get_record('message_conversations', ['convhash' => $hash])) {
+            return $conversation->id;
+        }
+
+        return false;
+    }
+
+    /**
+     * Creates a conversation between two users.
+     *
+     * @param array $userids
+     * @return int The id of the conversation
+     */
+    public static function create_conversation_between_users(array $userids) {
+        global $DB;
+
+        $conversation = new \stdClass();
+        $conversation->convhash = helper::get_conversation_hash($userids);
+        $conversation->timecreated = time();
+        $conversation->id = $DB->insert_record('message_conversations', $conversation);
+
+        // Add members to this conversation.
+        foreach ($userids as $userid) {
+            $member = new \stdClass();
+            $member->conversationid = $conversation->id;
+            $member->userid = $userid;
+            $member->timecreated = time();
+            $DB->insert_record('message_conversation_members', $member);
+        }
+
+        return $conversation->id;
+    }
 }
index b7f2a04..0c68fd7 100644 (file)
@@ -51,48 +51,61 @@ class helper {
                                         $sort = 'timecreated ASC', $timefrom = 0, $timeto = 0) {
         global $DB;
 
-        $messageid = $DB->sql_concat("'message_'", 'id');
-        $messagereadid = $DB->sql_concat("'messageread_'", 'id');
-
-        $sql = "SELECT {$messageid} AS fakeid, id, useridfrom, useridto, subject, fullmessage, fullmessagehtml, fullmessageformat,
-                       smallmessage, notification, timecreated, 0 as timeread
-                  FROM {message} m
-                 WHERE ((useridto = ? AND useridfrom = ? AND timeusertodeleted = ?)
-                    OR (useridto = ? AND useridfrom = ? AND timeuserfromdeleted = ?))
-                   AND notification = 0
-                   %where%
-             UNION ALL
-                SELECT {$messagereadid} AS fakeid, id, useridfrom, useridto, subject, fullmessage, fullmessagehtml, fullmessageformat,
-                       smallmessage, notification, timecreated, timeread
-                  FROM {message_read} mr
-                 WHERE ((useridto = ? AND useridfrom = ? AND timeusertodeleted = ?)
-                    OR (useridto = ? AND useridfrom = ? AND timeuserfromdeleted = ?))
-                   AND notification = 0
-                   %where%
-              ORDER BY $sort";
-        $params1 = array($userid, $otheruserid, $timedeleted,
-                         $otheruserid, $userid, $timedeleted);
-
-        $params2 = array($userid, $otheruserid, $timedeleted,
-                         $otheruserid, $userid, $timedeleted);
-        $where = array();
+        $hash = self::get_conversation_hash([$userid, $otheruserid]);
+
+        $sql = "SELECT m.id, m.useridfrom, m.subject, m.fullmessage, m.fullmessagehtml,
+                       m.fullmessageformat, m.smallmessage, m.timecreated, muaread.timecreated AS timeread
+                  FROM {message_conversations} mc
+            INNER JOIN {messages} m
+                    ON m.conversationid = mc.id
+             LEFT JOIN {message_user_actions} muaread
+                    ON (muaread.messageid = m.id
+                   AND muaread.userid = :userid1
+                   AND muaread.action = :readaction)";
+        $params = ['userid1' => $userid, 'readaction' => api::MESSAGE_ACTION_READ, 'convhash' => $hash];
+
+        if (empty($timedeleted)) {
+            $sql .= " LEFT JOIN {message_user_actions} mua
+                             ON (mua.messageid = m.id
+                            AND mua.userid = :userid2
+                            AND mua.action = :deleteaction
+                            AND mua.timecreated is NOT NULL)";
+        } else {
+            $sql .= " INNER JOIN {message_user_actions} mua
+                              ON (mua.messageid = m.id
+                             AND mua.userid = :userid2
+                             AND mua.action = :deleteaction
+                             AND mua.timecreated = :timedeleted)";
+            $params['timedeleted'] = $timedeleted;
+        }
+
+        $params['userid2'] = $userid;
+        $params['deleteaction'] = api::MESSAGE_ACTION_DELETED;
+
+        $sql .= " WHERE mc.convhash = :convhash";
 
         if (!empty($timefrom)) {
-            $where[] = 'AND timecreated >= ?';
-            $params1[] = $timefrom;
-            $params2[] = $timefrom;
+            $sql .= " AND m.timecreated >= :timefrom";
+            $params['timefrom'] = $timefrom;
         }
 
         if (!empty($timeto)) {
-            $where[] = 'AND timecreated <= ?';
-            $params1[] = $timeto;
-            $params2[] = $timeto;
+            $sql .= " AND m.timecreated <= :timeto";
+            $params['timeto'] = $timeto;
         }
 
-        $sql = str_replace('%where%', implode(' ', $where), $sql);
-        $params = array_merge($params1, $params2);
+        if (empty($timedeleted)) {
+            $sql .= " AND mua.id is NULL";
+        }
+
+        $sql .= " ORDER BY m.$sort";
+
+        $messages = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
+        foreach ($messages as &$message) {
+            $message->useridto = ($message->useridfrom == $userid) ? $otheruserid : $userid;
+        }
 
-        return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
+        return $messages;
     }
 
     /**
@@ -293,6 +306,18 @@ class helper {
         return $params;
     }
 
+    /**
+     * Returns the conversation hash between users for easy look-ups in the DB.
+     *
+     * @param array $userids
+     * @return string
+     */
+    public static function get_conversation_hash(array $userids) {
+        sort($userids);
+
+        return sha1(implode('-', $userids));
+    }
+
     /**
      * Returns the cache key for the time created value of the last message between two users.
      *
index 35fce7f..58479f2 100644 (file)
@@ -145,9 +145,15 @@ abstract class base_message extends \core_search\base {
             $userfield) {
         global $DB;
 
+        if ($userfield == 'useridto') {
+            $userfield = 'mcm.userid';
+        } else {
+            $userfield = 'm.useridfrom';
+        }
+
         // Set up basic query.
         $where = $userfield . ' != :noreplyuser AND ' . $userfield .
-                ' != :supportuser AND timecreated >= :modifiedfrom';
+                ' != :supportuser AND m.timecreated >= :modifiedfrom';
         $params = [
             'noreplyuser' => \core_user::NOREPLY_USER,
             'supportuser' => \core_user::SUPPORT_USER,
@@ -179,6 +185,15 @@ abstract class base_message extends \core_search\base {
                 throw new \coding_exception('Unexpected contextlevel: ' . $context->contextlevel);
         }
 
-        return $DB->get_recordset_select('message_read', $where, $params, 'timeread ASC');
+        $sql = "SELECT m.*, mcm.userid as useridto
+                  FROM {messages} m
+            INNER JOIN {message_conversations} mc
+                    ON m.conversationid = mc.id
+            INNER JOIN {message_conversation_members} mcm
+                    ON mcm.conversationid = mc.id
+                 WHERE mcm.userid != m.useridfrom
+                   AND $where
+              ORDER BY m.timecreated ASC";
+        return $DB->get_recordset_sql($sql, $params);
     }
 }
index 26a2561..052d6bd 100644 (file)
@@ -70,7 +70,15 @@ class message_received extends base_message {
             return \core_search\manager::ACCESS_DENIED;
         }
 
-        $message = $DB->get_record('message_read', array('id' => $id));
+        $sql = "SELECT m.*, mcm.userid as useridto
+                  FROM {messages} m
+            INNER JOIN {message_conversations} mc
+                    ON m.conversationid = mc.id
+            INNER JOIN {message_conversation_members} mcm
+                    ON mcm.conversationid = mc.id
+                 WHERE mcm.userid != m.useridfrom
+                   AND m.id = :id";
+        $message = $DB->get_record_sql($sql, array('id' => $id));
         if (!$message) {
             return \core_search\manager::ACCESS_DELETED;
         }
@@ -86,7 +94,9 @@ class message_received extends base_message {
             return \core_search\manager::ACCESS_DENIED;
         }
 
-        if ($message->timeusertodeleted != 0) {
+        $usertodeleted = $DB->record_exists('message_user_actions', ['messageid' => $id, 'userid' => $message->useridto,
+            'action' => \core_message\api::MESSAGE_ACTION_DELETED]);
+        if ($usertodeleted) {
             return \core_search\manager::ACCESS_DELETED;
         }
 
index 9425a82..79dc0bd 100644 (file)
@@ -69,7 +69,15 @@ class message_sent extends base_message {
             return \core_search\manager::ACCESS_DENIED;
         }
 
-        $message = $DB->get_record('message_read', array('id' => $id));
+        $sql = "SELECT m.*, mcm.userid as useridto
+                  FROM {messages} m
+            INNER JOIN {message_conversations} mc
+                    ON m.conversationid = mc.id
+            INNER JOIN {message_conversation_members} mcm
+                    ON mcm.conversationid = mc.id
+                 WHERE mcm.userid != m.useridfrom
+                   AND m.id = :id";
+        $message = $DB->get_record_sql($sql, array('id' => $id));
         if (!$message) {
             return \core_search\manager::ACCESS_DELETED;
         }
@@ -85,7 +93,9 @@ class message_sent extends base_message {
             return \core_search\manager::ACCESS_DENIED;
         }
 
-        if ($message->timeuserfromdeleted != 0) {
+        $userfromdeleted = $DB->record_exists('message_user_actions', ['messageid' => $id, 'userid' => $message->useridfrom,
+            'action' => \core_message\api::MESSAGE_ACTION_DELETED]);
+        if ($userfromdeleted) {
             return \core_search\manager::ACCESS_DELETED;
         }
 
index 173b1c4..26c4ccd 100644 (file)
@@ -1161,7 +1161,7 @@ class core_message_external extends external_api {
      * @since Moodle 2.5
      */
     public static function get_contacts() {
-        global $CFG, $PAGE;
+        global $CFG, $PAGE, $USER;
 
         // Check if messaging is enabled.
         if (empty($CFG->messaging)) {
@@ -1170,25 +1170,68 @@ class core_message_external extends external_api {
 
         require_once($CFG->dirroot . '/user/lib.php');
 
-        list($online, $offline, $strangers) = message_get_contacts();
-        $allcontacts = array('online' => $online, 'offline' => $offline, 'strangers' => $strangers);
-        foreach ($allcontacts as $mode => $contacts) {
-            foreach ($contacts as $key => $contact) {
-                $newcontact = array(
-                    'id' => $contact->id,
-                    'fullname' => fullname($contact),
-                    'unread' => $contact->messagecount
-                );
+        $allcontacts = array('online' => [], 'offline' => [], 'strangers' => []);
+        $contacts = \core_message\api::get_contacts_with_unread_message_count($USER->id);
+        foreach ($contacts as $contact) {
+            // Set the mode.
+            $mode = 'offline';
+            if (\core_message\helper::is_online($contact->lastaccess)) {
+                $mode = 'online';
+            }
 
-                $userpicture = new user_picture($contact);
-                $userpicture->size = 1; // Size f1.
-                $newcontact['profileimageurl'] = $userpicture->get_url($PAGE)->out(false);
-                $userpicture->size = 0; // Size f2.
-                $newcontact['profileimageurlsmall'] = $userpicture->get_url($PAGE)->out(false);
+            $newcontact = array(
+                'id' => $contact->id,
+                'fullname' => fullname($contact),
+                'unread' => $contact->messagecount
+            );
+
+            $userpicture = new user_picture($contact);
+            $userpicture->size = 1; // Size f1.
+            $newcontact['profileimageurl'] = $userpicture->get_url($PAGE)->out(false);
+            $userpicture->size = 0; // Size f2.
+            $newcontact['profileimageurlsmall'] = $userpicture->get_url($PAGE)->out(false);
 
-                $allcontacts[$mode][$key] = $newcontact;
+            $allcontacts[$mode][$contact->id] = $newcontact;
+        }
+
+        $strangers = \core_message\api::get_non_contacts_with_unread_message_count($USER->id);
+        foreach ($strangers as $contact) {
+            $newcontact = array(
+                'id' => $contact->id,
+                'fullname' => fullname($contact),
+                'unread' => $contact->messagecount
+            );
+
+            $userpicture = new user_picture($contact);
+            $userpicture->size = 1; // Size f1.
+            $newcontact['profileimageurl'] = $userpicture->get_url($PAGE)->out(false);
+            $userpicture->size = 0; // Size f2.
+            $newcontact['profileimageurlsmall'] = $userpicture->get_url($PAGE)->out(false);
+
+            $allcontacts['strangers'][$contact->id] = $newcontact;
+        }
+
+        // Add noreply user and support user to the list, if they don't exist.
+        $supportuser = core_user::get_support_user();
+        if (!isset($strangers[$supportuser->id]) && !$supportuser->deleted) {
+            $supportuser->messagecount = message_count_unread_messages($USER, $supportuser);
+            if ($supportuser->messagecount > 0) {
+                $supportuser->fullname = fullname($supportuser);
+                $supportuser->unread = $supportuser->messagecount;
+                $allcontacts['strangers'][$supportuser->id] = $supportuser;
+            }
+        }
+
+        $noreplyuser = core_user::get_noreply_user();
+        if (!isset($strangers[$noreplyuser->id]) && !$noreplyuser->deleted) {
+            $noreplyuser->messagecount = message_count_unread_messages($USER, $noreplyuser);
+            if ($noreplyuser->messagecount > 0) {
+                $noreplyuser->fullname = fullname($noreplyuser);
+                $noreplyuser->unread = $noreplyuser->messagecount;
+                $allcontacts['strangers'][$noreplyuser->id] = $noreplyuser;
             }
         }
+
         return $allcontacts;
     }
 
@@ -1486,13 +1529,16 @@ class core_message_external extends external_api {
             foreach ($messages as $mid => $message) {
 
                 // Do not return deleted messages.
-                if (($useridto == $USER->id and $message->timeusertodeleted) or
+                if (!$message->notification) {
+                    if (($useridto == $USER->id and $message->timeusertodeleted) or
                         ($useridfrom == $USER->id and $message->timeuserfromdeleted)) {
-
-                    unset($messages[$mid]);
-                    continue;
+                        unset($messages[$mid]);
+                        continue;
+                    }
                 }
 
+                $message->useridto = $useridto;
+
                 // We need to get the user from the query.
                 if (empty($userfromfullname)) {
                     // Check for non-reply and support users.
@@ -1517,11 +1563,6 @@ class core_message_external extends external_api {
                     $message->usertofullname = $usertofullname;
                 }
 
-                // This field is only available in the message_read table.
-                if (!isset($message->timeread)) {
-                    $message->timeread = 0;
-                }
-
                 $message->text = message_format_message_text($message);
                 $messages[$mid] = (array) $message;
             }
@@ -1635,7 +1676,7 @@ class core_message_external extends external_api {
             throw new moodle_exception('accessdenied', 'admin');
         }
 
-        \core_message\api::mark_all_read_for_user($useridto, $useridfrom, MESSAGE_TYPE_NOTIFICATION);
+        \core_message\api::mark_all_notifications_as_read($useridto, $useridfrom);
 
         return true;
     }
@@ -1774,7 +1815,7 @@ class core_message_external extends external_api {
         }
 
         // Now, we can get safely all the blocked users.
-        $users = message_get_blocked_users($user);
+        $users = \core_message\api::get_blocked_users($user->id);
 
         $blockedusers = array();
         foreach ($users as $user) {
@@ -1830,7 +1871,7 @@ class core_message_external extends external_api {
     public static function mark_message_read_parameters() {
         return new external_function_parameters(
             array(
-                'messageid' => new external_value(PARAM_INT, 'id of the message (in the message table)'),
+                'messageid' => new external_value(PARAM_INT, 'id of the message in the messages table'),
                 'timeread' => new external_value(PARAM_INT, 'timestamp for when the message should be marked read',
                     VALUE_DEFAULT, 0)
             )
@@ -1875,16 +1916,31 @@ class core_message_external extends external_api {
         $context = context_system::instance();
         self::validate_context($context);
 
-        $message = $DB->get_record('message', array('id' => $params['messageid']), '*', MUST_EXIST);
+        $sql = "SELECT m.*, mcm.userid as useridto
+                  FROM {messages} m
+            INNER JOIN {message_conversations} mc
+                    ON m.conversationid = mc.id
+            INNER JOIN {message_conversation_members} mcm
+                    ON mcm.conversationid = mc.id
+             LEFT JOIN {message_user_actions} mua
+                    ON (mua.messageid = m.id AND mua.userid = ? AND mua.action = ?)
+                 WHERE mua.id is NULL
+                   AND mcm.userid != m.useridfrom
+                   AND m.id = ?";
+        $messageparams = [];
+        $messageparams[] = $USER->id;
+        $messageparams[] = \core_message\api::MESSAGE_ACTION_READ;
+        $messageparams[] = $params['messageid'];
+        $message = $DB->get_record_sql($sql, $messageparams, MUST_EXIST);
 
         if ($message->useridto != $USER->id) {
             throw new invalid_parameter_exception('Invalid messageid, you don\'t have permissions to mark this message as read');
         }
 
-        $messageid = message_mark_message_read($message, $timeread);
+        \core_message\api::mark_message_as_read($USER->id, $message, $timeread);
 
         $results = array(
-            'messageid' => $messageid,
+            'messageid' => $message->id,
             'warnings' => $warnings
         );
         return $results;
@@ -1899,7 +1955,92 @@ class core_message_external extends external_api {
     public static function mark_message_read_returns() {
         return new external_single_structure(
             array(
-                'messageid' => new external_value(PARAM_INT, 'the id of the message in the message_read table'),
+                'messageid' => new external_value(PARAM_INT, 'the id of the message in the messages table'),
+                'warnings' => new external_warnings()
+            )
+        );
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     */
+    public static function mark_notification_read_parameters() {
+        return new external_function_parameters(
+            array(
+                'notificationid' => new external_value(PARAM_INT, 'id of the notification'),
+                'timeread' => new external_value(PARAM_INT, 'timestamp for when the notification should be marked read',
+                    VALUE_DEFAULT, 0)
+            )
+        );
+    }
+
+    /**
+     * Mark a single notification as read.
+     *
+     * This will trigger a 'notification_viewed' event.
+     *
+     * @param int $notificationid id of the notification
+     * @param int $timeread timestamp for when the notification should be marked read
+     * @return external_description
+     * @throws invalid_parameter_exception
+     * @throws moodle_exception
+     */
+    public static function mark_notification_read($notificationid, $timeread) {
+        global $CFG, $DB, $USER;
+
+        // Check if private messaging between users is allowed.
+        if (empty($CFG->messaging)) {
+            throw new moodle_exception('disabled', 'message');
+        }
+
+        // Warnings array, it can be empty at the end but is mandatory.
+        $warnings = array();
+
+        // Validate params.
+        $params = array(
+            'notificationid' => $notificationid,
+            'timeread' => $timeread
+        );
+        $params = self::validate_parameters(self::mark_notification_read_parameters(), $params);
+
+        if (empty($params['timeread'])) {
+            $timeread = time();
+        } else {
+            $timeread = $params['timeread'];
+        }
+
+        // Validate context.
+        $context = context_system::instance();
+        self::validate_context($context);
+
+        $notification = $DB->get_record('notifications', ['id' => $params['notificationid']], '*', MUST_EXIST);
+
+        if ($notification->useridto != $USER->id) {
+            throw new invalid_parameter_exception('Invalid notificationid, you don\'t have permissions to mark this ' .
+                'notification as read');
+        }
+
+        \core_message\api::mark_notification_as_read($notification, $timeread);
+
+        $results = array(
+            'notificationid' => $notification->id,
+            'warnings' => $warnings
+        );
+
+        return $results;
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     */
+    public static function mark_notification_read_returns() {
+        return new external_single_structure(
+            array(
+                'notificationid' => new external_value(PARAM_INT, 'id of the notification'),
                 'warnings' => new external_warnings()
             )
         );
@@ -1974,7 +2115,13 @@ class core_message_external extends external_api {
             throw new moodle_exception('accessdenied', 'admin');
         }
 
-        \core_message\api::mark_all_read_for_user($useridto, $useridfrom, MESSAGE_TYPE_MESSAGE);
+        if ($useridfrom) {
+            if ($conversationid = \core_message\api::get_conversation_between_users([$useridto, $useridfrom])) {
+                \core_message\api::mark_all_messages_as_read($useridto, $conversationid);
+            }
+        } else {
+            \core_message\api::mark_all_messages_as_read($useridto);
+        }
 
         return true;
     }
@@ -2094,7 +2241,7 @@ class core_message_external extends external_api {
      * @since 3.1
      */
     public static function delete_message($messageid, $userid, $read = true) {
-        global $CFG, $DB;
+        global $CFG;
 
         // Check if private messaging between users is allowed.
         if (empty($CFG->messaging)) {
@@ -2116,15 +2263,11 @@ class core_message_external extends external_api {
         $context = context_system::instance();
         self::validate_context($context);
 
-        $messagestable = $params['read'] ? 'message_read' : 'message';
-        $message = $DB->get_record($messagestable, array('id' => $params['messageid']), '*', MUST_EXIST);
-
         $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
         core_user::require_active_user($user);
 
-        $status = false;
-        if (message_can_delete_message($message, $user->id)) {
-            $status = message_delete_message($message, $user->id);;
+        if (\core_message\api::can_delete_message($user->id, $messageid)) {
+            $status = \core_message\api::delete_message($user->id, $messageid);
         } else {
             throw new moodle_exception('You do not have permission to delete this message');
         }
index 65349b3..b90a8ea 100644 (file)
@@ -127,7 +127,10 @@ if (!$user2realuser) {
 if (!empty($user2->id)) {
     if ($currentuser && isset($conversations[$user2->id])) {
         // Mark the conversation we are loading as read.
-        \core_message\api::mark_all_read_for_user($user1->id, $user2->id);
+        if ($conversationid = \core_message\api::get_conversation_between_users([$user1->id, $user2->id])) {
+            \core_message\api::mark_all_messages_as_read($user1->id, $conversationid);
+        }
+
         // Ensure the UI knows it's read as well.
         $conversations[$user2->id]->isread = 1;
     }
index 90f7a29..9143d13 100644 (file)
@@ -64,147 +64,6 @@ define('MESSAGE_DEFAULT_MIN_POLL_IN_SECONDS', 10);
 define('MESSAGE_DEFAULT_MAX_POLL_IN_SECONDS', 2 * MINSECS);
 define('MESSAGE_DEFAULT_TIMEOUT_POLL_IN_SECONDS', 5 * MINSECS);
 
-/**
- * Retrieve users blocked by $user1
- *
- * @param object $user1 the user whose messages are being viewed
- * @param object $user2 the user $user1 is talking to. If they are being blocked
- *                      they will have a variable called 'isblocked' added to their user object
- * @return array the users blocked by $user1
- */
-function message_get_blocked_users($user1=null, $user2=null) {
-    global $DB, $USER;
-
-    if (empty($user1)) {
-        $user1 = $USER;
-    }
-
-    if (!empty($user2)) {
-        $user2->isblocked = false;
-    }
-
-    $blockedusers = array();
-
-    $userfields = user_picture::fields('u', array('lastaccess'));
-    $blockeduserssql = "SELECT $userfields, COUNT(m.id) AS messagecount
-                          FROM {message_contacts} mc
-                          JOIN {user} u ON u.id = mc.contactid
-                          LEFT OUTER JOIN {message} m ON m.useridfrom = mc.contactid AND m.useridto = :user1id1
-                         WHERE u.deleted = 0 AND mc.userid = :user1id2 AND mc.blocked = 1
-                      GROUP BY $userfields
-                      ORDER BY u.firstname ASC";
-    $rs =  $DB->get_recordset_sql($blockeduserssql, array('user1id1' => $user1->id, 'user1id2' => $user1->id));
-
-    foreach($rs as $rd) {
-        $blockedusers[] = $rd;
-
-        if (!empty($user2) && $user2->id == $rd->id) {
-            $user2->isblocked = true;
-        }
-    }
-    $rs->close();
-
-    return $blockedusers;
-}
-
-/**
- * Retrieve $user1's contacts (online, offline and strangers)
- *
- * @param object $user1 the user whose messages are being viewed
- * @param object $user2 the user $user1 is talking to. If they are a contact
- *                      they will have a variable called 'iscontact' added to their user object
- * @return array containing 3 arrays. array($onlinecontacts, $offlinecontacts, $strangers)
- */
-function message_get_contacts($user1=null, $user2=null) {
-    global $DB, $CFG, $USER;
-
-    if (empty($user1)) {
-        $user1 = $USER;
-    }
-
-    if (!empty($user2)) {
-        $user2->iscontact = false;
-    }
-
-    $timetoshowusers = 300; //Seconds default
-    if (isset($CFG->block_online_users_timetosee)) {
-        $timetoshowusers = $CFG->block_online_users_timetosee * 60;
-    }
-
-    // time which a user is counting as being active since
-    $timefrom = time()-$timetoshowusers;
-
-    // people in our contactlist who are online
-    $onlinecontacts  = array();
-    // people in our contactlist who are offline
-    $offlinecontacts = array();
-    // people who are not in our contactlist but have sent us a message
-    $strangers       = array();
-
-    $userfields = user_picture::fields('u', array('lastaccess'));
-
-    // get all in our contactlist who are not blocked in our contact list
-    // and count messages we have waiting from each of them
-    $contactsql = "SELECT $userfields, COUNT(m.id) AS messagecount
-                     FROM {message_contacts} mc
-                     JOIN {user} u ON u.id = mc.contactid
-                     LEFT OUTER JOIN {message} m ON m.useridfrom = mc.contactid AND m.useridto = ?
-                    WHERE u.deleted = 0 AND mc.userid = ? AND mc.blocked = 0
-                 GROUP BY $userfields
-                 ORDER BY u.firstname ASC";
-
-    $rs = $DB->get_recordset_sql($contactsql, array($user1->id, $user1->id));
-    foreach ($rs as $rd) {
-        if ($rd->lastaccess >= $timefrom) {
-            // they have been active recently, so are counted online
-            $onlinecontacts[] = $rd;
-
-        } else {
-            $offlinecontacts[] = $rd;
-        }
-
-        if (!empty($user2) && $user2->id == $rd->id) {
-            $user2->iscontact = true;
-        }
-    }
-    $rs->close();
-
-    // get messages from anyone who isn't in our contact list and count the number
-    // of messages we have from each of them
-    $strangersql = "SELECT $userfields, count(m.id) as messagecount
-                      FROM {message} m
-                      JOIN {user} u  ON u.id = m.useridfrom
-                      LEFT OUTER JOIN {message_contacts} mc ON mc.contactid = m.useridfrom AND mc.userid = m.useridto
-                     WHERE u.deleted = 0 AND mc.id IS NULL AND m.useridto = ?
-                  GROUP BY $userfields
-                  ORDER BY u.firstname ASC";
-
-    $rs = $DB->get_recordset_sql($strangersql, array($USER->id));
-    // Add user id as array index, so supportuser and noreply user don't get duplicated (if they are real users).
-    foreach ($rs as $rd) {
-        $strangers[$rd->id] = $rd;
-    }
-    $rs->close();
-
-    // Add noreply user and support user to the list, if they don't exist.
-    $supportuser = core_user::get_support_user();
-    if (!isset($strangers[$supportuser->id])) {
-        $supportuser->messagecount = message_count_unread_messages($USER, $supportuser);
-        if ($supportuser->messagecount > 0) {
-            $strangers[$supportuser->id] = $supportuser;
-        }
-    }
-
-    $noreplyuser = core_user::get_noreply_user();
-    if (!isset($strangers[$noreplyuser->id])) {
-        $noreplyuser->messagecount = message_count_unread_messages($USER, $noreplyuser);
-        if ($noreplyuser->messagecount > 0) {
-            $strangers[$noreplyuser->id] = $noreplyuser;
-        }
-    }
-    return array($onlinecontacts, $offlinecontacts, $strangers);
-}
-
 /**
  * Returns the count of unread messages for user. Either from a specific user or from all users.
  *
@@ -219,15 +78,24 @@ function message_count_unread_messages($user1=null, $user2=null) {
         $user1 = $USER;
     }
 
+    $sql = "SELECT COUNT(m.id)
+              FROM {messages} m
+        INNER JOIN {message_conversations} mc
+                ON mc.id = m.conversationid
+        INNER JOIN {message_conversation_members} mcm
+                ON mcm.conversationid = mc.id
+         LEFT JOIN {message_user_actions} mua
+                ON (mua.messageid = m.id AND mua.userid = ? AND (mua.action = ? OR mua.action = ?))
+             WHERE mua.id is NULL
+               AND mcm.userid = ?";
+    $params = [$user1->id, \core_message\api::MESSAGE_ACTION_DELETED, \core_message\api::MESSAGE_ACTION_READ,  $user1->id];
+
     if (!empty($user2)) {
-        return $DB->count_records_select('message', "useridto = ? AND useridfrom = ? AND notification = 0
-            AND timeusertodeleted = 0",
-            array($user1->id, $user2->id), "COUNT('id')");
-    } else {
-        return $DB->count_records_select('message', "useridto = ? AND notification = 0
-            AND timeusertodeleted = 0",
-            array($user1->id), "COUNT('id')");
+        $sql .= " AND m.useridfrom = ?";
+        $params[] = $user2->id;
     }
+
+    return $DB->count_records_sql($sql, $params);
 }
 
 /**
@@ -249,7 +117,7 @@ function message_format_message_text($message, $forcetexttohtml = false) {
     $format = $message->fullmessageformat;
 
     if (strval($message->smallmessage) !== '') {
-        if ($message->notification == 1) {
+        if (!empty($message->notification)) {
             if (strval($message->fullmessagehtml) !== '' or strval($message->fullmessage) !== '') {
                 $format = FORMAT_PLAIN;
             }
@@ -421,86 +289,6 @@ function message_block_contact($contactid, $userid = 0) {
     return message_add_contact($contactid, 1, $userid);
 }
 
-/**
- * Checks if a user can delete a message.
- *
- * @param stdClass $message the message to delete
- * @param string $userid the user id of who we want to delete the message for (this may be done by the admin
- *  but will still seem as if it was by the user)
- * @return bool Returns true if a user can delete the message, false otherwise.
- */
-function message_can_delete_message($message, $userid) {
-    global $USER;
-
-    if ($message->useridfrom == $userid) {
-        $userdeleting = 'useridfrom';
-    } else if ($message->useridto == $userid) {
-        $userdeleting = 'useridto';
-    } else {
-        return false;
-    }
-
-    $systemcontext = context_system::instance();
-
-    // Let's check if the user is allowed to delete this message.
-    if (has_capability('moodle/site:deleteanymessage', $systemcontext) ||
-        ((has_capability('moodle/site:deleteownmessage', $systemcontext) &&
-            $USER->id == $message->$userdeleting))) {
-        return true;
-    }
-
-    return false;
-}
-
-/**
- * Deletes a message.
- *
- * This function does not verify any permissions.
- *
- * @param stdClass $message the message to delete
- * @param string $userid the user id of who we want to delete the message for (this may be done by the admin
- *  but will still seem as if it was by the user)
- * @return bool
- */
-function message_delete_message($message, $userid) {
-    global $DB;
-
-    // The column we want to alter.
-    if ($message->useridfrom == $userid) {
-        $coltimedeleted = 'timeuserfromdeleted';
-    } else if ($message->useridto == $userid) {
-        $coltimedeleted = 'timeusertodeleted';
-    } else {
-        return false;
-    }
-
-    // Don't update it if it's already been deleted.
-    if ($message->$coltimedeleted > 0) {
-        return false;
-    }
-
-    // Get the table we want to update.
-    if (isset($message->timeread)) {
-        $messagetable = 'message_read';
-    } else {
-        $messagetable = 'message';
-    }
-
-    // Mark the message as deleted.
-    $updatemessage = new stdClass();
-    $updatemessage->id = $message->id;
-    $updatemessage->$coltimedeleted = time();
-    $success = $DB->update_record($messagetable, $updatemessage);
-
-    if ($success) {
-        // Trigger event for deleting a message.
-        \core\event\message_deleted::create_from_ids($message->useridfrom, $message->useridto,
-            $userid, $messagetable, $message->id)->trigger();
-    }
-
-    return $success;
-}
-
 /**
  * Load a user's contact record
  *
@@ -722,71 +510,6 @@ function message_post_message($userfrom, $userto, $message, $format) {
     return message_send($eventdata);
 }
 
-/**
- * Moves messages from a particular user from the message table (unread messages) to message_read
- * This is typically only used when a user is deleted
- *
- * @param object $userid User id
- * @return boolean success
- */
-function message_move_userfrom_unread2read($userid) {
-    global $DB;
-
-    // move all unread messages from message table to message_read
-    if ($messages = $DB->get_records_select('message', 'useridfrom = ?', array($userid), 'timecreated')) {
-        foreach ($messages as $message) {
-            message_mark_message_read($message, 0); //set timeread to 0 as the message was never read
-        }
-    }
-    return true;
-}
-
-/**
- * Mark a single message as read
- *
- * @param stdClass $message An object with an object property ie $message->id which is an id in the message table
- * @param int $timeread the timestamp for when the message should be marked read. Usually time().
- * @param bool $messageworkingempty Is the message_working table already confirmed empty for this message?
- * @return int the ID of the message in the message_read table
- */
-function message_mark_message_read($message, $timeread, $messageworkingempty=false) {
-    global $DB;
-
-    $message->timeread = $timeread;
-
-    $messageid = $message->id;
-    unset($message->id);//unset because it will get a new id on insert into message_read
-
-    //If any processors have pending actions abort them
-    if (!$messageworkingempty) {
-        $DB->delete_records('message_working', array('unreadmessageid' => $messageid));
-    }
-    $messagereadid = $DB->insert_record('message_read', $message);
-
-    $DB->delete_records('message', array('id' => $messageid));
-
-    // Get the context for the user who received the message.
-    $context = context_user::instance($message->useridto, IGNORE_MISSING);
-    // If the user no longer exists the context value will be false, in this case use the system context.
-    if ($context === false) {
-        $context = context_system::instance();
-    }
-
-    // Trigger event for reading a message.
-    $event = \core\event\message_viewed::create(array(
-        'objectid' => $messagereadid,
-        'userid' => $message->useridto, // Using the user who read the message as they are the ones performing the action.
-        'context' => $context,
-        'relateduserid' => $message->useridfrom,
-        'other' => array(
-            'messageid' => $messageid
-        )
-    ));
-    $event->trigger();
-
-    return $messagereadid;
-}
-
 /**
  * Get all message processors, validate corresponding plugin existance and
  * system configuration
@@ -962,42 +685,108 @@ function message_get_messages($useridto, $useridfrom = 0, $notifications = -1, $
                                 $sort = 'mr.timecreated DESC', $limitfrom = 0, $limitnum = 0) {
     global $DB;
 
-    $messagetable = $read ? '{message_read}' : '{message}';
-    $params = array('deleted' => 0);
-
-    // Empty useridto means that we are going to retrieve messages send by the useridfrom to any user.
+    // If the 'useridto' value is empty then we are going to retrieve messages sent by the useridfrom to any user.
     if (empty($useridto)) {
         $userfields = get_all_user_name_fields(true, 'u', '', 'userto');
-        $joinsql = "JOIN {user} u ON u.id = mr.useridto";
-        $usersql = "mr.useridfrom = :useridfrom AND u.deleted = :deleted";
-        $params['useridfrom'] = $useridfrom;
     } else {
         $userfields = get_all_user_name_fields(true, 'u', '', 'userfrom');
+    }
+
+    // Create the SQL we will be using.
+    $messagesql = "SELECT mr.*, $userfields, 0 as notification, '' as contexturl, '' as contexturlname,
+                          mua.timecreated as timeusertodeleted, mua2.timecreated as timeread,
+                          mua3.timecreated as timeuserfromdeleted
+                     FROM {messages} mr
+               INNER JOIN {message_conversations} mc
+                       ON mc.id = mr.conversationid
+               INNER JOIN {message_conversation_members} mcm
+                       ON mcm.conversationid = mc.id ";
+
+    $notificationsql = "SELECT mr.*, $userfields, 1 as notification
+                          FROM {notifications} mr ";
+
+    $messagejoinsql = "LEFT JOIN {message_user_actions} mua
+                              ON (mua.messageid = mr.id AND mua.userid = mcm.userid AND mua.action = ?)
+                       LEFT JOIN {message_user_actions} mua2
+                              ON (mua2.messageid = mr.id AND mua2.userid = mcm.userid AND mua2.action = ?)
+                       LEFT JOIN {message_user_actions} mua3
+                              ON (mua3.messageid = mr.id AND mua3.userid = mr.useridfrom AND mua3.action = ?)";
+    $messagejoinparams = [\core_message\api::MESSAGE_ACTION_DELETED, \core_message\api::MESSAGE_ACTION_READ,
+        \core_message\api::MESSAGE_ACTION_DELETED];
+    $notificationsparams = [];
+
+    // If the 'useridto' value is empty then we are going to retrieve messages sent by the useridfrom to any user.
+    if (empty($useridto)) {
+        // Create the messaging query and params.
+        $messagesql .= "INNER JOIN {user} u
+                                ON u.id = mcm.userid
+                                $messagejoinsql
+                             WHERE mr.useridfrom = ?
+                               AND mr.useridfrom != mcm.userid
+                               AND u.deleted = 0 ";
+        $messageparams = array_merge($messagejoinparams, [$useridfrom]);
+
+        // Create the notifications query and params.
+        $notificationsql .= "INNER JOIN {user} u
+                                     ON u.id = mr.useridto
+                                  WHERE mr.useridfrom = ?
+                                    AND u.deleted = 0 ";
+        $notificationsparams[] = $useridfrom;
+    } else {
+        // Create the messaging query and params.
         // Left join because useridfrom may be -10 or -20 (no-reply and support users).
-        $joinsql = "LEFT JOIN {user} u ON u.id = mr.useridfrom";
-        $usersql = "mr.useridto = :useridto AND (u.deleted IS NULL OR u.deleted = :deleted)";
-        $params['useridto'] = $useridto;
+        $messagesql .= "LEFT JOIN {user} u
+                               ON u.id = mr.useridfrom
+                               $messagejoinsql
+                            WHERE mcm.userid = ?
+                              AND mr.useridfrom != mcm.userid
+                              AND u.deleted = 0 ";
+        $messageparams = array_merge($messagejoinparams, [$useridto]);
         if (!empty($useridfrom)) {
-            $usersql .= " AND mr.useridfrom = :useridfrom";
-            $params['useridfrom'] = $useridfrom;
+            $messagesql .= " AND mr.useridfrom = ? ";
+            $messageparams[] = $useridfrom;
+        }
+
+        // Create the notifications query and params.
+        // Left join because useridfrom may be -10 or -20 (no-reply and support users).
+        $notificationsql .= "LEFT JOIN {user} u
+                                    ON (u.id = mr.useridfrom AND u.deleted = 0)
+                                 WHERE mr.useridto = ? ";
+        $notificationsparams[] = $useridto;
+        if (!empty($useridfrom)) {
+            $notificationsql .= " AND mr.useridfrom = ? ";
+            $notificationsparams[] = $useridfrom;
         }
     }
+    if ($read) {
+        $notificationsql .= "AND mr.timeread IS NOT NULL ";
+    } else {
+        $notificationsql .= "AND mr.timeread IS NULL ";
+    }
+    $messagesql .= "ORDER BY $sort";
+    $notificationsql .= "ORDER BY $sort";
+
+    // Handle messages if needed.
+    if ($notifications === -1 || $notifications === 0) {
+        $messages = $DB->get_records_sql($messagesql, $messageparams, $limitfrom, $limitnum);
+        // Get rid of the messages that have either been read or not read depending on the value of $read.
+        $messages = array_filter($messages, function ($message) use ($read) {
+            if ($read) {
+                return !is_null($message->timeread);
+            }
 
-    // Now, if retrieve notifications, conversations or both.
-    $typesql = "";
-    if ($notifications !== -1) {
-        $typesql = "AND mr.notification = :notification";
-        $params['notification'] = ($notifications) ? 1 : 0;
+            return is_null($message->timeread);
+        });
     }
 
-    $sql = "SELECT mr.*, $userfields
-              FROM $messagetable mr
-                   $joinsql
-             WHERE $usersql
-                   $typesql
-             ORDER BY $sort";
+    // All.
+    if ($notifications === -1) {
+        return array_merge($messages, $DB->get_records_sql($notificationsql, $notificationsparams, $limitfrom, $limitnum));
+    } else if ($notifications === 1) { // Just notifications.
+        return $DB->get_records_sql($notificationsql, $notificationsparams, $limitfrom, $limitnum);
+    }
 
-    $messages = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
+    // Just messages.
     return $messages;
 }
 
index 6d076df..54afde0 100644 (file)
Binary files a/message/output/popup/amd/build/notification_repository.min.js and b/message/output/popup/amd/build/notification_repository.min.js differ
index 2fdb83e..801f9b9 100644 (file)
@@ -97,7 +97,7 @@ define(['core/ajax', 'core/notification'], function(Ajax, Notification) {
      */
     var markAsRead = function(id, timeread) {
         var args = {
-            messageid: id,
+            notificationid: id,
         };
 
         if (timeread) {
@@ -105,7 +105,7 @@ define(['core/ajax', 'core/notification'], function(Ajax, Notification) {
         }
 
         var request = {
-            methodname: 'core_message_mark_message_read',
+            methodname: 'core_message_mark_notification_read',
             args: args
         };
 
index 84826f6..775d25e 100644 (file)
@@ -69,34 +69,20 @@ class api {
             $disabled = $user->emailstop;
         }
         if ($disabled) {
-            // Notifications are disabled, no need to run giant queries.
+            // Notifications are disabled.
             return array();
         }
 
-        $sql = "SELECT r.id, r.useridfrom, r.useridto,
-                       r.subject, r.fullmessage, r.fullmessageformat,
-                       r.fullmessagehtml, r.smallmessage, r.notification, r.contexturl,
-                       r.contexturlname, r.timecreated, r.timeuserfromdeleted, r.timeusertodeleted,
-                       r.component, r.eventtype, r.timeread
-                  FROM {message_read} r
-                 WHERE r.notification = 1
-                       AND r.id IN (SELECT messageid FROM {message_popup} WHERE isread = 1)
-                       AND r.useridto = :useridto1
-             UNION ALL
-                SELECT u.id, u.useridfrom, u.useridto,
-                       u.subject, u.fullmessage, u.fullmessageformat,
-                       u.fullmessagehtml, u.smallmessage, u.notification, u.contexturl,
-                       u.contexturlname, u.timecreated, u.timeuserfromdeleted, u.timeusertodeleted,
-                       u.component, u.eventtype, 0 as timeread
-                  FROM {message} u
-                 WHERE u.notification = 1
-                       AND u.id IN (SELECT messageid FROM {message_popup} WHERE isread = 0)
-                       AND u.useridto = :useridto2
+        $sql = "SELECT n.id, n.useridfrom, n.useridto,
+                       n.subject, n.fullmessage, n.fullmessageformat,
+                       n.fullmessagehtml, n.smallmessage, n.contexturl,
+                       n.contexturlname, n.timecreated, n.component,
+                       n.eventtype, n.timeread
+                  FROM {notifications} n
+                 WHERE n.useridto = :useridto1
               ORDER BY timecreated $sort, timeread $sort, id $sort";
 
         $notifications = [];
-        // Use recordset here to ensure records with the same id aren't ignored because
-        // we can have id clashes between the message and message_read tables.
         $records = $DB->get_recordset_sql($sql, $params, $offset, $limit);
         foreach ($records as $record) {
             $notifications[] = (object) $record;
@@ -122,9 +108,9 @@ class api {
 
         return $DB->count_records_sql(
             "SELECT count(id)
-            FROM {message}
-            WHERE id IN (SELECT messageid FROM {message_popup} WHERE isread = 0)
-            AND useridto = ?",
+               FROM {notifications}
+              WHERE useridto = ?
+                AND timeread is NULL",
             [$useridto]
         );
     }
index 0644a22..0511873 100644 (file)
@@ -57,16 +57,7 @@ class popup_notification implements templatable, renderable {
     }
 
     public function export_for_template(\renderer_base $output) {
-        global $USER;
-
         $context = clone $this->notification;
-
-        if ($context->useridto == $USER->id && $context->timeusertodeleted) {
-            $context->deleted = true;
-        } else {
-            $context->deleted = false;
-        }
-
         $context->timecreatedpretty = get_string('ago', 'message', format_time(time() - $context->timecreated));
         $context->text = message_format_message_text($context);
         $context->read = $context->timeread ? true : false;
diff --git a/message/output/popup/db/install.xml b/message/output/popup/db/install.xml
deleted file mode 100644 (file)
index 6a92e56..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="message/output/popup/db" VERSION="20161221" COMMENT="XMLDB file for Moodle message/output/popup"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
->
-  <TABLES>
-    <TABLE NAME="message_popup" COMMENT="Keep state of notifications for the popup message processor">
-      <FIELDS>
-        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
-        <FIELD NAME="messageid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="isread" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
-      </FIELDS>
-      <KEYS>
-        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
-      </KEYS>
-      <INDEXES>
-        <INDEX NAME="messageid-isread" UNIQUE="true" FIELDS="messageid, isread"/>
-        <INDEX NAME="isread" UNIQUE="false" FIELDS="isread"/>
-      </INDEXES>
-    </TABLE>
-  </TABLES>
-</XMLDB>
\ No newline at end of file
index 455d89a..26f3b1e 100644 (file)
@@ -83,5 +83,16 @@ function xmldb_message_popup_upgrade($oldversion) {
     // Automatically generated Moodle v3.4.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2018022000) {
+        // Drop table that is no longer needed.
+        $table = new xmldb_table('message_popup');
+        if ($dbman->table_exists($table)) {
+            $dbman->drop_table($table);
+        }
+
+        // Popup savepoint reached.
+        upgrade_plugin_savepoint(true, 2018022000, 'message', 'popup');
+    }
+
     return true;
 }
index 87e93b0..ffbc092 100644 (file)
@@ -117,6 +117,9 @@ class message_popup_external extends external_api {
                 $notificationoutput = new \message_popup\output\popup_notification($notification);
 
                 $notificationcontext = $notificationoutput->export_for_template($renderer);
+
+                // Keep this for BC.
+                $notificationcontext->deleted = false;
                 $notificationcontexts[] = $notificationcontext;
             }
         }
index 67409cc..dde8b2c 100644 (file)
@@ -41,35 +41,6 @@ class message_output_popup extends message_output {
      * @return true if ok, false if error
      */
     public function send_message($eventdata) {
-        global $DB;
-
-        //hold onto the popup processor id because /admin/cron.php sends a lot of messages at once
-        static $processorid = null;
-
-        //prevent users from getting popup notifications of messages to themselves (happens with forum notifications)
-        if ($eventdata->userfrom->id != $eventdata->userto->id) {
-            if (empty($processorid)) {
-                $processor = $DB->get_record('message_processors', array('name'=>'popup'));
-                $processorid = $processor->id;
-            }
-            $procmessage = new stdClass();
-            $procmessage->unreadmessageid = $eventdata->savedmessageid;
-            $procmessage->processorid     = $processorid;
-
-            //save this message for later delivery
-            $DB->insert_record('message_working', $procmessage);
-
-            if ($eventdata->notification) {
-                if (!$DB->record_exists('message_popup', ['messageid' => $eventdata->savedmessageid, 'isread' => 0])) {
-                    $record = new StdClass();
-                    $record->messageid = $eventdata->savedmessageid;
-                    $record->isread = 0;
-
-                    $DB->insert_record('message_popup', $record);
-                }
-            }
-        }
-
         return true;
     }
 
@@ -113,22 +84,6 @@ class message_output_popup extends message_output {
         return false;
     }
 
-    /**
-     * Handles the message_viewed event to keep data in sync.
-     *
-     * @param \core\event\base $event The event data
-     */
-    public static function message_viewed(\core\event\base $event) {
-        global $DB;
-
-        if ($record = $DB->get_record('message_popup', ['messageid' => $event->other['messageid']])) {
-            // The id can change when the moving to the message_read table.
-            $record->messageid = $event->objectid;
-            $record->isread = 1;
-            $DB->update_record('message_popup', $record);
-        }
-    }
-
     /**
      * Determines if this processor should process a message regardless of user preferences or site settings.
      *
index 2d082ae..338a6c0 100644 (file)
@@ -50,15 +50,7 @@ trait message_popup_test_helper {
         $record->smallmessage = $message;
         $record->timecreated = $timecreated ? $timecreated : time();
 
-        $id = $DB->insert_record('message', $record);
-
-        $popup = new stdClass();
-        $popup->messageid = $id;
-        $popup->isread = 0;
-
-        $DB->insert_record('message_popup', $popup);
-
-        return $id;
+        return $DB->insert_record('notifications', $record);
     }
 
     /**
@@ -89,14 +81,11 @@ trait message_popup_test_helper {
         $record->timecreated = $timecreated ? $timecreated : time();
         $record->timeread = $timeread ? $timeread : time();
 
-        $id = $DB->insert_record('message_read', $record);
-
-        $popup = new stdClass();
-        $popup->messageid = $id;
-        $popup->isread = 1;
+        $record->id = $DB->insert_record('notifications', $record);
 
-        $DB->insert_record('message_popup', $popup);
+        // Mark it as read.
+        \core_message\api::mark_notification_as_read($record);
 
-        return $id;
+        return $record->id;
     }
 }
index fdee795..67289a2 100644 (file)
@@ -13,7 +13,7 @@ Feature: Message popover unread messages
     And I send "Test message" message to "Student 1" user
     And I log out
 
-  Scenario: Notification popover shows correct unread count
+  Scenario: Message popover shows correct unread count
     When I log in as "student2"
     And I send "Test message 2" message to "Student 1" user
     And I log out
@@ -38,7 +38,7 @@ Feature: Message popover unread messages
     # Confirm the message was loaded in the messaging page.
     And I should see "Test message" in the "[data-region='message-text']" "css_element"
 
-  Scenario: Mark all notifications as read
+  Scenario: Mark all messages as read
     When I log in as "student1"
     # Open the popover.
     And I open the message popover
index 94b0a2a..048daf7 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2018022000;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2017110800;        // Requires this Moodle version
 $plugin->component = 'message_popup';  // Full name of the plugin (used for diagnostics)
index 7139800..60a5bb7 100644 (file)
@@ -51,6 +51,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $this->send_fake_message($sender, $recipient);
 
         \core_message\api::mark_all_read_for_user($recipient->id);
+        $this->assertDebuggingCalled();
         $this->assertEquals(message_count_unread_messages($recipient), 0);
     }
 
@@ -73,6 +74,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $this->send_fake_message($sender2, $recipient);
 
         \core_message\api::mark_all_read_for_user($recipient->id, $sender1->id);
+        $this->assertDebuggingCalled();
         $this->assertEquals(message_count_unread_messages($recipient), 3);
     }
 
@@ -88,9 +90,11 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $this->send_fake_message($sender, $recipient);
 
         \core_message\api::mark_all_read_for_user($recipient->id, 0, MESSAGE_TYPE_NOTIFICATION);
+        $this->assertDebuggingCalled();
         $this->assertEquals(message_count_unread_messages($recipient), 3);
 
         \core_message\api::mark_all_read_for_user($recipient->id, 0, MESSAGE_TYPE_MESSAGE);
+        $this->assertDebuggingCalled();
         $this->assertEquals(message_count_unread_messages($recipient), 0);
     }
 
@@ -631,7 +635,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                             'messageposition'   => 0,
                             'with'              => 'user1',
                             'subject'           => 'S2',
-                            'unreadcount'       => 2,
+                            'unreadcount'       => 0, // Messages sent to and from the same user are counted as read.
                         ),
                     ),
                 ),
@@ -872,11 +876,9 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
             $subject    = $messagedata['subject'];
 
             if (isset($messagedata['state']) && $messagedata['state'] == 'unread') {
-                $table = 'message';
                 $messageid = $this->send_fake_message($from, $to, $subject);
             } else {
                 // If there is no state, or the state is not 'unread', assume the message is read.
-                $table = 'message_read';
                 $messageid = message_post_message($from, $to, $subject, FORMAT_PLAIN);
             }
 
@@ -890,7 +892,7 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
                 $updatemessage->timecreated = $defaulttimecreated;
             }
 
-            $DB->update_record($table, $updatemessage);
+            $DB->update_record('messages', $updatemessage);
         }
 
         foreach ($expectations as $username => $data) {
@@ -1155,34 +1157,39 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
 
         // Send some messages back and forth.
         $time = 1;
-        $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1);
-        $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2);
-        $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3);
-        $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4);
+        $m1id = $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1);
+        $m2id = $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2);
+        $m3id = $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3);
+        $m4id = $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4);
 
         // Delete the conversation as user 1.
         \core_message\api::delete_conversation($user1->id, $user2->id);
 
-        $messages = $DB->get_records('message', array(), 'timecreated ASC');
-        $this->assertCount(4, $messages);
-
-        $message1 = array_shift($messages);
-        $message2 = array_shift($messages);
-        $message3 = array_shift($messages);
-        $message4 = array_shift($messages);
+        $muas = $DB->get_records('message_user_actions', array(), 'timecreated ASC');
+        $this->assertCount(4, $muas);
+        // Sort by id.
+        ksort($muas);
 
-        $this->assertNotEmpty($message1->timeuserfromdeleted);
-        $this->assertEmpty($message1->timeusertodeleted);
+        $mua1 = array_shift($muas);
+        $mua2 = array_shift($muas);
+        $mua3 = array_shift($muas);
+        $mua4 = array_shift($muas);
 
-        $this->assertEmpty($message2->timeuserfromdeleted);
-        $this->assertNotEmpty($message2->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua1->userid);
+        $this->assertEquals($m1id, $mua1->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua1->action);
 
-        $this->assertNotEmpty($message3->timeuserfromdeleted);
-        $this->assertEmpty($message3->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua2->userid);
+        $this->assertEquals($m2id, $mua2->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua2->action);
 
-        $this->assertEmpty($message4->timeuserfromdeleted);
-        $this->assertNotEmpty($message4->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua3->userid);
+        $this->assertEquals($m3id, $mua3->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua3->action);
 
+        $this->assertEquals($user1->id, $mua4->userid);
+        $this->assertEquals($m4id, $mua4->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua4->action);
     }
 
     /**
@@ -1602,4 +1609,335 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
         $this->assertContains('Message 2', $message1->text);
         $this->assertContains('Message 3', $message2->text);
     }
+
+    /**
+     * Test returning blocked users.
+     */
+    public function test_get_blocked_users() {
+        global $USER;
+
+        // Set this user as the admin.
+        $this->setAdminUser();
+
+        // Create a user to add to the admin's contact list.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        // Add users to the admin's contact list.
+        message_add_contact($user1->id);
+        message_add_contact($user2->id, 1);
+
+        $this->assertCount(1, \core_message\api::get_blocked_users($USER->id));
+
+        // Block other user.
+        message_block_contact($user1->id);
+        $this->assertCount(2, \core_message\api::get_blocked_users($USER->id));
+
+        // Test deleting users.
+        delete_user($user1);
+        $this->assertCount(1, \core_message\api::get_blocked_users($USER->id));
+    }
+
+    /**
+     * Test returning contacts with unread message count.
+     */
+    public function test_get_contacts_with_unread_message_count() {
+        global $DB;
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+        $user3 = self::getDataGenerator()->create_user();
+        $user4 = self::getDataGenerator()->create_user();
+
+        // Add the users to each of their contacts.
+        message_add_contact($user1->id, 0, $user2->id);
+        message_add_contact($user2->id, 0, $user1->id);
+        message_add_contact($user3->id, 0, $user2->id);
+
+        $this->send_fake_message($user1, $user2);
+        $this->send_fake_message($user1, $user2);
+        $this->send_fake_message($user1, $user2);
+        $message4id = $this->send_fake_message($user1, $user2);
+
+        $this->send_fake_message($user3, $user2);
+        $message6id = $this->send_fake_message($user3, $user2);
+        $this->send_fake_message($user3, $user2);
+        $this->send_fake_message($user3, $user2);
+        $this->send_fake_message($user3, $user2);
+
+        // Send a message that should never be included as the user is not a contact.
+        $this->send_fake_message($user4, $user2);
+
+        // 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);
+
+        $messageinfo1 = array_shift($messages);
+        $messageinfo2 = array_shift($messages);
+        $this->assertEquals($user1->id, $messageinfo1->id);
+        $this->assertEquals(4, $messageinfo1->messagecount);
+        $this->assertEquals($user3->id, $messageinfo2->id);
+        $this->assertEquals(5, $messageinfo2->messagecount);
+
+        // Mark some of the messages as read.
+        $m4 = $DB->get_record('messages', ['id' => $message4id]);
+        $m6 = $DB->get_record('messages', ['id' => $message6id]);
+        \core_message\api::mark_message_as_read($user2->id, $m4);
+        \core_message\api::mark_message_as_read($user2->id, $m6);
+
+        // 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);
+
+        // Confirm read messages are not included.
+        $messageinfo1 = array_shift($messages);
+        $messageinfo2 = array_shift($messages);
+        $this->assertEquals($user1->id, $messageinfo1->id);
+        $this->assertEquals(3, $messageinfo1->messagecount);
+        $this->assertEquals($user3->id, $messageinfo2->id);
+        $this->assertEquals(4, $messageinfo2->messagecount);
+
+        // Now, let's populate the database with messages from user2 to user 1.
+        $this->send_fake_message($user2, $user1);
+        $this->send_fake_message($user2, $user1);
+        $messageid = $this->send_fake_message($user2, $user1);
+
+        // Send a message that should never be included as the user is not a contact.
+        $this->send_fake_message($user4, $user1);
+
+        // Get the contacts and the unread message count.
+        $messages = \core_message\api::get_contacts_with_unread_message_count($user1->id);
+
+        // Confirm the size is correct.
+        $this->assertCount(1, $messages);
+        $messageinfo1 = array_shift($messages);
+        $this->assertEquals($user2->id, $messageinfo1->id);
+        $this->assertEquals(3, $messageinfo1->messagecount);
+
+        // Mark the last message as read.
+        $m = $DB->get_record('messages', ['id' => $messageid]);
+        \core_message\api::mark_message_as_read($user1->id, $m);
+
+        $messages = \core_message\api::get_contacts_with_unread_message_count($user1->id);
+
+        // Confirm the size is correct.
+        $this->assertCount(1, $messages);
+
+        // Confirm read messages are not included.
+        $messageinfo1 = array_shift($messages);
+        $this->assertEquals($user2->id, $messageinfo1->id);
+        $this->assertEquals(2, $messageinfo1->messagecount);
+    }
+
+    /**
+     * Test returning contacts with unread message count when there are no messages.
+     */
+    public function test_get_contacts_with_unread_message_count_no_messages() {
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        // Add the users to each of their contacts.
+        message_add_contact($user1->id, 0, $user2->id);
+
+        // Check we get the correct message count.
+        $messages = \core_message\api::get_contacts_with_unread_message_count($user2->id);
+
+        // Confirm the size is correct.
+        $this->assertCount(1, $messages);
+
+        $messageinfo = array_shift($messages);
+
+        $this->assertEquals($user1->id, $messageinfo->id);
+        $this->assertEquals(0, $messageinfo->messagecount);
+    }
+
+    /**
+     * Test returning non-contacts with unread message count.
+     */
+    public function test_get_non_contacts_with_unread_message_count() {
+        global $DB;
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+        $user3 = self::getDataGenerator()->create_user();
+        $user4 = self::getDataGenerator()->create_user();
+
+        // Add a user to the contact list of the users we are testing this function with.
+        message_add_contact($user4->id, 0, $user1->id);
+        message_add_contact($user4->id, 0, $user2->id);
+
+        $this->send_fake_message($user1, $user2);
+        $this->send_fake_message($user1, $user2);
+        $this->send_fake_message($user1, $user2);
+        $message4id = $this->send_fake_message($user1, $user2);
+
+        $this->send_fake_message($user3, $user2);
+        $message6id = $this->send_fake_message($user3, $user2);
+        $this->send_fake_message($user3, $user2);
+        $this->send_fake_message($user3, $user2);
+        $this->send_fake_message($user3, $user2);
+
+        // Send a message that should never be included as the user is a contact.
+        $this->send_fake_message($user4, $user2);
+
+        // Get the non-contacts and the unread message count.
+        $messages = \core_message\api::get_non_contacts_with_unread_message_count($user2->id);
+
+        // Check we get the correct message count.
+        ksort($messages);
+        $this->assertCount(2, $messages);
+        $messageinfo1 = array_shift($messages);
+        $messageinfo2 = array_shift($messages);
+        $this->assertEquals($user1->id, $messageinfo1->id);
+        $this->assertEquals(4, $messageinfo1->messagecount);
+        $this->assertEquals($user3->id, $messageinfo2->id);
+        $this->assertEquals(5, $messageinfo2->messagecount);
+
+        // Mark some of the messages as read.
+        $m4 = $DB->get_record('messages', ['id' => $message4id]);
+        $m6 = $DB->get_record('messages', ['id' => $message6id]);
+        \core_message\api::mark_message_as_read($user2->id, $m4);
+        \core_message\api::mark_message_as_read($user2->id, $m6);
+
+        // Get the non-contacts and the unread message count.
+        $messages = \core_message\api::get_non_contacts_with_unread_message_count($user2->id);
+
+        // Check the marked message is not returned in the message count.
+        ksort($messages);
+        $this->assertCount(2, $messages);
+        $messageinfo1 = array_shift($messages);
+        $messageinfo2 = array_shift($messages);
+        $this->assertEquals($user1->id, $messageinfo1->id);
+        $this->assertEquals(3, $messageinfo1->messagecount);
+        $this->assertEquals($user3->id, $messageinfo2->id);
+        $this->assertEquals(4, $messageinfo2->messagecount);
+
+        // Now, let's populate the database with messages from user2 to user 1.
+        $this->send_fake_message($user2, $user1);
+        $this->send_fake_message($user2, $user1);
+        $messageid = $this->send_fake_message($user2, $user1);
+
+        // Send a message that should never be included as the user is a contact.
+        $this->send_fake_message($user4, $user1);
+
+        // Get the non-contacts and the unread message count.
+        $messages = \core_message\api::get_non_contacts_with_unread_message_count($user1->id);
+
+        // Confirm the size is correct.
+        $this->assertCount(1, $messages);
+        $messageinfo1 = array_shift($messages);
+        $this->assertEquals($user2->id, $messageinfo1->id);
+        $this->assertEquals(3, $messageinfo1->messagecount);
+
+        // Mark the last message as read.
+        $m = $DB->get_record('messages', ['id' => $messageid]);
+        \core_message\api::mark_message_as_read($user1->id, $m);
+
+        // Get the non-contacts and the unread message count.
+        $messages = \core_message\api::get_non_contacts_with_unread_message_count($user1->id);
+
+        // Check the marked message is not returned in the message count.
+        $this->assertCount(1, $messages);
+        $messageinfo1 = array_shift($messages);
+        $this->assertEquals($user2->id, $messageinfo1->id);
+        $this->assertEquals(2, $messageinfo1->messagecount);
+    }
+
+    /**
+     * Test marking a message as read.
+     */
+    public function test_mark_message_as_read() {
+        global $DB;
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        $this->send_fake_message($user1, $user2);
+        $m2id = $this->send_fake_message($user1, $user2);
+        $this->send_fake_message($user2, $user1);
+        $m4id = $this->send_fake_message($user2, $user1);
+
+        $m2 = $DB->get_record('messages', ['id' => $m2id]);
+        $m4 = $DB->get_record('messages', ['id' => $m4id]);
+        \core_message\api::mark_message_as_read($user2->id, $m2, 11);
+        \core_message\api::mark_message_as_read($user1->id, $m4, 12);
+
+        // Confirm there are two user actions.
+        $muas = $DB->get_records('message_user_actions', [], 'timecreated ASC');
+        $this->assertEquals(2, count($muas));
+
+        // Confirm they are correct.
+        $mua1 = array_shift($muas);
+        $mua2 = array_shift($muas);
+
+        // Confirm first action.
+        $this->assertEquals($user2->id, $mua1->userid);
+        $this->assertEquals($m2id, $mua1->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_READ, $mua1->action);
+        $this->assertEquals(11, $mua1->timecreated);
+
+        // Confirm second action.
+        $this->assertEquals($user1->id, $mua2->userid);
+        $this->assertEquals($m4id, $mua2->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_READ, $mua2->action);
+        $this->assertEquals(12, $mua2->timecreated);
+    }
+
+    /**
+     * Test marking a notification as read.
+     */
+    public function test_mark_notification_as_read() {
+        global $DB;
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        $this->send_fake_message($user1, $user2, 'Notification 1', 1);
+        $n2id = $this->send_fake_message($user1, $user2, 'Notification 2', 1);
+        $this->send_fake_message($user2, $user1, 'Notification 3', 1);
+        $n4id = $this->send_fake_message($user2, $user1, 'Notification 4', 1);
+
+        $n2 = $DB->get_record('notifications', ['id' => $n2id]);
+        $n4 = $DB->get_record('notifications', ['id' => $n4id]);
+
+        \core_message\api::mark_notification_as_read($n2, 11);
+        \core_message\api::mark_notification_as_read($n4, 12);
+
+        // Retrieve the notifications.
+        $n2 = $DB->get_record('notifications', ['id' => $n2id]);
+        $n4 = $DB->get_record('notifications', ['id' => $n4id]);
+
+        // Confirm they have been marked as read.
+        $this->assertEquals(11, $n2->timeread);
+        $this->assertEquals(12, $n4->timeread);
+    }
+
+    /**
+     * Test a conversation is not returned if there is none.
+     */
+    public function test_get_conversation_between_users_no_conversation() {
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        $this->assertFalse(\core_message\api::get_conversation_between_users([$user1->id, $user2->id]));
+    }
+
+    /**
+     * Test we can return a conversation that exists between users.
+     */
+    public function test_get_conversation_between_users_with_existing_conversation() {
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        $conversationid = \core_message\api::create_conversation_between_users([$user1->id, $user2->id]);
+
+        $this->assertEquals($conversationid,
+            \core_message\api::get_conversation_between_users([$user1->id, $user2->id]));
+    }
 }
index 4006738..0f31ae0 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-class core_message_events_testcase extends advanced_testcase {
+global $CFG;
+
+require_once($CFG->dirroot . '/message/tests/messagelib_test.php');
+
+/**
+ * Class containing the tests for message related events.
+ *
+ * @package core_message
+ * @category test
+ * @copyright 2014 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_message_events_testcase extends core_message_messagelib_testcase {
 
     /**
      * Test set up.
@@ -197,11 +209,11 @@ class core_message_events_testcase extends advanced_testcase {
      */
     public function test_message_sent() {
         $event = \core\event\message_sent::create(array(
+            'objectid' => 3,
             'userid' => 1,
             'context'  => context_system::instance(),
             'relateduserid' => 2,
             'other' => array(
-                'messageid' => 3,
                 'courseid' => 4
             )
         ));
@@ -219,7 +231,7 @@ class core_message_events_testcase extends advanced_testcase {
         $this->assertEventLegacyLogData($expected, $event);
         $url = new moodle_url('/message/index.php', array('user1' => $event->userid, 'user2' => $event->relateduserid));
         $this->assertEquals($url, $event->get_url());
-        $this->assertEquals(3, $event->other['messageid']);
+        $this->assertEquals(3, $event->objectid);
         $this->assertEquals(4, $event->other['courseid']);
     }
 
@@ -256,7 +268,7 @@ class core_message_events_testcase extends advanced_testcase {
         $this->assertEventLegacyLogData($expected, $event);
         $url = new moodle_url('/message/index.php', array('user1' => $event->userid, 'user2' => $event->relateduserid));
         $this->assertEquals($url, $event->get_url());
-        $this->assertEquals(3, $event->other['messageid']);
+        $this->assertEquals(3, $event->objectid);
         $this->assertEquals(4, $event->other['courseid']);
     }
 
@@ -276,32 +288,34 @@ class core_message_events_testcase extends advanced_testcase {
         $this->assertEquals(SITEID, $event->other['courseid']);
     }
 
-
-
-
     /**
      * Test the message viewed event.
      */
     public function test_message_viewed() {
         global $DB;
 
-        // Create a message to mark as read.
-        $message = new stdClass();
-        $message->useridfrom = '1';
-        $message->useridto = '2';
-        $message->subject = 'Subject';
-        $message->message = 'Message';
-        $message->id = $DB->insert_record('message', $message);
+        // Create users to send messages between.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $messageid = $this->send_fake_message($user1, $user2);
 
         // Trigger and capture the event.
         $sink = $this->redirectEvents();
-        message_mark_message_read($message, time());
+        $message = $DB->get_record('messages', ['id' => $messageid]);
+        \core_message\api::mark_message_as_read($user1->id, $message);
         $events = $sink->get_events();
         $event = reset($events);
 
+        // Get the usage action.
+        $mua = $DB->get_record('message_user_actions', ['userid' => $user1->id, 'messageid' => $messageid,
+            'action' => \core_message\api::MESSAGE_ACTION_READ]);
+
         // Check that the event data is valid.
         $this->assertInstanceOf('\core\event\message_viewed', $event);
-        $this->assertEquals(context_user::instance(2), $event->get_context());
+        $this->assertEquals(context_user::instance($user1->id), $event->get_context());
+        $this->assertEquals($mua->id, $event->objectid);
+        $this->assertEquals($messageid, $event->other['messageid']);
         $url = new moodle_url('/message/index.php', array('user1' => $event->userid, 'user2' => $event->relateduserid));
         $this->assertEquals($url, $event->get_url());
     }
@@ -312,56 +326,54 @@ class core_message_events_testcase extends advanced_testcase {
     public function test_message_deleted() {
         global $DB;
 
-        // Create a message.
-        $message = new stdClass();
-        $message->useridfrom = '1';
-        $message->useridto = '2';
-        $message->subject = 'Subject';
-        $message->message = 'Message';
-        $message->timeuserfromdeleted = 0;
-        $message->timeusertodeleted = 0;
-        $message->id = $DB->insert_record('message', $message);
+        // Create users to send messages between.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $messageid = $this->send_fake_message($user1, $user2);
 
         // Trigger and capture the event.
         $sink = $this->redirectEvents();
-        message_delete_message($message, $message->useridfrom);
+        \core_message\api::delete_message($user1->id, $messageid);
         $events = $sink->get_events();
         $event = reset($events);
 
+        // Get the usage action.
+        $mua = $DB->get_record('message_user_actions', ['userid' => $user1->id, 'messageid' => $messageid,
+            'action' => \core_message\api::MESSAGE_ACTION_DELETED]);
+
         // Check that the event data is valid.
         $this->assertInstanceOf('\core\event\message_deleted', $event);
-        $this->assertEquals($message->useridfrom, $event->userid); // The user who deleted it.
-        $this->assertEquals($message->useridto, $event->relateduserid);
-        $this->assertEquals('message', $event->other['messagetable']);
-        $this->assertEquals($message->id, $event->other['messageid']);
-        $this->assertEquals($message->useridfrom, $event->other['useridfrom']);
-        $this->assertEquals($message->useridto, $event->other['useridto']);
+        $this->assertEquals($user1->id, $event->userid); // The user who deleted it.
+        $this->assertEquals($user2->id, $event->relateduserid);
+        $this->assertEquals($mua->id, $event->objectid);
+        $this->assertEquals($messageid, $event->other['messageid']);
+        $this->assertEquals($user1->id, $event->other['useridfrom']);
+        $this->assertEquals($user2->id, $event->other['useridto']);
 
         // Create a read message.
-        $message = new stdClass();
-        $message->useridfrom = '2';
-        $message->useridto = '1';
-        $message->subject = 'Subject';
-        $message->message = 'Message';
-        $message->timeuserfromdeleted = 0;
-        $message->timeusertodeleted = 0;
-        $message->timeread = time();
-        $message->id = $DB->insert_record('message_read', $message);
+        $messageid = $this->send_fake_message($user1, $user2);
+        $m = $DB->get_record('messages', ['id' => $messageid]);
+        \core_message\api::mark_message_as_read($user2->id, $m);
 
         // Trigger and capture the event.
         $sink = $this->redirectEvents();
-        message_delete_message($message, $message->useridto);
+        \core_message\api::delete_message($user2->id, $messageid);
         $events = $sink->get_events();
         $event = reset($events);
 
+        // Get the usage action.
+        $mua = $DB->get_record('message_user_actions', ['userid' => $user2->id, 'messageid' => $messageid,
+            'action' => \core_message\api::MESSAGE_ACTION_DELETED]);
+
         // Check that the event data is valid.
         $this->assertInstanceOf('\core\event\message_deleted', $event);
-        $this->assertEquals($message->useridto, $event->userid);
-        $this->assertEquals($message->useridfrom, $event->relateduserid);
-        $this->assertEquals('message_read', $event->other['messagetable']);
-        $this->assertEquals($message->id, $event->other['messageid']);
-        $this->assertEquals($message->useridfrom, $event->other['useridfrom']);
-        $this->assertEquals($message->useridto, $event->other['useridto']);
+        $this->assertEquals($user2->id, $event->userid);
+        $this->assertEquals($user1->id, $event->relateduserid);
+        $this->assertEquals($mua->id, $event->objectid);
+        $this->assertEquals($messageid, $event->other['messageid']);
+        $this->assertEquals($user1->id, $event->other['useridfrom']);
+        $this->assertEquals($user2->id, $event->other['useridto']);
     }
 
     /**
@@ -370,67 +382,135 @@ class core_message_events_testcase extends advanced_testcase {
     public function test_message_deleted_whole_conversation() {
         global $DB;
 
-        // Create a message.
-        $message = new stdClass();
-        $message->useridfrom = '1';
-        $message->useridto = '2';
-        $message->subject = 'Subject';
-        $message->message = 'Message';
-        $message->timeuserfromdeleted = 0;
-        $message->timeusertodeleted = 0;
-        $message->timecreated = 1;
-
-        $messages = [];
-        // Send this a few times.
-        $messages[] = $DB->insert_record('message', $message);
+        // Create some users.
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
 
-        $message->timecreated++;
-        $messages[] = $DB->insert_record('message', $message);
-
-        $message->timecreated++;
-        $messages[] = $DB->insert_record('message', $message);
-
-        $message->timecreated++;
-        $messages[] = $DB->insert_record('message', $message);
-
-        // Create a read message.
-        $message->timeread = time();
+        // The person doing the search.
+        $this->setUser($user1);
 
-        // Send this a few times.
-        $message->timecreated++;
-        $messages[] = $DB->insert_record('message_read', $message);
-
-        $message->timecreated++;
-        $messages[] = $DB->insert_record('message_read', $message);
-
-        $message->timecreated++;
-        $messages[] = $DB->insert_record('message_read', $message);
-
-        $message->timecreated++;
-        $messages[] = $DB->insert_record('message_read', $message);
+        // Send some messages back and forth.
+        $time = 1;
+        $messages = [];
+        $messages[] = $this->send_fake_message($user1, $user2, 'Yo!', 0, $time + 1);
+        $messages[] = $this->send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2);
+        $messages[] = $this->send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3);
+        $messages[] = $this->send_fake_message($user2, $user1, 'Word.', 0, $time + 4);
+        $messages[] = $this->send_fake_message($user1, $user2, 'You doing much?', 0, $time + 5);
+        $messages[] = $this->send_fake_message($user2, $user1, 'Nah', 0, $time + 6);
+        $messages[] = $this->send_fake_message($user1, $user2, 'You nubz0r!', 0, $time + 7);
+        $messages[] = $this->send_fake_message($user2, $user1, 'Ouch.', 0, $time + 8);
+
+        // Mark the last 4 messages as read.
+        $m5 = $DB->get_record('messages', ['id' => $messages[4]]);
+        $m6 = $DB->get_record('messages', ['id' => $messages[5]]);
+        $m7 = $DB->get_record('messages', ['id' => $messages[6]]);
+        $m8 = $DB->get_record('messages', ['id' => $messages[7]]);
+        \core_message\api::mark_message_as_read($user2->id, $m5);
+        \core_message\api::mark_message_as_read($user1->id, $m6);
+        \core_message\api::mark_message_as_read($user2->id, $m7);
+        \core_message\api::mark_message_as_read($user1->id, $m8);
 
         // Trigger and capture the event.
         $sink = $this->redirectEvents();
-        \core_message\api::delete_conversation(1, 2);
+        \core_message\api::delete_conversation($user1->id, $user2->id);
         $events = $sink->get_events();
 
+        // Get the user actions for the messages deleted by that user.
+        $muas = $DB->get_records('message_user_actions', ['userid' => $user1->id,
+            'action' => \core_message\api::MESSAGE_ACTION_DELETED], 'timecreated ASC');
+        $this->assertCount(8, $muas);
+
+        // Create a list we can use for testing.
+        $muatest = [];
+        foreach ($muas as $mua) {
+            $muatest[$mua->messageid] = $mua;
+        }
+
         // Check that there were the correct number of events triggered.
         $this->assertEquals(8, count($events));
 
         // Check that the event data is valid.
-        $i = 0;
+        $i = 1;
         foreach ($events as $event) {
-            $table = ($i > 3) ? 'message_read' : 'message';
+            $useridfromid = ($i % 2 == 0) ? $user2->id : $user1->id;
+            $useridtoid = ($i % 2 == 0) ? $user1->id : $user2->id;
+            $messageid = $messages[$i - 1];
 
             $this->assertInstanceOf('\core\event\message_deleted', $event);
-            $this->assertEquals($message->useridfrom, $event->userid);
-            $this->assertEquals($message->useridto, $event->relateduserid);
-            $this->assertEquals($table, $event->other['messagetable']);
-            $this->assertEquals($messages[$i], $event->other['messageid']);
-            $this->assertEquals($message->useridfrom, $event->other['useridfrom']);
-            $this->assertEquals($message->useridto, $event->other['useridto']);
+
+            $this->assertEquals($muatest[$messageid]->id, $event->objectid);
+            $this->assertEquals($user1->id, $event->userid);
+            $this->assertEquals($user2->id, $event->relateduserid);
+            $this->assertEquals($messageid, $event->other['messageid']);
+            $this->assertEquals($useridfromid, $event->other['useridfrom']);
+            $this->assertEquals($useridtoid, $event->other['useridto']);
 
             $i++;
         }
     }
+
+    /**
+     * Test the notification sent event.
+     */
+    public function test_notification_sent() {
+        // Create a course.
+        $course = $this->getDataGenerator()->create_course();
+
+        // Create users to send notification between.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        // Send a notification.
+        $notificationid = $this->send_fake_message($user1, $user2, 'Hello world!', 1);
+
+        // Containing courseid.
+        $event = \core\event\notification_sent::create_from_ids($user1->id, $user2->id, $notificationid, $course->id);
+
+        // Trigger and capturing the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\notification_sent', $event);
+        $this->assertEquals($notificationid, $event->objectid);
+        $this->assertEquals($user1->id, $event->userid);
+        $this->assertEquals($user2->id, $event->relateduserid);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+        $this->assertEquals($course->id, $event->other['courseid']);
+        $url = new moodle_url('/message/output/popup/notifications.php', array('notificationid' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
+    }
+
+    /**
+     * Test the notification viewed event.
+     */
+    public function test_notification_viewed() {
+        global $DB;
+
+        // Create users to send notifications between.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        // Send a notification.
+        $notificationid = $this->send_fake_message($user1, $user2, 'Hello world!', 1);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $notification = $DB->get_record('notifications', ['id' => $notificationid]);
+        \core_message\api::mark_notification_as_read($notification);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\notification_viewed', $event);
+        $this->assertEquals($notificationid, $event->objectid);
+        $this->assertEquals($user2->id, $event->userid);
+        $this->assertEquals($user1->id, $event->relateduserid);
+        $this->assertEquals(context_user::instance($user2->id), $event->get_context());
+        $url = new moodle_url('/message/output/popup/notifications.php', array('notificationid' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
+    }
 }
index b7392f1..f5475d7 100644 (file)
@@ -61,16 +61,33 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
             $time = time();
         }
 
+        if ($notification) {
+            $record = new stdClass();
+            $record->useridfrom = $userfrom->id;
+            $record->useridto = $userto->id;
+            $record->subject = 'No subject';
+            $record->fullmessage = $message;
+            $record->smallmessage = $message;
+            $record->timecreated = $time;
+
+            return $DB->insert_record('notifications', $record);
+        }
+
+        if (!$conversationid = \core_message\api::get_conversation_between_users([$userfrom->id, $userto->id])) {
+            $conversationid = \core_message\api::create_conversation_between_users([$userfrom->id,
+                $userto->id]);
+        }
+
+        // Ok, send the message.
         $record = new stdClass();
         $record->useridfrom = $userfrom->id;
-        $record->useridto = $userto->id;
+        $record->conversationid = $conversationid;
         $record->subject = 'No subject';
-        $record->smallmessage = $message;
         $record->fullmessage = $message;
+        $record->smallmessage = $message;
         $record->timecreated = $time;
-        $record->notification = $notification;
 
-        return $DB->insert_record('message', $record);
+        return $DB->insert_record('messages', $record);
     }
 
     /**
@@ -110,7 +127,15 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         // 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);
 
-        $themessage = $DB->get_record('message', array('id' => $sentmessages[0]['msgid']));
+        $sql = "SELECT m.*, mcm.userid as useridto
+                 FROM {messages} m
+           INNER JOIN {message_conversations} mc
+                   ON m.conversationid = mc.id
+           INNER JOIN {message_conversation_members} mcm
+                   ON mcm.conversationid = mc.id
+                WHERE mcm.userid != ?
+                  AND m.id = ?";
+        $themessage = $DB->get_record_sql($sql, [$USER->id, $sentmessages[0]['msgid']]);
 
         // Confirm that the message was inserted correctly.
         $this->assertEquals($themessage->useridfrom, $USER->id);
@@ -465,8 +490,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
 
         // Delete the message.
         $message = array_shift($messages['messages']);
-        $messagetobedeleted = $DB->get_record('message_read', array('id' => $message['id']));
-        message_delete_message($messagetobedeleted, $user1->id);
+        \core_message\api::delete_message($user1->id, $message['id']);
 
         $messages = core_message_external::get_messages($user2->id, $user1->id, 'conversations', true, true, 0, 0);
         $messages = external_api::clean_returnvalue(core_message_external::get_messages_returns(), $messages);
@@ -495,8 +519,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
 
         // Delete the message.
         $message = array_shift($messages['messages']);
-        $messagetobedeleted = $DB->get_record('message_read', array('id' => $message['id']));
-        message_delete_message($messagetobedeleted, $user2->id);
+        \core_message\api::delete_message($user2->id, $message['id']);
 
         $messages = core_message_external::get_messages($user2->id, $user3->id, 'conversations', true, true, 0, 0);
         $messages = external_api::clean_returnvalue(core_message_external::get_messages_returns(), $messages);
@@ -735,10 +758,10 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
 
         // Invalid message ids.
         try {
-            $messageid = core_message_external::mark_message_read($messageids[0]['messageid'] * 2, time());
+            $messageid = core_message_external::mark_message_read(1337, time());
             $this->fail('Exception expected due invalid messageid.');
         } catch (dml_missing_record_exception $e) {
-            $this->assertEquals('invalidrecord', $e->errorcode);
+            $this->assertEquals('invalidrecordunknown', $e->errorcode);
         }
 
         // A message to a different user.
@@ -750,7 +773,66 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         } catch (invalid_parameter_exception $e) {
             $this->assertEquals('invalidparameter', $e->errorcode);
         }
+    }
+
+    /**
+     * Test mark_notification_read.
+     */
+    public function test_mark_notification_read() {
+        $this->resetAfterTest(true);
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+        $user3 = self::getDataGenerator()->create_user();
+
+        // Login as user1.
+        $this->setUser($user1);
+        $this->assertEquals(array(), core_message_external::create_contacts(
+            array($user2->id, $user3->id)));
+
+        // The user2 sends a couple of notifications to user1.
+        $this->send_message($user2, $user1, 'Hello there!', 1);
+        $this->send_message($user2, $user1, 'How you goin?', 1);
+        $this->send_message($user3, $user1, 'How you goin?', 1);
+        $this->send_message($user3, $user2, 'How you goin?', 1);
+
+        // Retrieve all notifications sent by user2 (they are currently unread).
+        $lastnotifications = message_get_messages($user1->id, $user2->id, 1, false);
+
+        $notificationids = array();
+        foreach ($lastnotifications as $n) {
+            $notificationid = core_message_external::mark_notification_read($n->id, time());
+            $notificationids[] = external_api::clean_returnvalue(core_message_external::mark_notification_read_returns(),
+                $notificationid);
+        }
+
+        // Retrieve all notifications sent (they are currently read).
+        $lastnotifications = message_get_messages($user1->id, $user2->id, 1, true);
+        $this->assertCount(2, $lastnotifications);
+        $this->assertArrayHasKey($notificationids[1]['notificationid'], $lastnotifications);
+        $this->assertArrayHasKey($notificationids[0]['notificationid'], $lastnotifications);
+
+        // Retrieve all notifications sent by any user (that are currently unread).
+        $lastnotifications = message_get_messages($user1->id, 0, 1, false);
+        $this->assertCount(1, $lastnotifications);
 
+        // Invalid notification ids.
+        try {
+            $notificationid = core_message_external::mark_notification_read(1337, time());
+            $this->fail('Exception expected due invalid notificationid.');
+        } catch (dml_missing_record_exception $e) {
+            $this->assertEquals('invalidrecord', $e->errorcode);
+        }
+
+        // A notification to a different user.
+        $lastnotifications = message_get_messages($user2->id, $user3->id, 1, false);
+        $notificationid = array_pop($lastnotifications)->id;
+        try {
+            $notificationid = core_message_external::mark_notification_read($notificationid, time());
+            $this->fail('Exception expected due invalid notificationid.');
+        } catch (invalid_parameter_exception $e) {
+            $this->assertEquals('invalidparameter', $e->errorcode);
+        }
     }
 
     /**
@@ -783,8 +865,8 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $result = external_api::clean_returnvalue(core_message_external::delete_message_returns(), $result);
         $this->assertTrue($result['status']);
         $this->assertCount(0, $result['warnings']);
-        $deletedmessage = $DB->get_record('message', array('id' => $m1to2));
-        $this->assertNotEquals(0, $deletedmessage->timeuserfromdeleted);
+        $mua = $DB->get_record('message_user_actions', array('messageid' => $m1to2, 'userid' => $user1->id));
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua->action);
 
         // Try to delete the same message again.
         $result = core_message_external::delete_message($m1to2, $user1->id, false);
@@ -805,25 +887,25 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $result = external_api::clean_returnvalue(core_message_external::delete_message_returns(), $result);
         $this->assertTrue($result['status']);
         $this->assertCount(0, $result['warnings']);
-        $deletedmessage = $DB->get_record('message', array('id' => $m2to3));
-        $this->assertNotEquals(0, $deletedmessage->timeusertodeleted);
+        $this->assertTrue($DB->record_exists('message_user_actions', array('messageid' => $m2to3, 'userid' => $user3->id,
+            'action' => \core_message\api::MESSAGE_ACTION_DELETED)));
 
         // Delete a message read.
-        $message = $DB->get_record('message', array('id' => $m3to2));
-        $messageid = message_mark_message_read($message, time());
-        $result = core_message_external::delete_message($messageid, $user3->id);
+        $message = $DB->get_record('messages', ['id' => $m3to2]);
+        \core_message\api::mark_message_as_read($user3->id, $message, time());
+        $result = core_message_external::delete_message($m3to2, $user3->id);
         $result = external_api::clean_returnvalue(core_message_external::delete_message_returns(), $result);
         $this->assertTrue($result['status']);
         $this->assertCount(0, $result['warnings']);
-        $deletedmessage = $DB->get_record('message_read', array('id' => $messageid));
-        $this->assertNotEquals(0, $deletedmessage->timeuserfromdeleted);
+        $this->assertTrue($DB->record_exists('message_user_actions', array('messageid' => $m3to2, 'userid' => $user3->id,
+            'action' => \core_message\api::MESSAGE_ACTION_DELETED)));
 
         // Invalid message ids.
         try {
             $result = core_message_external::delete_message(-1, $user1->id);
             $this->fail('Exception expected due invalid messageid.');
         } catch (dml_missing_record_exception $e) {
-            $this->assertEquals('invalidrecord', $e->errorcode);
+            $this->assertEquals('invalidrecordunknown', $e->errorcode);
         }
 
         // Invalid user.
@@ -849,8 +931,8 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $result = external_api::clean_returnvalue(core_message_external::delete_message_returns(), $result);
         $this->assertTrue($result['status']);
         $this->assertCount(0, $result['warnings']);
-        $deletedmessage = $DB->get_record('message', array('id' => $m3to4));
-        $this->assertNotEquals(0, $deletedmessage->timeusertodeleted);
+        $this->assertTrue($DB->record_exists('message_user_actions', array('messageid' => $m3to4, 'userid' => $user4->id,
+            'action' => \core_message\api::MESSAGE_ACTION_DELETED)));
 
     }
 
@@ -902,15 +984,15 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->send_message($sender3, $recipient, 'Notification', 1);
 
         core_message_external::mark_all_notifications_as_read($recipient->id, $sender1->id);
-        $readnotifications = $DB->get_records('message_read', ['useridto' => $recipient->id]);
-        $unreadnotifications = $DB->get_records('message', ['useridto' => $recipient->id]);
+        $readnotifications = $DB->get_records_select('notifications', 'useridto = ? AND timeread IS NOT NULL', [$recipient->id]);
+        $unreadnotifications = $DB->get_records_select('notifications', 'useridto = ? AND timeread IS NULL', [$recipient->id]);
 
         $this->assertCount(2, $readnotifications);
         $this->assertCount(4, $unreadnotifications);
 
         core_message_external::mark_all_notifications_as_read($recipient->id, 0);
-        $readnotifications = $DB->get_records('message_read', ['useridto' => $recipient->id]);
-        $unreadnotifications = $DB->get_records('message', ['useridto' => $recipient->id]);
+        $readnotifications = $DB->get_records_select('notifications', 'useridto = ? AND timeread IS NOT NULL', [$recipient->id]);
+        $unreadnotifications = $DB->get_records_select('notifications', 'useridto = ? AND timeread IS NULL', [$recipient->id]);
 
         $this->assertCount(6, $readnotifications);
         $this->assertCount(0, $unreadnotifications);
@@ -2400,18 +2482,10 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->send_message($sender3, $recipient, 'Message');
 
         core_message_external::mark_all_messages_as_read($recipient->id, $sender1->id);
-        $readnotifications = $DB->get_records('message_read', array('useridto' => $recipient->id));
-        $unreadnotifications = $DB->get_records('message', array('useridto' => $recipient->id));
-
-        $this->assertCount(2, $readnotifications);
-        $this->assertCount(4, $unreadnotifications);
+        $this->assertEquals(2, $DB->count_records('message_user_actions'));
 
         core_message_external::mark_all_messages_as_read($recipient->id, 0);
-        $readnotifications = $DB->get_records('message_read', array('useridto' => $recipient->id));
-        $unreadnotifications = $DB->get_records('message', array('useridto' => $recipient->id));
-
-        $this->assertCount(6, $readnotifications);
-        $this->assertCount(0, $unreadnotifications);
+        $this->assertEquals(6, $DB->count_records('message_user_actions'));
     }
 
     /**
@@ -2529,33 +2603,39 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
 
         // Send some messages back and forth.
         $time = time();
-        $this->send_message($user1, $user2, 'Yo!', 0, $time);
-        $this->send_message($user2, $user1, 'Sup mang?', 0, $time + 1);
-        $this->send_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 2);
-        $this->send_message($user2, $user1, 'Word.', 0, $time + 3);
+        $m1id = $this->send_message($user1, $user2, 'Yo!', 0, $time);
+        $m2id = $this->send_message($user2, $user1, 'Sup mang?', 0, $time + 1);
+        $m3id = $this->send_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 2);
+        $m4id = $this->send_message($user2, $user1, 'Word.', 0, $time + 3);
 
         // Delete the conversation.
         core_message_external::delete_conversation($user1->id, $user2->id);
 
-        $messages = $DB->get_records('message', array(), 'timecreated ASC');
-        $this->assertCount(4, $messages);
+        $muas = $DB->get_records('message_user_actions', array(), 'timecreated ASC');
+        $this->assertCount(4, $muas);
+        // Sort by id.
+        ksort($muas);
 
-        $message1 = array_shift($messages);
-        $message2 = array_shift($messages);
-        $message3 = array_shift($messages);
-        $message4 = array_shift($messages);
+        $mua1 = array_shift($muas);
+        $mua2 = array_shift($muas);
+        $mua3 = array_shift($muas);
+        $mua4 = array_shift($muas);
 
-        $this->assertNotEmpty($message1->timeuserfromdeleted);
-        $this->assertEmpty($message1->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua1->userid);
+        $this->assertEquals($m1id, $mua1->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua1->action);
 
-        $this->assertEmpty($message2->timeuserfromdeleted);
-        $this->assertNotEmpty($message2->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua2->userid);
+        $this->assertEquals($m2id, $mua2->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua2->action);
 
-        $this->assertNotEmpty($message3->timeuserfromdeleted);
-        $this->assertEmpty($message3->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua3->userid);
+        $this->assertEquals($m3id, $mua3->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua3->action);
 
-        $this->assertEmpty($message4->timeuserfromdeleted);
-        $this->assertNotEmpty($message4->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua4->userid);
+        $this->assertEquals($m4id, $mua4->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua4->action);
     }
 
     /**
@@ -2574,33 +2654,39 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
 
         // Send some messages back and forth.
         $time = time();
-        $this->send_message($user1, $user2, 'Yo!', 0, $time);
-        $this->send_message($user2, $user1, 'Sup mang?', 0, $time + 1);
-        $this->send_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 2);
-        $this->send_message($user2, $user1, 'Word.', 0, $time + 3);
+        $m1id = $this->send_message($user1, $user2, 'Yo!', 0, $time);
+        $m2id = $this->send_message($user2, $user1, 'Sup mang?', 0, $time + 1);
+        $m3id = $this->send_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 2);
+        $m4id = $this->send_message($user2, $user1, 'Word.', 0, $time + 3);
 
         // Delete the conversation.
         core_message_external::delete_conversation($user1->id, $user2->id);
 
-        $messages = $DB->get_records('message', array(), 'timecreated ASC');
-        $this->assertCount(4, $messages);
+        $muas = $DB->get_records('message_user_actions', array(), 'timecreated ASC');
+        $this->assertCount(4, $muas);
+        // Sort by id.
+        ksort($muas);
 
-        $message1 = array_shift($messages);
-        $message2 = array_shift($messages);
-        $message3 = array_shift($messages);
-        $message4 = array_shift($messages);
+        $mua1 = array_shift($muas);
+        $mua2 = array_shift($muas);
+        $mua3 = array_shift($muas);
+        $mua4 = array_shift($muas);
 
-        $this->assertNotEmpty($message1->timeuserfromdeleted);
-        $this->assertEmpty($message1->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua1->userid);
+        $this->assertEquals($m1id, $mua1->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua1->action);
 
-        $this->assertEmpty($message2->timeuserfromdeleted);
-        $this->assertNotEmpty($message2->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua2->userid);
+        $this->assertEquals($m2id, $mua2->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua2->action);
 
-        $this->assertNotEmpty($message3->timeuserfromdeleted);
-        $this->assertEmpty($message3->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua3->userid);
+        $this->assertEquals($m3id, $mua3->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua3->action);
 
-        $this->assertEmpty($message4->timeuserfromdeleted);
-        $this->assertNotEmpty($message4->timeusertodeleted);
+        $this->assertEquals($user1->id, $mua4->userid);
+        $this->assertEquals($m4id, $mua4->messageid);
+        $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua4->action);
     }
 
     /**
index f057df6..231149a 100644 (file)
@@ -73,16 +73,33 @@ class core_message_messagelib_testcase extends advanced_testcase {
             $time = time();
         }
 
+        if ($notification) {
+            $record = new stdClass();
+            $record->useridfrom = $userfrom->id;
+            $record->useridto = $userto->id;
+            $record->subject = 'No subject';
+            $record->fullmessage = $message;
+            $record->smallmessage = $message;
+            $record->timecreated = $time;
+
+            return $DB->insert_record('notifications', $record);
+        }
+
+        if (!$conversationid = \core_message\api::get_conversation_between_users([$userfrom->id, $userto->id])) {
+            $conversationid = \core_message\api::create_conversation_between_users([$userfrom->id,
+                $userto->id]);
+        }
+
+        // Ok, send the message.
         $record = new stdClass();
         $record->useridfrom = $userfrom->id;
-        $record->useridto = $userto->id;
+        $record->conversationid = $conversationid;
         $record->subject = 'No subject';
         $record->fullmessage = $message;
         $record->smallmessage = $message;
         $record->timecreated = $time;
-        $record->notification = $notification;
 
-        return $DB->insert_record('message', $record);
+        return $DB->insert_record('messages', $record);
     }
 
     /**
@@ -101,14 +118,17 @@ class core_message_messagelib_testcase extends advanced_testcase {
         message_add_contact($user2->id, 1);
 
         $this->assertCount(1, message_get_blocked_users());
+        $this->assertDebuggingCalled();
 
         // Block other user.
         message_block_contact($user1->id);
         $this->assertCount(2, message_get_blocked_users());
+        $this->assertDebuggingCalled();
 
         // Test deleting users.
         delete_user($user1);
         $this->assertCount(1, message_get_blocked_users());
+        $this->assertDebuggingCalled();
     }
 
     /**
@@ -138,6 +158,7 @@ class core_message_messagelib_testcase extends advanced_testcase {
         $this->send_fake_message($user3, $USER);
 
         list($onlinecontacts, $offlinecontacts, $strangers) = message_get_contacts();
+        $this->assertDebuggingCalled();
         $this->assertCount(0, $onlinecontacts);
         $this->assertCount(2, $offlinecontacts);
         $this->assertCount(1, $strangers);
@@ -146,6 +167,7 @@ class core_message_messagelib_testcase extends advanced_testcase {
         $this->send_fake_message($noreplyuser, $USER);
         $this->send_fake_message($supportuser, $USER);
         list($onlinecontacts, $offlinecontacts, $strangers) = message_get_contacts();
+        $this->assertDebuggingCalled();
         $this->assertCount(0, $onlinecontacts);
         $this->assertCount(2, $offlinecontacts);
         $this->assertCount(3, $strangers);
@@ -153,6 +175,7 @@ class core_message_messagelib_testcase extends advanced_testcase {
         // Block 1 user.
         message_block_contact($user2->id);
         list($onlinecontacts, $offlinecontacts, $strangers) = message_get_contacts();
+        $this->assertDebuggingCalled();
         $this->assertCount(0, $onlinecontacts);
         $this->assertCount(1, $offlinecontacts);
         $this->assertCount(3, $strangers);
@@ -161,6 +184,7 @@ class core_message_messagelib_testcase extends advanced_testcase {
         core_user::reset_internal_users();
         $CFG->noreplyuserid = $user3->id;
         list($onlinecontacts, $offlinecontacts, $strangers) = message_get_contacts();
+        $this->assertDebuggingCalled();
         $this->assertCount(0, $onlinecontacts);
         $this->assertCount(1, $offlinecontacts);
         $this->assertCount(2, $strangers);
@@ -168,8 +192,9 @@ class core_message_messagelib_testcase extends advanced_testcase {
         // Test deleting users.
         delete_user($user1);
         delete_user($user3);
-
+        core_user::reset_internal_users();
         list($onlinecontacts, $offlinecontacts, $strangers) = message_get_contacts();
+        $this->assertDebuggingCalled();
         $this->assertCount(0, $onlinecontacts);
         $this->assertCount(0, $offlinecontacts);
         $this->assertCount(1, $strangers);
@@ -195,9 +220,11 @@ class core_message_messagelib_testcase extends advanced_testcase {
     }
 
     /**
-     * Test message_count_unread_messages with notifications.
+     * Test message_count_unread_messages with read messages.
      */
-    public function test_message_count_unread_messages_with_notifications() {
+    public function test_message_count_unread_messages_with_read_messages() {
+        global $DB;
+
         // Create users to send and receive messages.
         $userfrom1 = $this->getDataGenerator()->create_user();
         $userfrom2 = $this->getDataGenerator()->create_user();
@@ -206,16 +233,16 @@ class core_message_messagelib_testcase extends advanced_testcase {
         $this->assertEquals(0, message_count_unread_messages($userto));
 
         // Send fake messages.
-        $this->send_fake_message($userfrom1, $userto);
+        $messageid = $this->send_fake_message($userfrom1, $userto);
         $this->send_fake_message($userfrom2, $userto);
 
-        // Send fake notifications.
-        $this->send_fake_message($userfrom1, $userto, 'Notification', 1);
-        $this->send_fake_message($userfrom2, $userto, 'Notification', 1);
+        // Mark message as read.
+        $message = $DB->get_record('messages', ['id' => $messageid]);
+        \core_message\api::mark_message_as_read($userto->id, $message);
 
-        // Should only count the messages.
-        $this->assertEquals(2, message_count_unread_messages($userto));
-        $this->assertEquals(1, message_count_unread_messages($userto, $userfrom1));
+        // Should only count the messages that weren't read by the current user.
+        $this->assertEquals(1, message_count_unread_messages($userto));
+        $this->assertEquals(0, message_count_unread_messages($userto, $userfrom1));
     }
 
     /**
@@ -235,13 +262,8 @@ class core_message_messagelib_testcase extends advanced_testcase {
         $messageid = $this->send_fake_message($userfrom1, $userto);
         $this->send_fake_message($userfrom2, $userto);
 
-        // Send fake notifications.
-        $this->send_fake_message($userfrom1, $userto, 'Notification', 1);
-        $this->send_fake_message($userfrom2, $userto, 'Notification', 1);
-
         // Delete a message.
-        $message = $DB->get_record('message', array('id' => $messageid));
-        message_delete_message($message, $userto->id);
+        \core_message\api::delete_message($userto->id, $messageid);
 
         // Should only count the messages that weren't deleted by the current user.
         $this->assertEquals(1, message_count_unread_messages($userto));
@@ -355,316 +377,4 @@ class core_message_messagelib_testcase extends advanced_testcase {
         $this->assertCount(1, message_search_users(0, 'user1'));
         $this->assertCount(2, message_search_users(0, 'user'));
     }
-
-    /**
-     * The data provider for message_get_recent_conversations.
-     *
-     * This provides sets of data to for testing.
-     * @return array
-     */
-    public function message_get_recent_conversations_provider() {
-        return array(
-            'Test that conversations with messages contacts is correctly ordered.' => array(
-                'users' => array(
-                    'user1',
-                    'user2',
-                    'user3',
-                ),
-                'contacts' => array(
-                ),
-                'messages' => array(
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user2',
-                        'state'         => 'unread',
-                        'subject'       => 'S1',
-                    ),
-                    array(
-                        'from'          => 'user2',
-                        'to'            => 'user1',
-                        'state'         => 'unread',
-                        'subject'       => 'S2',
-                    ),
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user2',
-                        'state'         => 'unread',
-                        'timecreated'   => 0,
-                        'subject'       => 'S3',
-                    ),
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user3',
-                        'state'         => 'read',
-                        'timemodifier'  => 1,
-                        'subject'       => 'S4',
-                    ),
-                    array(
-                        'from'          => 'user3',
-                        'to'            => 'user1',
-                        'state'         => 'read',
-                        'timemodifier'  => 1,
-                        'subject'       => 'S5',
-                    ),
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user3',
-                        'state'         => 'read',
-                        'timecreated'   => 0,
-                        'subject'       => 'S6',
-                    ),
-                ),
-                'expectations' => array(
-                    'user1' => array(
-                        // User1 has conversed most recently with user3. The most recent message is M5.
-                        array(
-                            'messageposition'   => 0,
-                            'with'              => 'user3',
-                            'subject'           => 'S5',
-                        ),
-                        // User1 has also conversed with user2. The most recent message is S2.
-                        array(
-                            'messageposition'   => 1,
-                            'with'              => 'user2',
-                            'subject'           => 'S2',
-                        ),
-                    ),
-                    'user2' => array(
-                        // User2 has only conversed with user1. Their most recent shared message was S2.
-                        array(
-                            'messageposition'   => 0,
-                            'with'              => 'user1',
-                            'subject'           => 'S2',
-                        ),
-                    ),
-                    'user3' => array(
-                        // User3 has only conversed with user1. Their most recent shared message was S5.
-                        array(
-                            'messageposition'   => 0,
-                            'with'              => 'user1',
-                            'subject'           => 'S5',
-                        ),
-                    ),
-                ),
-            ),
-            'Test that users with contacts and messages to self work as expected' => array(
-                'users' => array(
-                    'user1',
-                    'user2',
-                    'user3',
-                ),
-                'contacts' => array(
-                    'user1' => array(
-                        'user2' => 0,
-                        'user3' => 0,
-                    ),
-                    'user2' => array(
-                        'user3' => 0,
-                    ),
-                ),
-                'messages' => array(
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user1',
-                        'state'         => 'unread',
-                        'subject'       => 'S1',
-                    ),
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user1',
-                        'state'         => 'unread',
-                        'subject'       => 'S2',
-                    ),
-                ),
-                'expectations' => array(
-                    'user1' => array(
-                        // User1 has conversed most recently with user1. The most recent message is S2.
-                        array(
-                            'messageposition'   => 0,
-                            'with'              => 'user1',
-                            'subject'           => 'S2',
-                        ),
-                    ),
-                ),
-            ),
-            'Test conversations with a single user, where some messages are read and some are not.' => array(
-                'users' => array(
-                    'user1',
-                    'user2',
-                ),
-                'contacts' => array(
-                ),
-                'messages' => array(
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user2',
-                        'state'         => 'read',
-                        'subject'       => 'S1',
-                    ),
-                    array(
-                        'from'          => 'user2',
-                        'to'            => 'user1',
-                        'state'         => 'read',
-                        'subject'       => 'S2',
-                    ),
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user2',
-                        'state'         => 'unread',
-                        'timemodifier'  => 1,
-                        'subject'       => 'S3',
-                    ),
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user2',
-                        'state'         => 'unread',
-                        'timemodifier'  => 1,
-                        'subject'       => 'S4',
-                    ),
-                ),
-                'expectations' => array(
-                    // The most recent message between user1 and user2 was S4.
-                    'user1' => array(
-                        array(
-                            'messageposition'   => 0,
-                            'with'              => 'user2',
-                            'subject'           => 'S4',
-                        ),
-                    ),
-                    'user2' => array(
-                        // The most recent message between user1 and user2 was S4.
-                        array(
-                            'messageposition'   => 0,
-                            'with'              => 'user1',
-                            'subject'           => 'S4',
-                        ),
-                    ),
-                ),
-            ),
-            'Test conversations with a single user, where some messages are read and some are not, and messages ' .
-            'are out of order' => array(
-            // This can happen through a combination of factors including multi-master DB replication with messages
-            // read somehow (e.g. API).
-                'users' => array(
-                    'user1',
-                    'user2',
-                ),
-                'contacts' => array(
-                ),
-                'messages' => array(
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user2',
-                        'state'         => 'read',
-                        'subject'       => 'S1',
-                        'timemodifier'  => 1,
-                    ),
-                    array(
-                        'from'          => 'user2',
-                        'to'            => 'user1',
-                        'state'         => 'read',
-                        'subject'       => 'S2',
-                        'timemodifier'  => 2,
-                    ),
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user2',
-                        'state'         => 'unread',
-                        'subject'       => 'S3',
-                    ),
-                    array(
-                        'from'          => 'user1',
-                        'to'            => 'user2',
-                        'state'         => 'unread',
-                        'subject'       => 'S4',
-                    ),
-                ),
-                'expectations' => array(
-                    // The most recent message between user1 and user2 was S2, even though later IDs have not been read.
-                    'user1' => array(
-                        array(
-                            'messageposition'   => 0,
-                            'with'              => 'user2',
-                            'subject'           => 'S2',
-                        ),
-                    ),
-                    'user2' => array(
-                        array(
-                            'messageposition'   => 0,
-                            'with'              => 'user1',
-                            'subject'           => 'S2',
-                        ),
-                    ),
-                ),
-            ),
-        );
-    }
-
-    /**
-     * Test message_get_recent_conversations with a mixture of messages.
-     *
-     * @dataProvider message_get_recent_conversations_provider
-     * @param array $usersdata The list of users to create for this test.
-     * @param array $messagesdata The list of messages to create.
-     * @param array $expectations The list of expected outcomes.
-     */
-    public function test_message_get_recent_conversations($usersdata, $contacts, $messagesdata, $expectations) {
-        global $DB;
-
-        // Create all of the users.
-        $users = array();
-        foreach ($usersdata as $username) {
-            $users[$username] = $this->getDataGenerator()->create_user(array('username' => $username));
-        }
-
-        foreach ($contacts as $username => $contact) {
-            foreach ($contact as $contactname => $blocked) {
-                $record = new stdClass();
-                $record->userid     = $users[$username]->id;
-                $record->contactid  = $users[$contactname]->id;
-                $record->blocked    = $blocked;
-                $record->id = $DB->insert_record('message_contacts', $record);
-            }
-        }
-
-        $defaulttimecreated = time();
-        foreach ($messagesdata as $messagedata) {
-            $from       = $users[$messagedata['from']];
-            $to         = $users[$messagedata['to']];
-            $subject    = $messagedata['subject'];
-
-            if (isset($messagedata['state']) && $messagedata['state'] == 'unread') {
-                $table = 'message';
-                $messageid = $this->send_fake_message($from, $to, $subject);
-            } else {
-                // If there is no state, or the state is not 'unread', assume the message is read.
-                $table = 'message_read';
-                $messageid = message_post_message($from, $to, $subject, FORMAT_PLAIN);
-            }
-
-            $updatemessage = new stdClass();
-            $updatemessage->id = $messageid;
-            if (isset($messagedata['timecreated'])) {
-                $updatemessage->timecreated = $messagedata['timecreated'];
-            } else if (isset($messagedata['timemodifier'])) {
-                $updatemessage->timecreated = $defaulttimecreated + $messagedata['timemodifier'];
-            } else {
-                $updatemessage->timecreated = $defaulttimecreated;
-            }
-            $DB->update_record($table, $updatemessage);
-        }
-
-        foreach ($expectations as $username => $data) {
-            // Get the recent conversations for the specified user.
-            $user = $users[$username];
-            $conversations = message_get_recent_conversations($user);
-            $this->assertDebuggingCalled();
-            foreach ($data as $expectation) {
-                $otheruser = $users[$expectation['with']];
-                $conversation = $conversations[$expectation['messageposition']];
-                $this->assertEquals($otheruser->id, $conversation->id);
-                $this->assertEquals($expectation['subject'], $conversation->smallmessage);
-            }
-        }
-    }
 }
index 9eeb93e..fcc2921 100644 (file)
@@ -273,7 +273,7 @@ class message_received_search_testcase extends advanced_testcase {
             $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($me