diff --git a/mod/chat/lib.php b/mod/chat/lib.php
index 20d6de059362f..7e2aaada97985 100644
--- a/mod/chat/lib.php
+++ b/mod/chat/lib.php
@@ -29,6 +29,9 @@
// Event types.
define('CHAT_EVENT_TYPE_CHATTIME', 'chattime');
+// Gap between sessions. 5 minutes or more of idleness between messages in a chat means the messages belong in different sessions.
+define('CHAT_SESSION_GAP', 300);
+
// The HTML head for the message window to start with ( is used to get some browsers starting with output.
global $CHAT_HTMLHEAD;
$CHAT_HTMLHEAD = "
\n\n\n".padding(200);
@@ -1486,58 +1489,63 @@ function mod_chat_core_calendar_provide_event_action(calendar_event $event,
/**
* Given a set of messages for a chat, return the completed chat sessions (including optionally not completed ones).
*
- * @param array $messages list of messages from a chat
+ * @param array $messages list of messages from a chat. It is assumed that these are sorted by timestamp in DESCENDING order.
* @param bool $showall whether to include incomplete sessions or not
* @return array the list of sessions
- * @since Moodle 3.4
+ * @since Moodle 3.5
*/
function chat_get_sessions($messages, $showall = false) {
- $sessions = array();
- $sessiongap = 5 * 60; // 5 minutes silence means a new session.
- $sessionend = 0;
- $sessionstart = 0;
- $sessionusers = array();
- $lasttime = 0;
-
- $messagesleft = count($messages);
-
- foreach ($messages as $message) { // We are walking BACKWARDS through the messages.
-
- $messagesleft --; // Countdown.
+ $sessions = [];
+ $start = 0;
+ $end = 0;
+ $sessiontimes = [];
+
+ // Group messages by session times.
+ foreach ($messages as $message) {
+ // Initialise values start-end times if necessary.
+ if (empty($start)) {
+ $start = $message->timestamp;
+ }
+ if (empty($end)) {
+ $end = $message->timestamp;
+ }
- if (!$lasttime) {
- $lasttime = $message->timestamp;
+ // If this message's timestamp has been more than the gap, it means it's been idle.
+ if ($start - $message->timestamp > CHAT_SESSION_GAP) {
+ // Mark this as the session end of the next session.
+ $end = $message->timestamp;
}
- if (!$sessionend) {
- $sessionend = $message->timestamp;
+ // Use this time as the session's start (until it gets overwritten on the next iteration, if needed).
+ $start = $message->timestamp;
+
+ // Set this start-end pair in our list of session times.
+ $sessiontimes[$end]['sessionstart'] = $start;
+ if (!isset($sessiontimes[$end]['sessionend'])) {
+ $sessiontimes[$end]['sessionend'] = $end;
}
- if ((($lasttime - $message->timestamp) < $sessiongap) and $messagesleft) { // Same session.
- if ($message->userid and !$message->issystem) { // Remember user and count messages.
- if (empty($sessionusers[$message->userid])) {
- $sessionusers[$message->userid] = 1;
- } else {
- $sessionusers[$message->userid] ++;
- }
- }
- } else {
- $sessionstart = $lasttime;
-
- $iscomplete = ($sessionend - $sessionstart > 60 and count($sessionusers) > 1);
- if ($showall or $iscomplete) {
- $sessions[] = (object) array(
- 'sessionstart' => $sessionstart,
- 'sessionend' => $sessionend,
- 'sessionusers' => $sessionusers,
- 'iscomplete' => $iscomplete,
- );
+ if ($message->userid && !$message->issystem) {
+ if (!isset($sessiontimes[$end]['sessionusers'][$message->userid])) {
+ $sessiontimes[$end]['sessionusers'][$message->userid] = 1;
+ } else {
+ $sessiontimes[$end]['sessionusers'][$message->userid]++;
}
+ }
+ }
+
+ // Go through each session time and prepare the session data to be returned.
+ foreach ($sessiontimes as $sessionend => $sessiondata) {
+ if (!isset($sessiondata['sessionusers'])) {
+ $sessiondata['sessionusers'] = [];
+ }
+ $sessionusers = $sessiondata['sessionusers'];
+ $sessionstart = $sessiondata['sessionstart'];
- $sessionend = $message->timestamp;
- $sessionusers = array();
- $sessionusers[$message->userid] = 1;
+ $iscomplete = $sessionend - $sessionstart > 60 && count($sessionusers) > 1;
+ if ($showall || $iscomplete) {
+ $sessions[] = (object) ($sessiondata + ['iscomplete' => $iscomplete]);
}
- $lasttime = $message->timestamp;
}
+
return $sessions;
}
@@ -1550,7 +1558,7 @@ function chat_get_sessions($messages, $showall = false) {
* @param int $end the session end timestamp (0 to not filter by time)
* @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter)
* @return array session messages
- * @since Moodle 3.4
+ * @since Moodle 3.5
*/
function chat_get_session_messages($chatid, $group = false, $start = 0, $end = 0, $sort = '') {
global $DB;
diff --git a/mod/chat/locallib.php b/mod/chat/locallib.php
index 98e1ab0df2456..06635fb8f3b75 100644
--- a/mod/chat/locallib.php
+++ b/mod/chat/locallib.php
@@ -116,14 +116,13 @@ public function check_permissions() {
public function prepare_package() {
$content = '';
$lasttime = 0;
- $sessiongap = 5 * 60; // 5 minutes silence means a new session
foreach ($this->messages as $message) { // We are walking FORWARDS through messages
$m = clone $message; // grrrrrr - this causes the sha1 to change as chat_format_message changes what it's passed.
$formatmessage = chat_format_message($m, $this->cm->course, $this->user);
if (!isset($formatmessage->html)) {
continue;
}
- if (empty($lasttime) || (($message->timestamp - $lasttime) > $sessiongap)) {
+ if (empty($lasttime) || (($message->timestamp - $lasttime) > CHAT_SESSION_GAP)) {
$content .= '
';
$content .= userdate($message->timestamp);
}
diff --git a/mod/chat/tests/externallib_test.php b/mod/chat/tests/externallib_test.php
index 2443f71d6b1a6..050753baaa6c4 100644
--- a/mod/chat/tests/externallib_test.php
+++ b/mod/chat/tests/externallib_test.php
@@ -428,7 +428,8 @@ public function test_get_sessions_completed_session() {
$result = external_api::clean_returnvalue(mod_chat_external::get_sessions_returns(), $result);
$this->assertCount(1, $result['sessions']); // One session.
$this->assertTrue($result['sessions'][0]['iscomplete']); // Session complete.
- $this->assertEquals($timenow - HOURSECS + 70, $result['sessions'][0]['sessionstart']); // First not system message time.
+ // The session started when user1 entered the chat.
+ $this->assertEquals($timenow - HOURSECS, $result['sessions'][0]['sessionstart']);
$this->assertEmpty($result['warnings']);
}
diff --git a/mod/chat/tests/lib_test.php b/mod/chat/tests/lib_test.php
index 4edef1aea5aa1..b233af18aa890 100644
--- a/mod/chat/tests/lib_test.php
+++ b/mod/chat/tests/lib_test.php
@@ -143,6 +143,200 @@ public function test_chat_core_calendar_provide_event_action_chattime_event_tomo
$this->assertFalse($actionevent->is_actionable());
}
+ /**
+ * Test for chat_get_sessions().
+ */
+ public function test_chat_get_sessions() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $generator = $this->getDataGenerator();
+
+ // Setup test data.
+ $this->setAdminUser();
+ $course = $generator->create_course();
+ $chat = $generator->create_module('chat', ['course' => $course->id]);
+
+ $user1 = $generator->create_user();
+ $user2 = $generator->create_user();
+ $studentrole = $DB->get_record('role', ['shortname' => 'student']);
+ $generator->enrol_user($user1->id, $course->id, $studentrole->id);
+ $generator->enrol_user($user2->id, $course->id, $studentrole->id);
+
+ // Login as user 1.
+ $this->setUser($user1);
+ $chatsid = chat_login_user($chat->id, 'ajax', 0, $course);
+ $chatuser = $DB->get_record('chat_users', ['sid' => $chatsid]);
+
+ // This is when the session starts (when the user enters the chat).
+ $sessionstart = $chatuser->lastping;
+
+ // Send some messages.
+ chat_send_chatmessage($chatuser, 'hello!');
+ chat_send_chatmessage($chatuser, 'bye bye!');
+
+ // Login as user 2.
+ $this->setUser($user2);
+ $chatsid = chat_login_user($chat->id, 'ajax', 0, $course);
+ $chatuser = $DB->get_record('chat_users', ['sid' => $chatsid]);
+
+ // Send a message and take note of this message ID.
+ $messageid = chat_send_chatmessage($chatuser, 'greetings!');
+
+ // This is when the session ends (timestamp of the last message sent to the chat).
+ $sessionend = $DB->get_field('chat_messages', 'timestamp', ['id' => $messageid]);
+
+ // Get the messages for this chat session.
+ $messages = chat_get_session_messages($chat->id, false, 0, 0, 'timestamp DESC');
+
+ // We should have 3 user and 2 system (enter) messages.
+ $this->assertCount(5, $messages);
+
+ // Fetch the chat sessions from the messages we retrieved.
+ $sessions = chat_get_sessions($messages, true);
+
+ // There should be only one session.
+ $this->assertCount(1, $sessions);
+
+ // Get this session.
+ $session = reset($sessions);
+
+ // Confirm that the start and end times of the session matches.
+ $this->assertEquals($sessionstart, $session->sessionstart);
+ $this->assertEquals($sessionend, $session->sessionend);
+ // Confirm we have 2 participants in the chat.
+ $this->assertCount(2, $session->sessionusers);
+ }
+
+ /**
+ * Test for chat_get_sessions with messages belonging to multiple sessions.
+ */
+ public function test_chat_get_sessions_multiple() {
+ $messages = [];
+ $gap = 5; // 5 secs.
+
+ $now = time();
+ $timestamp = $now;
+
+ // Messages belonging to 3 sessions. Session 1 has 10 messages, 2 has 15, 3 has 25.
+ $sessionusers = [];
+ $sessiontimes = [];
+ $session = 0; // Incomplete session.
+ for ($i = 1; $i <= 50; $i++) {
+ // Take note of expected session times as we go through.
+ switch ($i) {
+ case 1:
+ // Session 1 start time.
+ $sessiontimes[0]['start'] = $timestamp;
+ break;
+ case 10:
+ // Session 1 end time.
+ $sessiontimes[0]['end'] = $timestamp;
+ break;
+ case 11:
+ // Session 2 start time.
+ $sessiontimes[1]['start'] = $timestamp;
+ break;
+ case 25:
+ // Session 2 end time.
+ $sessiontimes[1]['end'] = $timestamp;
+ break;
+ case 26:
+ // Session 3 start time.
+ $sessiontimes[2]['start'] = $timestamp;
+ break;
+ case 50:
+ // Session 3 end time.
+ $sessiontimes[2]['end'] = $timestamp;
+ break;
+ }
+
+ // User 1 to 5.
+ $user = rand(1, 5);
+
+ // Let's also include system messages as well. Give them to pop in 1-in-10 chance.
+ $issystem = rand(1, 10) == 10;
+
+ if ($issystem) {
+ $message = 'enter';
+ } else {
+ $message = 'Message ' . $i;
+ if (!isset($sessionusers[$session][$user])) {
+ $sessionusers[$session][$user] = 1;
+ } else {
+ $sessionusers[$session][$user]++;
+ }
+ }
+ $messages[] = (object)[
+ 'id' => $i,
+ 'chatid' => 1,
+ 'userid' => $user,
+ 'message' => $message,
+ 'issystem' => $issystem,
+ 'timestamp' => $timestamp,
+ ];
+
+ // Set the next timestamp.
+ if ($i == 10 || $i == 25) {
+ // New session.
+ $session++;
+ $timestamp += CHAT_SESSION_GAP + 1;
+ } else {
+ $timestamp += $gap;
+ }
+ }
+ // Reverse sort the messages so they're in descending order.
+ rsort($messages);
+
+ // Get chat sessions showing only complete ones.
+ $completesessions = chat_get_sessions($messages);
+ // Session 1 is incomplete, so there should only be 2 sessions when $showall is false.
+ $this->assertCount(2, $completesessions);
+
+ // Reverse sort sessions so they are in ascending order matching our expected session times and users.
+ $completesessions = array_reverse($completesessions);
+ foreach ($completesessions as $index => $session) {
+ // We increment index by 1 because the incomplete expected session (index=0) is not included.
+ $expectedindex = $index + 1;
+
+ // Check the session users.
+ $users = $sessionusers[$expectedindex];
+ $this->assertCount(count($users), $session->sessionusers);
+ // Check the message counts for each user in this session.
+ foreach ($users as $userid => $messagecount) {
+ $this->assertEquals($messagecount, $session->sessionusers[$userid]);
+ }
+
+ $sessionstart = $sessiontimes[$expectedindex]['start'];
+ $sessionend = $sessiontimes[$expectedindex]['end'];
+ $this->assertEquals($sessionstart, $session->sessionstart);
+ $this->assertEquals($sessionend, $session->sessionend);
+ }
+
+ // Get all the chat sessions.
+ $allsessions = chat_get_sessions($messages, true);
+ // When showall is true, we should get 3 sessions.
+ $this->assertCount(3, $allsessions);
+
+ // Reverse sort sessions so they are in ascending order matching our expected session times and users.
+ $allsessions = array_reverse($allsessions);
+ foreach ($allsessions as $index => $session) {
+ // Check the session users.
+ $users = $sessionusers[$index];
+ $this->assertCount(count($users), $session->sessionusers);
+ // Check the message counts for each user in this session.
+ foreach ($users as $userid => $messagecount) {
+ $this->assertEquals($messagecount, $session->sessionusers[$userid]);
+ }
+
+ $sessionstart = $sessiontimes[$index]['start'];
+ $sessionend = $sessiontimes[$index]['end'];
+ $this->assertEquals($sessionstart, $session->sessionstart);
+ $this->assertEquals($sessionend, $session->sessionend);
+ }
+ }
+
/**
* Creates an action event.
*