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.
$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';
* @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.
* @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;
// 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
* 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;
}
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.');
}
}
}
+ 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;
* @property-read array $other {
* Extra information about event.
*
- * - int messageid: the id of the message.
* - int courseid: the id of the related course.
* }
*
}
$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
)
));
* Init method.
*/
protected function init() {
+ $this->data['objecttable'] = 'messages';
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
// 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;
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;
}
}
* 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;
}
}
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;
}
}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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);
+ }
+}
* @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;
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.
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;
$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;
}
/**
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 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"/>
'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',
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);
+ }
+
return true;
}
}
/**
- * 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);
}
/**
}
/**
- * 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);
}
/**
}
/**
- * 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);
}
/**
*/
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);
}
/**
}
/**
- * 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);
}
/**
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);
+}
* @return {boolean} whether or not the parameter node exists within the editor.
*/
_stopAtContentEditableFilter: function(node) {
- this.editor.contains(node);
+ return this.editor.contains(node);
},
/**
_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) {
$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.
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;
}
// 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;
}
// 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.
}
// 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);
}
// 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.
* @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();
/**
/**
* 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. */
/**
* 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.
*
/**
* 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. */
/**
* 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.
*
/**
* 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) {
/**
* 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) {
$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);
$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);
$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;
$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();
$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.
}
$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.
$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]);
$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]);
$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.
$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]);
$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]);
$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]);
$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]);
$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();
$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]);
$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();
$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 {
}
$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() {
*/
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.
*
$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();
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
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;
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.
*
* @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;
$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();
}
/**
*
* 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();
}
/**
}
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;
+ }
}
$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;
}
/**
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.
*
$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,
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);
}
}
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;
}
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;
}
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;
}
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;
}
* @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)) {
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;
}
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.
$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;
}
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;
}
}
// 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) {
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)
)
$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;
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()
)
);
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;
}
* @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)) {
$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');
}
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;
}
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.
*
$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);
}
/**
$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;
}
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
*
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
$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;
}
*/
var markAsRead = function(id, timeread) {
var args = {
- messageid: id,
+ notificationid: id,
};
if (timeread) {
}
var request = {
- methodname: 'core_message_mark_message_read',
+ methodname: 'core_message_mark_notification_read',
args: args
};
$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;
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]
);
}
}
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;
+++ /dev/null
-<?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/>.
-
-/**
- * Event observers definition.
- *
- * @package message_popup
- * @category event
- * @copyright 2016 Ryan Wyllie <ryan@moodle.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-$observers = array(
-
- // Message viewed.
- array(
- 'eventname' => '\core\event\message_viewed',
- 'callback' => 'message_output_popup::message_viewed',
- 'includefile' => '/message/output/popup/message_output_popup.php'
- )
-);
+++ /dev/null
-<?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
// 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;
}
$notificationoutput = new \message_popup\output\popup_notification($notification);
$notificationcontext = $notificationoutput->export_for_template($renderer);
+
+ // Keep this for BC.
+ $notificationcontext->deleted = false;
$notificationcontexts[] = $notificationcontext;
}
}
* @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;
}
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.
*
$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);
}
/**
$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;
}
}
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
# 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
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)
$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);
}
$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);
}
$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);
}
'messageposition' => 0,
'with' => 'user1',
'subject' => 'S2',
- 'unreadcount' => 2,
+ 'unreadcount' => 0, // Messages sent to and from the same user are counted as read.
),
),
),
$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->timecreated = $defaulttimecreated;
}
- $DB->update_record($table, $updatemessage);
+ $DB->update_record('messages', $updatemessage);
}
foreach ($expectations as $username => $data) {
// 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);
}
/**
$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]));
+ }
}
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.
*/
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
)
));
$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']);
}
$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']);
}
$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());
}
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']);
}
/**
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());
+ }
}
$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);
}
/**
// 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);
// 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);
// 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);
// 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.
} 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);
+ }
}
/**
$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);
$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.
$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)));
}
$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);
$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'));
}
/**
// 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);
}
/**
// 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);
}
/**
$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);
}
/**
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();
}
/**
$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);
$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);
// 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);
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);
// 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);
}
/**
- * 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();
$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));
}
/**
$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));
$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);
- }
- }
- }
}
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid));
}
- message_delete_message($message, $user2->id);
+ \core_message\api::delete_message($user2->id, $message->id);
$this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access($messageid));
$this->setUser($user3);
$this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access(-123));
- message_delete_message($message, $user1->id);
+ \core_message\api::delete_message($user1->id, $message->id);
$this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access($messageid));
$this->setUser($user2);
This files describes API changes in /message/ messaging system,
information provided here is intended especially for developers.
+=== 3.5 ===
+
+* Changed the database structure so there are no longer two tables for messages, with the only
+ difference being that one stores read messages. The 'message' and 'message_read' tables are
+ still present in core but will no longer be populated by core APIs. The data will be
+ transferred to the new database structure via an ad-hoc task. Please be patient. This can
+ take time.
+ The new database structure is as follows -
+ 'messages' - Stores the messages with a 'useridfrom' field specifying the user who sent the
+ message and a 'conversationid' field specifying which conversation it is for.
+ 'message_conversations' - The list of conversations.
+ 'message_conversation_members' - The list of users in each conversation.
+ 'message_user_actions' - The list of user actions against a message, eg. read/deleted.
+ 'notifications' - This has a very similar structure to the old table 'message' and still
+ has a one-to-one relation between users.
+ Due to these huge differences the events message_sent, message_deleted and message_viewed
+ have changed quite a lot. Please, if you have any observers or are triggering these events
+ in your code you will have to make some changes!
+* The webservice external function 'core_message_mark_message_read' now only marks messages as
+ read, and not notifications. A new external function 'core_message_mark_notification_read' was
+ added to mark notifications as read.
+* Deprecated the following functions.
+ - message_move_userfrom_unread2read
+ - message_get_blocked_users
+ - message_get_contacts
+ - message_mark_message_read
+ - message_can_delete_message
+ - message_delete_message
+ - \core_message\api::mark_all_read_for_user
+ Please see their declaration in lib/deprecatedlib.php to view their alternatives (if applicable).
+* Final deprecation of the following functions.
+ - message_get_recent_notifications
+ - message_search
+ - message_get_history
+ - message_get_recent_conversations
+* Added new events for when a notification is sent and viewed.
+* Removed the database tables 'message_popup' and 'message_working'. The 'message_working' table was introduced when
+ the messaging system was first introduced in Moodle, so, a long time ago. It served no real purpose. The
+ 'message_popup' table is also no longer necessary now we have a separate table for notifications.
+
=== 3.2 ===
* Removed all message_print_* functions as well as the files search.html, search_advanced.html and
assign::cron();
$events = $sink->get_events();
- // Two messages are sent, one to student and one to teacher. This generates
+ // Two notifications are sent, one to student and one to teacher. This generates
// four events:
- // core\event\message_sent
- // core\event\message_viewed
- // core\event\message_sent
- // core\event\message_viewed.
+ // core\event\notification_sent
+ // core\event\notification_viewed
+ // core\event\notification_sent
+ // core\event\notification_viewed.
$event = reset($events);
- $this->assertInstanceOf('\core\event\message_sent', $event);
+ $this->assertInstanceOf('\core\event\notification_sent', $event);
$this->assertEquals($assign->get_course()->id, $event->other['courseid']);
$sink->close();
}
$options = array();
foreach ($lines as $idx => $line) {
list($weight, $optiontext) = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $line);
- $options[$idx + 1] = format_text("<span class=\"weight\">($weight) </span>".$optiontext,
- FORMAT_HTML, array('noclean' => true, 'para' => false));
+ $a = new stdclass();
+ $a->weight = $weight;
+ $a->name = format_text($optiontext, FORMAT_HTML, array('noclean' => true, 'para' => false));
+ $options[$idx + 1] = get_string('multichoiceoption', 'feedback', $a);
}
if ($info->subtype === 'r' && !$this->hidenoselect($item)) {
$options = array(0 => get_string('not_selected', 'feedback')) + $options;
$string['modulenameplural'] = 'Feedback';
$string['move_item'] = 'Move this question';
$string['multichoice'] = 'Multiple choice';
+$string['multichoiceoption'] = '<span class="weight">({$a->weight}) </span>{$a->name}';
$string['multichoicerated'] = 'Multiple choice (rated)';
$string['multichoicetype'] = 'Multiple choice type';
$string['multichoice_values'] = 'Multiple choice values';
defined('MOODLE_INTERNAL') || die();
-$version = 2018032200.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2018032200.07; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.