Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Contains class containing unit tests for mod/chat/lib.php.
  19   *
  20   * @package mod_chat
  21   * @category test
  22   * @copyright 2017 Mark Nelson <markn@moodle.com>
  23   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  /**
  29   * Class containing unit tests for mod/chat/lib.php.
  30   *
  31   * @package mod_chat
  32   * @category test
  33   * @copyright 2017 Mark Nelson <markn@moodle.com>
  34   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class mod_chat_lib_testcase extends advanced_testcase {
  37  
  38      public function setUp() {
  39          $this->resetAfterTest();
  40      }
  41  
  42      /*
  43       * The chat's event should not be shown to a user when the user cannot view the chat at all.
  44       */
  45      public function test_chat_core_calendar_provide_event_action_in_hidden_section() {
  46          global $CFG;
  47  
  48          $this->setAdminUser();
  49  
  50          // Create a course.
  51          $course = $this->getDataGenerator()->create_course();
  52  
  53          // Create a student.
  54          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
  55  
  56          // Create a chat.
  57          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
  58                  'chattime' => usergetmidnight(time())));
  59  
  60          // Create a calendar event.
  61          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
  62  
  63          // Set sections 0 as hidden.
  64          set_section_visible($course->id, 0, 0);
  65  
  66          // Now, log out.
  67          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
  68          $this->setUser();
  69  
  70          // Create an action factory.
  71          $factory = new \core_calendar\action_factory();
  72  
  73          // Decorate action event for the student.
  74          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory, $student->id);
  75  
  76          // Confirm the event is not shown at all.
  77          $this->assertNull($actionevent);
  78      }
  79  
  80      /*
  81       * The chat's event should not be shown to a user who does not have permission to view the chat at all.
  82       */
  83      public function test_chat_core_calendar_provide_event_action_for_non_user() {
  84          global $CFG;
  85  
  86          $this->setAdminUser();
  87  
  88          // Create a course.
  89          $course = $this->getDataGenerator()->create_course();
  90  
  91          // Create a chat.
  92          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
  93                  'chattime' => usergetmidnight(time())));
  94  
  95          // Create a calendar event.
  96          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
  97  
  98          // Now, log out.
  99          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 100          $this->setUser();
 101  
 102          // Create an action factory.
 103          $factory = new \core_calendar\action_factory();
 104  
 105          // Decorate action event.
 106          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory);
 107  
 108          // Confirm the event is not shown at all.
 109          $this->assertNull($actionevent);
 110      }
 111  
 112      public function test_chat_core_calendar_provide_event_action_chattime_event_yesterday() {
 113          $this->setAdminUser();
 114  
 115          // Create a course.
 116          $course = $this->getDataGenerator()->create_course();
 117  
 118          // Create a chat.
 119          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 120              'chattime' => time() - DAYSECS));
 121  
 122          // Create a calendar event.
 123          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
 124  
 125          // Create an action factory.
 126          $factory = new \core_calendar\action_factory();
 127  
 128          // Decorate action event.
 129          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory);
 130  
 131          // Confirm the event is not shown at all.
 132          $this->assertNull($actionevent);
 133      }
 134  
 135      public function test_chat_core_calendar_provide_event_action_chattime_event_yesterday_for_user() {
 136          global $CFG;
 137  
 138          $this->setAdminUser();
 139  
 140          // Create a course.
 141          $course = $this->getDataGenerator()->create_course();
 142  
 143          // Enrol a student in the course.
 144          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 145  
 146          // Create a chat.
 147          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 148                  'chattime' => time() - DAYSECS));
 149  
 150          // Create a calendar event.
 151          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
 152  
 153          // Now, log out.
 154          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users have mod/chat:view capability by default.
 155          $this->setUser();
 156  
 157          // Create an action factory.
 158          $factory = new \core_calendar\action_factory();
 159  
 160          // Decorate action event for the student.
 161          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory, $student->id);
 162  
 163          // Confirm the event is not shown at all.
 164          $this->assertNull($actionevent);
 165      }
 166  
 167      public function test_chat_core_calendar_provide_event_action_chattime_event_today() {
 168          $this->setAdminUser();
 169  
 170          // Create a course.
 171          $course = $this->getDataGenerator()->create_course();
 172  
 173          // Create a chat.
 174          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 175              'chattime' => usergetmidnight(time())));
 176  
 177          // Create a calendar event.
 178          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
 179  
 180          // Create an action factory.
 181          $factory = new \core_calendar\action_factory();
 182  
 183          // Decorate action event.
 184          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory);
 185  
 186          // Confirm the event was decorated.
 187          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 188          $this->assertEquals(get_string('enterchat', 'chat'), $actionevent->get_name());
 189          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 190          $this->assertEquals(1, $actionevent->get_item_count());
 191          $this->assertTrue($actionevent->is_actionable());
 192      }
 193  
 194      public function test_chat_core_calendar_provide_event_action_chattime_event_today_for_user() {
 195          global $CFG;
 196  
 197          $this->setAdminUser();
 198  
 199          // Create a course.
 200          $course = $this->getDataGenerator()->create_course();
 201  
 202          // Enrol a student in the course.
 203          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 204  
 205          // Create a chat.
 206          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 207                  'chattime' => usergetmidnight(time())));
 208  
 209          // Create a calendar event.
 210          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
 211  
 212          // Now, log out.
 213          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users have mod/chat:view capability by default.
 214          $this->setUser();
 215  
 216          // Create an action factory.
 217          $factory = new \core_calendar\action_factory();
 218  
 219          // Decorate action event for the student.
 220          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory, $student->id);
 221  
 222          // Confirm the event was decorated.
 223          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 224          $this->assertEquals(get_string('enterchat', 'chat'), $actionevent->get_name());
 225          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 226          $this->assertEquals(1, $actionevent->get_item_count());
 227          $this->assertTrue($actionevent->is_actionable());
 228      }
 229  
 230      public function test_chat_core_calendar_provide_event_action_chattime_event_tonight() {
 231          $this->setAdminUser();
 232  
 233          // Create a course.
 234          $course = $this->getDataGenerator()->create_course();
 235  
 236          // Create a chat.
 237          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 238              'chattime' => usergetmidnight(time()) + (23 * HOURSECS)));
 239  
 240          // Create a calendar event.
 241          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
 242  
 243          // Create an action factory.
 244          $factory = new \core_calendar\action_factory();
 245  
 246          // Decorate action event.
 247          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory);
 248  
 249          // Confirm the event was decorated.
 250          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 251          $this->assertEquals(get_string('enterchat', 'chat'), $actionevent->get_name());
 252          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 253          $this->assertEquals(1, $actionevent->get_item_count());
 254          $this->assertTrue($actionevent->is_actionable());
 255      }
 256  
 257      public function test_chat_core_calendar_provide_event_action_chattime_event_tonight_for_user() {
 258          global $CFG;
 259  
 260          $this->setAdminUser();
 261  
 262          // Create a course.
 263          $course = $this->getDataGenerator()->create_course();
 264  
 265          // Enrol a student in the course.
 266          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 267  
 268          // Create a chat.
 269          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 270                  'chattime' => usergetmidnight(time()) + (23 * HOURSECS)));
 271  
 272          // Create a calendar event.
 273          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
 274  
 275          // Now, log out.
 276          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users have mod/chat:view capability by default.
 277          $this->setUser();
 278  
 279          // Create an action factory.
 280          $factory = new \core_calendar\action_factory();
 281  
 282          // Decorate action event for the student.
 283          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory, $student->id);
 284  
 285          // Confirm the event was decorated.
 286          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 287          $this->assertEquals(get_string('enterchat', 'chat'), $actionevent->get_name());
 288          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 289          $this->assertEquals(1, $actionevent->get_item_count());
 290          $this->assertTrue($actionevent->is_actionable());
 291      }
 292  
 293      public function test_chat_core_calendar_provide_event_action_chattime_event_tomorrow() {
 294          $this->setAdminUser();
 295  
 296          // Create a course.
 297          $course = $this->getDataGenerator()->create_course();
 298  
 299          // Create a chat.
 300          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 301              'chattime' => time() + DAYSECS));
 302  
 303          // Create a calendar event.
 304          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
 305  
 306          // Create an action factory.
 307          $factory = new \core_calendar\action_factory();
 308  
 309          // Decorate action event.
 310          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory);
 311  
 312          // Confirm the event was decorated.
 313          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 314          $this->assertEquals(get_string('enterchat', 'chat'), $actionevent->get_name());
 315          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 316          $this->assertEquals(1, $actionevent->get_item_count());
 317          $this->assertFalse($actionevent->is_actionable());
 318      }
 319  
 320      public function test_chat_core_calendar_provide_event_action_chattime_event_tomorrow_for_user() {
 321          global $CFG;
 322  
 323          $this->setAdminUser();
 324  
 325          // Create a course.
 326          $course = $this->getDataGenerator()->create_course();
 327  
 328          // Enrol a student in the course.
 329          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 330  
 331          // Create a chat.
 332          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 333                  'chattime' => time() + DAYSECS));
 334  
 335          // Create a calendar event.
 336          $event = $this->create_action_event($course->id, $chat->id, CHAT_EVENT_TYPE_CHATTIME);
 337  
 338          // Now, log out.
 339          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users have mod/chat:view capability by default.
 340          $this->setUser();
 341  
 342          // Create an action factory.
 343          $factory = new \core_calendar\action_factory();
 344  
 345          // Decorate action event for the student.
 346          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory, $student->id);
 347  
 348          // Confirm the event was decorated.
 349          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 350          $this->assertEquals(get_string('enterchat', 'chat'), $actionevent->get_name());
 351          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 352          $this->assertEquals(1, $actionevent->get_item_count());
 353          $this->assertFalse($actionevent->is_actionable());
 354      }
 355  
 356      public function test_chat_core_calendar_provide_event_action_chattime_event_different_timezones() {
 357          global $CFG;
 358  
 359          $this->setAdminUser();
 360  
 361          // Create a course.
 362          $course = $this->getDataGenerator()->create_course();
 363  
 364          $hour = gmdate('H');
 365  
 366          // This could have been much easier if MDL-37327 were implemented.
 367          // We don't know when this test is being ran and there is no standard way to
 368          // mock the time() function (MDL-37327 to handle that).
 369          if ($hour < 10) {
 370              $timezone1 = 'UTC';                 // GMT.
 371              $timezone2 = 'Pacific/Pago_Pago';   // GMT -11:00.
 372          } else if ($hour < 11) {
 373              $timezone1 = 'Pacific/Kiritimati';  // GMT +14:00.
 374              $timezone2 = 'America/Sao_Paulo';   // GMT -03:00.
 375          } else {
 376              $timezone1 = 'Pacific/Kiritimati';  // GMT +14:00.
 377              $timezone2 = 'UTC';                 // GMT.
 378          }
 379  
 380          $this->setTimezone($timezone2);
 381  
 382          // Enrol 2 students with different timezones in the course.
 383          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student', (object)['timezone' => $timezone1]);
 384          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student', (object)['timezone' => $timezone2]);
 385  
 386          // Create a chat.
 387          $chat1 = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 388                  'chattime' => mktime(1, 0, 0)));    // This is always yesterday in timezone1 time
 389                                                      // and always today in timezone2 time.
 390  
 391          // Create a chat.
 392          $chat2 = $this->getDataGenerator()->create_module('chat', array('course' => $course->id,
 393                  'chattime' => mktime(1, 0, 0) + DAYSECS));  // This is always today in timezone1 time
 394                                                              // and always tomorrow in timezone2 time.
 395  
 396          // Create calendar events for the 2 chats above.
 397          $event1 = $this->create_action_event($course->id, $chat1->id, CHAT_EVENT_TYPE_CHATTIME);
 398          $event2 = $this->create_action_event($course->id, $chat2->id, CHAT_EVENT_TYPE_CHATTIME);
 399  
 400          // Now, log out.
 401          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users have mod/chat:view capability by default.
 402          $this->setUser();
 403  
 404          // Create an action factory.
 405          $factory = new \core_calendar\action_factory();
 406  
 407          // Decorate action event for student1.
 408          $actionevent11 = mod_chat_core_calendar_provide_event_action($event1, $factory, $student1->id);
 409          $actionevent12 = mod_chat_core_calendar_provide_event_action($event1, $factory, $student2->id);
 410          $actionevent21 = mod_chat_core_calendar_provide_event_action($event2, $factory, $student1->id);
 411          $actionevent22 = mod_chat_core_calendar_provide_event_action($event2, $factory, $student2->id);
 412  
 413          // Confirm event1 is not shown to student1 at all.
 414          $this->assertNull($actionevent11, 'Failed for UTC time ' . gmdate('H:i'));
 415  
 416          // Confirm event1 was decorated for student2 and it is actionable.
 417          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent12);
 418          $this->assertEquals(get_string('enterchat', 'chat'), $actionevent12->get_name());
 419          $this->assertInstanceOf('moodle_url', $actionevent12->get_url());
 420          $this->assertEquals(1, $actionevent12->get_item_count());
 421          $this->assertTrue($actionevent12->is_actionable());
 422  
 423          // Confirm event2 was decorated for student1 and it is actionable.
 424          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent21);
 425          $this->assertEquals(get_string('enterchat', 'chat'), $actionevent21->get_name());
 426          $this->assertInstanceOf('moodle_url', $actionevent21->get_url());
 427          $this->assertEquals(1, $actionevent21->get_item_count());
 428          $this->assertTrue($actionevent21->is_actionable());
 429  
 430          // Confirm event2 was decorated for student2 and it is not actionable.
 431          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent22);
 432          $this->assertEquals(get_string('enterchat', 'chat'), $actionevent22->get_name());
 433          $this->assertInstanceOf('moodle_url', $actionevent22->get_url());
 434          $this->assertEquals(1, $actionevent22->get_item_count());
 435          $this->assertFalse($actionevent22->is_actionable());
 436      }
 437  
 438      /**
 439       * Test for chat_get_sessions().
 440       */
 441      public function test_chat_get_sessions() {
 442          global $DB;
 443  
 444          $this->resetAfterTest();
 445  
 446          $generator = $this->getDataGenerator();
 447  
 448          // Setup test data.
 449          $this->setAdminUser();
 450          $course = $generator->create_course();
 451          $chat = $generator->create_module('chat', ['course' => $course->id]);
 452  
 453          $user1 = $generator->create_user();
 454          $user2 = $generator->create_user();
 455          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 456          $generator->enrol_user($user1->id, $course->id, $studentrole->id);
 457          $generator->enrol_user($user2->id, $course->id, $studentrole->id);
 458  
 459          // Login as user 1.
 460          $this->setUser($user1);
 461          $chatsid = chat_login_user($chat->id, 'ajax', 0, $course);
 462          $chatuser = $DB->get_record('chat_users', ['sid' => $chatsid]);
 463  
 464          // Get the messages for this chat session.
 465          $messages = chat_get_session_messages($chat->id, false, 0, 0, 'timestamp DESC');
 466  
 467          // We should have just 1 system (enter) messages.
 468          $this->assertCount(1, $messages);
 469  
 470          // This is when the session starts (when the first message - enter - has been sent).
 471          $sessionstart = reset($messages)->timestamp;
 472  
 473          // Send some messages.
 474          chat_send_chatmessage($chatuser, 'hello!');
 475          chat_send_chatmessage($chatuser, 'bye bye!');
 476  
 477          // Login as user 2.
 478          $this->setUser($user2);
 479          $chatsid = chat_login_user($chat->id, 'ajax', 0, $course);
 480          $chatuser = $DB->get_record('chat_users', ['sid' => $chatsid]);
 481  
 482          // Send a message and take note of this message ID.
 483          $messageid = chat_send_chatmessage($chatuser, 'greetings!');
 484  
 485          // This is when the session ends (timestamp of the last message sent to the chat).
 486          $sessionend = $DB->get_field('chat_messages', 'timestamp', ['id' => $messageid]);
 487  
 488          // Get the messages for this chat session.
 489          $messages = chat_get_session_messages($chat->id, false, 0, 0, 'timestamp DESC');
 490  
 491          // We should have 3 user and 2 system (enter) messages.
 492          $this->assertCount(5, $messages);
 493  
 494          // Fetch the chat sessions from the messages we retrieved.
 495          $sessions = chat_get_sessions($messages, true);
 496  
 497          // There should be only one session.
 498          $this->assertCount(1, $sessions);
 499  
 500          // Get this session.
 501          $session = reset($sessions);
 502  
 503          // Confirm that the start and end times of the session matches.
 504          $this->assertEquals($sessionstart, $session->sessionstart);
 505          $this->assertEquals($sessionend, $session->sessionend);
 506          // Confirm we have 2 participants in the chat.
 507          $this->assertCount(2, $session->sessionusers);
 508      }
 509  
 510      /**
 511       * Test for chat_get_sessions with messages belonging to multiple sessions.
 512       */
 513      public function test_chat_get_sessions_multiple() {
 514          $messages = [];
 515          $gap = 5; // 5 secs.
 516  
 517          $now = time();
 518          $timestamp = $now;
 519  
 520          // Messages belonging to 3 sessions. Session 1 has 10 messages, 2 has 15, 3 has 25.
 521          $sessionusers = [];
 522          $sessiontimes = [];
 523          $session = 0; // Incomplete session.
 524          for ($i = 1; $i <= 50; $i++) {
 525              // Take note of expected session times as we go through.
 526              switch ($i) {
 527                  case 1:
 528                      // Session 1 start time.
 529                      $sessiontimes[0]['start'] = $timestamp;
 530                      break;
 531                  case 10:
 532                      // Session 1 end time.
 533                      $sessiontimes[0]['end'] = $timestamp;
 534                      break;
 535                  case 11:
 536                      // Session 2 start time.
 537                      $sessiontimes[1]['start'] = $timestamp;
 538                      break;
 539                  case 25:
 540                      // Session 2 end time.
 541                      $sessiontimes[1]['end'] = $timestamp;
 542                      break;
 543                  case 26:
 544                      // Session 3 start time.
 545                      $sessiontimes[2]['start'] = $timestamp;
 546                      break;
 547                  case 50:
 548                      // Session 3 end time.
 549                      $sessiontimes[2]['end'] = $timestamp;
 550                      break;
 551              }
 552  
 553              // User 1 to 5.
 554              $user = rand(1, 5);
 555  
 556              // Let's also include system messages as well. Give them to pop in 1-in-10 chance.
 557              $issystem = rand(1, 10) == 10;
 558  
 559              if ($issystem) {
 560                  $message = 'enter';
 561              } else {
 562                  $message = 'Message ' . $i;
 563                  if (!isset($sessionusers[$session][$user])) {
 564                      $sessionusers[$session][$user] = 1;
 565                  } else {
 566                      $sessionusers[$session][$user]++;
 567                  }
 568              }
 569              $messages[] = (object)[
 570                  'id' => $i,
 571                  'chatid' => 1,
 572                  'userid' => $user,
 573                  'message' => $message,
 574                  'issystem' => $issystem,
 575                  'timestamp' => $timestamp,
 576              ];
 577  
 578              // Set the next timestamp.
 579              if ($i == 10 || $i == 25) {
 580                  // New session.
 581                  $session++;
 582                  $timestamp += CHAT_SESSION_GAP + 1;
 583              } else {
 584                  $timestamp += $gap;
 585              }
 586          }
 587          // Reverse sort the messages so they're in descending order.
 588          rsort($messages);
 589  
 590          // Get chat sessions showing only complete ones.
 591          $completesessions = chat_get_sessions($messages);
 592          // Session 1 is incomplete, so there should only be 2 sessions when $showall is false.
 593          $this->assertCount(2, $completesessions);
 594  
 595          // Reverse sort sessions so they are in ascending order matching our expected session times and users.
 596          $completesessions = array_reverse($completesessions);
 597          foreach ($completesessions as $index => $session) {
 598              // We increment index by 1 because the incomplete expected session (index=0) is not included.
 599              $expectedindex = $index + 1;
 600  
 601              // Check the session users.
 602              $users = $sessionusers[$expectedindex];
 603              $this->assertCount(count($users), $session->sessionusers);
 604              // Check the message counts for each user in this session.
 605              foreach ($users as $userid => $messagecount) {
 606                  $this->assertEquals($messagecount, $session->sessionusers[$userid]);
 607              }
 608  
 609              $sessionstart = $sessiontimes[$expectedindex]['start'];
 610              $sessionend = $sessiontimes[$expectedindex]['end'];
 611              $this->assertEquals($sessionstart, $session->sessionstart);
 612              $this->assertEquals($sessionend, $session->sessionend);
 613          }
 614  
 615          // Get all the chat sessions.
 616          $allsessions = chat_get_sessions($messages, true);
 617          // When showall is true, we should get 3 sessions.
 618          $this->assertCount(3, $allsessions);
 619  
 620          // Reverse sort sessions so they are in ascending order matching our expected session times and users.
 621          $allsessions = array_reverse($allsessions);
 622          foreach ($allsessions as $index => $session) {
 623              // Check the session users.
 624              $users = $sessionusers[$index];
 625              $this->assertCount(count($users), $session->sessionusers);
 626              // Check the message counts for each user in this session.
 627              foreach ($users as $userid => $messagecount) {
 628                  $this->assertEquals($messagecount, $session->sessionusers[$userid]);
 629              }
 630  
 631              $sessionstart = $sessiontimes[$index]['start'];
 632              $sessionend = $sessiontimes[$index]['end'];
 633              $this->assertEquals($sessionstart, $session->sessionstart);
 634              $this->assertEquals($sessionend, $session->sessionend);
 635          }
 636      }
 637  
 638      public function test_chat_core_calendar_provide_event_action_already_completed() {
 639          set_config('enablecompletion', 1);
 640          $this->setAdminUser();
 641  
 642          // Create the activity.
 643          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 644          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id),
 645              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 646  
 647          // Get some additional data.
 648          $cm = get_coursemodule_from_instance('chat', $chat->id);
 649  
 650          // Create a calendar event.
 651          $event = $this->create_action_event($course->id, $chat->id,
 652              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 653  
 654          // Mark the activity as completed.
 655          $completion = new completion_info($course);
 656          $completion->set_module_viewed($cm);
 657  
 658          // Create an action factory.
 659          $factory = new \core_calendar\action_factory();
 660  
 661          // Decorate action event.
 662          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory);
 663  
 664          // Ensure result was null.
 665          $this->assertNull($actionevent);
 666      }
 667  
 668      public function test_chat_core_calendar_provide_event_action_already_completed_for_user() {
 669          set_config('enablecompletion', 1);
 670          $this->setAdminUser();
 671  
 672          // Create the activity.
 673          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 674          $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id),
 675              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 676  
 677          // Enrol a student in the course.
 678          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 679  
 680          // Get some additional data.
 681          $cm = get_coursemodule_from_instance('chat', $chat->id);
 682  
 683          // Create a calendar event.
 684          $event = $this->create_action_event($course->id, $chat->id,
 685              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 686  
 687          // Mark the activity as completed for the student.
 688          $completion = new completion_info($course);
 689          $completion->set_module_viewed($cm, $student->id);
 690  
 691          // Create an action factory.
 692          $factory = new \core_calendar\action_factory();
 693  
 694          // Decorate action event for the student.
 695          $actionevent = mod_chat_core_calendar_provide_event_action($event, $factory, $student->id);
 696  
 697          // Ensure result was null.
 698          $this->assertNull($actionevent);
 699      }
 700  
 701      /**
 702       * Creates an action event.
 703       *
 704       * @param int $courseid
 705       * @param int $instanceid The chat id.
 706       * @param string $eventtype The event type. eg. ASSIGN_EVENT_TYPE_DUE.
 707       * @return bool|calendar_event
 708       */
 709      private function create_action_event($courseid, $instanceid, $eventtype) {
 710          $event = new stdClass();
 711          $event->name = 'Calendar event';
 712          $event->modulename  = 'chat';
 713          $event->courseid = $courseid;
 714          $event->instance = $instanceid;
 715          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 716          $event->eventtype = $eventtype;
 717          $event->timestart = time();
 718  
 719          return calendar_event::create($event);
 720      }
 721  
 722      /**
 723       * A user who does not have capabilities to add events to the calendar should be able to create an chat.
 724       */
 725      public function test_creation_with_no_calendar_capabilities() {
 726          $this->resetAfterTest();
 727          $course = self::getDataGenerator()->create_course();
 728          $context = context_course::instance($course->id);
 729          $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
 730          $roleid = self::getDataGenerator()->create_role();
 731          self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
 732          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
 733          $generator = self::getDataGenerator()->get_plugin_generator('mod_chat');
 734          // Create an instance as a user without the calendar capabilities.
 735          $this->setUser($user);
 736          $params = array(
 737              'course' => $course->id,
 738              'chattime' => time() + 500,
 739          );
 740          $generator->create_instance($params);
 741      }
 742  }