Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

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