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. *