Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   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  namespace core_calendar;
  18  
  19  use action_event_test_factory;
  20  use core_calendar\local\event\data_access\event_vault;
  21  use core_calendar\local\event\strategies\raw_event_retrieval_strategy;
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  global $CFG;
  26  require_once($CFG->dirroot . '/calendar/tests/helpers.php');
  27  
  28  /**
  29   * This file contains the class that handles testing of the calendar event vault.
  30   *
  31   * @package core_calendar
  32   * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
  33   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class event_vault_test extends \advanced_testcase {
  36  
  37      /**
  38       * Test that get_action_events_by_timesort returns events after the
  39       * provided timesort value.
  40       */
  41      public function test_get_action_events_by_timesort_after_time() {
  42          $this->resetAfterTest(true);
  43  
  44          $user = $this->getDataGenerator()->create_user();
  45          $factory = new action_event_test_factory();
  46          $strategy = new raw_event_retrieval_strategy();
  47          $vault = new event_vault($factory, $strategy);
  48  
  49          $this->setUser($user);
  50  
  51          for ($i = 1; $i < 6; $i++) {
  52              create_event([
  53                  'name' => sprintf('Event %d', $i),
  54                  'eventtype' => 'user',
  55                  'userid' => $user->id,
  56                  'timesort' => $i,
  57                  'type' => CALENDAR_EVENT_TYPE_ACTION
  58              ]);
  59          }
  60  
  61          $events = $vault->get_action_events_by_timesort($user, 3);
  62  
  63          $this->assertCount(3, $events);
  64          $this->assertEquals('Event 3', $events[0]->get_name());
  65          $this->assertEquals('Event 4', $events[1]->get_name());
  66          $this->assertEquals('Event 5', $events[2]->get_name());
  67  
  68          $events = $vault->get_action_events_by_timesort($user, 3, null, null, 1);
  69  
  70          $this->assertCount(1, $events);
  71          $this->assertEquals('Event 3', $events[0]->get_name());
  72  
  73          $events = $vault->get_action_events_by_timesort($user, 6);
  74  
  75          $this->assertCount(0, $events);
  76      }
  77  
  78      /**
  79       * Test that get_action_events_by_timesort returns events before the
  80       * provided timesort value.
  81       */
  82      public function test_get_action_events_by_timesort_before_time() {
  83          $this->resetAfterTest(true);
  84          $this->setAdminuser();
  85  
  86          $user = $this->getDataGenerator()->create_user();
  87          $factory = new action_event_test_factory();
  88          $strategy = new raw_event_retrieval_strategy();
  89          $vault = new event_vault($factory, $strategy);
  90  
  91          for ($i = 1; $i < 6; $i++) {
  92              create_event([
  93                  'name' => sprintf('Event %d', $i),
  94                  'eventtype' => 'user',
  95                  'userid' => $user->id,
  96                  'timesort' => $i,
  97                  'type' => CALENDAR_EVENT_TYPE_ACTION,
  98                  'courseid' => 1
  99              ]);
 100          }
 101  
 102          $events = $vault->get_action_events_by_timesort($user, null, 3);
 103  
 104          $this->assertCount(3, $events);
 105          $this->assertEquals('Event 1', $events[0]->get_name());
 106          $this->assertEquals('Event 2', $events[1]->get_name());
 107          $this->assertEquals('Event 3', $events[2]->get_name());
 108  
 109          $events = $vault->get_action_events_by_timesort($user, null, 3, null, 1);
 110  
 111          $this->assertCount(1, $events);
 112          $this->assertEquals('Event 1', $events[0]->get_name());
 113  
 114          $events = $vault->get_action_events_by_timesort($user, 6);
 115  
 116          $this->assertCount(0, $events);
 117      }
 118  
 119      /**
 120       * Test that get_action_events_by_timesort returns events between the
 121       * provided timesort values.
 122       */
 123      public function test_get_action_events_by_timesort_between_time() {
 124          $this->resetAfterTest(true);
 125          $this->setAdminuser();
 126  
 127          $user = $this->getDataGenerator()->create_user();
 128          $factory = new action_event_test_factory();
 129          $strategy = new raw_event_retrieval_strategy();
 130          $vault = new event_vault($factory, $strategy);
 131  
 132          for ($i = 1; $i < 6; $i++) {
 133              create_event([
 134                  'name' => sprintf('Event %d', $i),
 135                  'eventtype' => 'user',
 136                  'userid' => $user->id,
 137                  'timesort' => $i,
 138                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 139                  'courseid' => 1
 140              ]);
 141          }
 142  
 143          $events = $vault->get_action_events_by_timesort($user, 2, 4);
 144  
 145          $this->assertCount(3, $events);
 146          $this->assertEquals('Event 2', $events[0]->get_name());
 147          $this->assertEquals('Event 3', $events[1]->get_name());
 148          $this->assertEquals('Event 4', $events[2]->get_name());
 149  
 150          $events = $vault->get_action_events_by_timesort($user, 2, 4, null, 1);
 151  
 152          $this->assertCount(1, $events);
 153          $this->assertEquals('Event 2', $events[0]->get_name());
 154      }
 155  
 156      /**
 157       * Test that get_action_events_by_timesort returns events between the
 158       * provided timesort values and after the last seen event when one is
 159       * provided.
 160       */
 161      public function test_get_action_events_by_timesort_between_time_after_event() {
 162          $this->resetAfterTest(true);
 163          $this->setAdminuser();
 164  
 165          $user = $this->getDataGenerator()->create_user();
 166          $factory = new action_event_test_factory();
 167          $strategy = new raw_event_retrieval_strategy();
 168          $vault = new event_vault($factory, $strategy);
 169  
 170          $records = [];
 171          for ($i = 1; $i < 21; $i++) {
 172              $records[] = create_event([
 173                  'name' => sprintf('Event %d', $i),
 174                  'eventtype' => 'user',
 175                  'userid' => $user->id,
 176                  'timesort' => $i,
 177                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 178                  'courseid' => 1
 179              ]);
 180          }
 181  
 182          $aftereventid = $records[6]->id;
 183          $afterevent = $vault->get_event_by_id($aftereventid);
 184          $events = $vault->get_action_events_by_timesort($user, 3, 15, $afterevent);
 185  
 186          $this->assertCount(8, $events);
 187          $this->assertEquals('Event 8', $events[0]->get_name());
 188  
 189          $events = $vault->get_action_events_by_timesort($user, 3, 15, $afterevent, 3);
 190  
 191          $this->assertCount(3, $events);
 192      }
 193  
 194      /**
 195       * Test that get_action_events_by_timesort returns events between the
 196       * provided timesort values and the last seen event can be provided to
 197       * get paginated results.
 198       */
 199      public function test_get_action_events_by_timesort_between_time_skip_even_records() {
 200          $this->resetAfterTest(true);
 201          $this->setAdminuser();
 202  
 203          $user = $this->getDataGenerator()->create_user();
 204          // The factory will return every event that is divisible by 2.
 205          $factory = new action_event_test_factory(function($actionevent) {
 206              static $count = 0;
 207              $count++;
 208              return ($count % 2) ? true : false;
 209          });
 210          $strategy = new raw_event_retrieval_strategy();
 211          $vault = new event_vault($factory, $strategy);
 212  
 213          for ($i = 1; $i < 41; $i++) {
 214              create_event([
 215                  'name' => sprintf('Event %d', $i),
 216                  'eventtype' => 'user',
 217                  'userid' => $user->id,
 218                  'timesort' => $i,
 219                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 220                  'courseid' => 1
 221              ]);
 222          }
 223  
 224          $events = $vault->get_action_events_by_timesort($user, 3, 35, null, 5);
 225  
 226          $this->assertCount(5, $events);
 227          $this->assertEquals('Event 3', $events[0]->get_name());
 228          $this->assertEquals('Event 5', $events[1]->get_name());
 229          $this->assertEquals('Event 7', $events[2]->get_name());
 230          $this->assertEquals('Event 9', $events[3]->get_name());
 231          $this->assertEquals('Event 11', $events[4]->get_name());
 232  
 233          $afterevent = $events[4];
 234          $events = $vault->get_action_events_by_timesort($user, 3, 35, $afterevent, 5);
 235  
 236          $this->assertCount(5, $events);
 237          $this->assertEquals('Event 13', $events[0]->get_name());
 238          $this->assertEquals('Event 15', $events[1]->get_name());
 239          $this->assertEquals('Event 17', $events[2]->get_name());
 240          $this->assertEquals('Event 19', $events[3]->get_name());
 241          $this->assertEquals('Event 21', $events[4]->get_name());
 242      }
 243  
 244      /**
 245       * Test that get_action_events_by_timesort returns events between the
 246       * provided timesort values. The database will continue to be read until the
 247       * number of events requested has been satisfied. In this case the first
 248       * five events are rejected so it should require two database requests.
 249       */
 250      public function test_get_action_events_by_timesort_between_time_skip_first_records() {
 251          $this->resetAfterTest(true);
 252          $this->setAdminuser();
 253  
 254          $user = $this->getDataGenerator()->create_user();
 255          $limit = 5;
 256          $seen = 0;
 257          // The factory will skip the first $limit events.
 258          $factory = new action_event_test_factory(function($actionevent) use (&$seen, $limit) {
 259              if ($seen < $limit) {
 260                  $seen++;
 261                  return false;
 262              } else {
 263                  return true;
 264              }
 265          });
 266          $strategy = new raw_event_retrieval_strategy();
 267          $vault = new event_vault($factory, $strategy);
 268  
 269          for ($i = 1; $i < 21; $i++) {
 270              create_event([
 271                  'name' => sprintf('Event %d', $i),
 272                  'eventtype' => 'user',
 273                  'userid' => $user->id,
 274                  'timesort' => $i,
 275                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 276                  'courseid' => 1
 277              ]);
 278          }
 279  
 280          $events = $vault->get_action_events_by_timesort($user, 1, 20, null, $limit);
 281  
 282          $this->assertCount($limit, $events);
 283          $this->assertEquals(sprintf('Event %d', $limit + 1), $events[0]->get_name());
 284          $this->assertEquals(sprintf('Event %d', $limit + 2), $events[1]->get_name());
 285          $this->assertEquals(sprintf('Event %d', $limit + 3), $events[2]->get_name());
 286          $this->assertEquals(sprintf('Event %d', $limit + 4), $events[3]->get_name());
 287          $this->assertEquals(sprintf('Event %d', $limit + 5), $events[4]->get_name());
 288      }
 289  
 290      /**
 291       * Test that get_action_events_by_timesort returns events between the
 292       * provided timesort values and after the last seen event when one is
 293       * provided. This should work even when the event ids aren't ordered the
 294       * same as the timesort order.
 295       */
 296      public function test_get_action_events_by_timesort_non_consecutive_ids() {
 297          $this->resetAfterTest(true);
 298          $this->setAdminuser();
 299  
 300          $user = $this->getDataGenerator()->create_user();
 301          $factory = new action_event_test_factory();
 302          $strategy = new raw_event_retrieval_strategy();
 303          $vault = new event_vault($factory, $strategy);
 304  
 305          /*
 306           * The events should be ordered by timesort as follows:
 307           *
 308           * 1 event 1
 309           * 2 event 1
 310           * 1 event 2
 311           * 2 event 2
 312           * 1 event 3
 313           * 2 event 3
 314           * 1 event 4
 315           * 2 event 4
 316           * 1 event 5
 317           * 2 event 5
 318           * 1 event 6
 319           * 2 event 6
 320           * 1 event 7
 321           * 2 event 7
 322           * 1 event 8
 323           * 2 event 8
 324           * 1 event 9
 325           * 2 event 9
 326           * 1 event 10
 327           * 2 event 10
 328           */
 329          $records = [];
 330          for ($i = 1; $i < 11; $i++) {
 331              $records[] = create_event([
 332                  'name' => sprintf('1 event %d', $i),
 333                  'eventtype' => 'user',
 334                  'userid' => $user->id,
 335                  'timesort' => $i,
 336                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 337                  'courseid' => 1
 338              ]);
 339          }
 340  
 341          for ($i = 1; $i < 11; $i++) {
 342              $records[] = create_event([
 343                  'name' => sprintf('2 event %d', $i),
 344                  'eventtype' => 'user',
 345                  'userid' => $user->id,
 346                  'timesort' => $i,
 347                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 348                  'courseid' => 1
 349              ]);
 350          }
 351  
 352          /*
 353           * Expected result set:
 354           *
 355           * 2 event 4
 356           * 1 event 5
 357           * 2 event 5
 358           * 1 event 6
 359           * 2 event 6
 360           * 1 event 7
 361           * 2 event 7
 362           * 1 event 8
 363           * 2 event 8
 364           */
 365          $aftereventid = $records[3]->id;
 366          $afterevent = $vault->get_event_by_id($aftereventid);
 367          // Offset results by event with name "1 event 4" which has the same timesort
 368          // value as the lower boundary of this query (3). Confirm that the given
 369          // $afterevent is used to ignore events with the same timesortfrom values.
 370          $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
 371  
 372          $this->assertCount(9, $events);
 373          $this->assertEquals('2 event 4', $events[0]->get_name());
 374          $this->assertEquals('2 event 8', $events[8]->get_name());
 375  
 376          /*
 377           * Expected result set:
 378           *
 379           * 2 event 4
 380           * 1 event 5
 381           */
 382          $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent, 2);
 383  
 384          $this->assertCount(2, $events);
 385          $this->assertEquals('2 event 4', $events[0]->get_name());
 386          $this->assertEquals('1 event 5', $events[1]->get_name());
 387  
 388          /*
 389           * Expected result set:
 390           *
 391           * 2 event 8
 392           */
 393          $aftereventid = $records[7]->id;
 394          $afterevent = $vault->get_event_by_id($aftereventid);
 395          // Offset results by event with name "1 event 8" which has the same timesort
 396          // value as the upper boundary of this query (8). Confirm that the given
 397          // $afterevent is used to ignore events with the same timesortto values.
 398          $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
 399  
 400          $this->assertCount(1, $events);
 401          $this->assertEquals('2 event 8', $events[0]->get_name());
 402  
 403          /*
 404           * Expected empty result set.
 405           */
 406          $aftereventid = $records[18]->id;
 407          $afterevent = $vault->get_event_by_id($aftereventid);
 408          // Offset results by event with name "2 event 9" which has a timesort
 409          // value larger than the upper boundary of this query (9 > 8). Confirm
 410          // that the given $afterevent is used for filtering events.
 411          $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
 412          $this->assertEmpty($events);
 413      }
 414  
 415      /**
 416       * There are subtle cases where the priority of an event override may be identical to another.
 417       * For example, if you duplicate a group override, but make it apply to a different group. Now
 418       * there are two overrides with exactly the same overridden dates. In this case the priority of
 419       * both is 1.
 420       *
 421       * In this situation:
 422       * - A user in group A should see only the A override
 423       * - A user in group B should see only the B override
 424       * - A user in both A and B should see both
 425       */
 426      public function test_get_action_events_by_timesort_with_identical_group_override_priorities() {
 427          $this->resetAfterTest();
 428          $this->setAdminuser();
 429  
 430          $course = $this->getDataGenerator()->create_course();
 431  
 432          // Create an assign instance.
 433          $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
 434          $assigninstance = $assigngenerator->create_instance(['course' => $course->id]);
 435  
 436          // Create users.
 437          $users = [
 438              'Only in group A'  => $this->getDataGenerator()->create_user(),
 439              'Only in group B'  => $this->getDataGenerator()->create_user(),
 440              'In group A and B' => $this->getDataGenerator()->create_user(),
 441              'In no groups'     => $this->getDataGenerator()->create_user()
 442          ];
 443  
 444          // Enrol users.
 445          foreach ($users as $user) {
 446              $this->getDataGenerator()->enrol_user($user->id, $course->id);
 447          }
 448  
 449          // Create groups.
 450          $groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 451          $groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 452  
 453          // Add members to groups.
 454          // Group A.
 455          $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['Only in group A']->id]);
 456          $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['In group A and B']->id]);
 457  
 458          // Group B.
 459          $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['Only in group B']->id]);
 460          $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['In group A and B']->id]);
 461  
 462          // Events with the same module name, instance and event type.
 463          $events = [
 464              [
 465                  'name' => 'Assignment 1 due date - Group A override',
 466                  'description' => '',
 467                  'format' => 1,
 468                  'courseid' => $course->id,
 469                  'groupid' => $groupa->id,
 470                  'userid' => 2,
 471                  'modulename' => 'assign',
 472                  'instance' => $assigninstance->id,
 473                  'eventtype' => 'due',
 474                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 475                  'timestart' => 1,
 476                  'timeduration' => 0,
 477                  'visible' => 1,
 478                  'priority' => 1
 479              ],
 480              [
 481                  'name' => 'Assignment 1 due date - Group B override',
 482                  'description' => '',
 483                  'format' => 1,
 484                  'courseid' => $course->id,
 485                  'groupid' => $groupb->id,
 486                  'userid' => 2,
 487                  'modulename' => 'assign',
 488                  'instance' => $assigninstance->id,
 489                  'eventtype' => 'due',
 490                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 491                  'timestart' => 1,
 492                  'timeduration' => 0,
 493                  'visible' => 1,
 494                  'priority' => 1
 495              ],
 496              [
 497                  'name' => 'Assignment 1 due date',
 498                  'description' => '',
 499                  'format' => 1,
 500                  'courseid' => $course->id,
 501                  'groupid' => 0,
 502                  'userid' => 2,
 503                  'modulename' => 'assign',
 504                  'instance' => $assigninstance->id,
 505                  'eventtype' => 'due',
 506                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 507                  'timestart' => 1,
 508                  'timeduration' => 0,
 509                  'visible' => 1,
 510                  'priority' => null,
 511              ]
 512          ];
 513  
 514          foreach ($events as $event) {
 515              \calendar_event::create($event, false);
 516          }
 517  
 518          $factory = new action_event_test_factory();
 519          $strategy = new raw_event_retrieval_strategy();
 520          $vault = new event_vault($factory, $strategy);
 521  
 522          $usersevents = array_reduce(array_keys($users), function($carry, $description) use ($users, $vault) {
 523              // NB: This is currently needed to make get_action_events_by_timesort return the right thing.
 524              // It needs to be fixed, see MDL-58736.
 525              $this->setUser($users[$description]);
 526              return $carry + ['For user ' . lcfirst($description) => $vault->get_action_events_by_timesort($users[$description])];
 527          }, []);
 528  
 529          foreach ($usersevents as $description => $userevents) {
 530              if ($description == 'For user in group A and B') {
 531                  // User is in both A and B, so they should see the override for both
 532                  // given that the priority is the same.
 533                  $this->assertCount(2, $userevents);
 534                  continue;
 535              }
 536  
 537              // Otherwise there should be only one assign event for each user.
 538              $this->assertCount(1, $userevents);
 539          }
 540  
 541          // User in only group A should see the group A override.
 542          $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user only in group A'][0]->get_name());
 543  
 544          // User in only group B should see the group B override.
 545          $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user only in group B'][0]->get_name());
 546  
 547          // User in group A and B should see see both overrides since the priorities are the same.
 548          $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user in group A and B'][0]->get_name());
 549          $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user in group A and B'][1]->get_name());
 550  
 551          // User in no groups should see the plain assignment event.
 552          $this->assertEquals('Assignment 1 due date', $usersevents['For user in no groups'][0]->get_name());
 553      }
 554  
 555      /**
 556       * Test that if a user is suspended that events related to that course are not shown.
 557       * User 1 is suspended. User 2 is active.
 558       */
 559      public function test_get_action_events_by_timesort_with_suspended_user() {
 560          $this->resetAfterTest();
 561          $user1 = $this->getDataGenerator()->create_user();
 562          $user2 = $this->getDataGenerator()->create_user();
 563          $course = $this->getDataGenerator()->create_course();
 564          $this->setAdminuser();
 565          $lesson = $this->getDataGenerator()->create_module('lesson', [
 566                  'name' => 'Lesson 1',
 567                  'course' => $course->id,
 568                  'available' => time(),
 569                  'deadline' => (time() + (60 * 60 * 24 * 5))
 570              ]
 571          );
 572          $this->getDataGenerator()->enrol_user($user1->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
 573          $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 574  
 575          $factory = new action_event_test_factory();
 576          $strategy = new raw_event_retrieval_strategy();
 577          $vault = new event_vault($factory, $strategy);
 578  
 579          $user1events = $vault->get_action_events_by_timesort($user1, null, null, null, 20, true);
 580          $this->assertEmpty($user1events);
 581          $user2events = $vault->get_action_events_by_timesort($user2, null, null, null, 20, true);
 582          $this->assertCount(1, $user2events);
 583          $this->assertEquals('Lesson 1 closes', $user2events[0]->get_name());
 584      }
 585  
 586      /**
 587       * Test that get_action_events_by_course returns events after the
 588       * provided timesort value.
 589       */
 590      public function test_get_action_events_by_course_after_time() {
 591          $user = $this->getDataGenerator()->create_user();
 592          $course1 = $this->getDataGenerator()->create_course();
 593          $course2 = $this->getDataGenerator()->create_course();
 594          $factory = new action_event_test_factory();
 595          $strategy = new raw_event_retrieval_strategy();
 596          $vault = new event_vault($factory, $strategy);
 597  
 598          $this->resetAfterTest(true);
 599          $this->setAdminuser();
 600          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
 601          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
 602  
 603          for ($i = 1; $i < 6; $i++) {
 604              create_event([
 605                  'name' => sprintf('Event %d', $i),
 606                  'eventtype' => 'user',
 607                  'userid' => $user->id,
 608                  'timesort' => $i,
 609                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 610                  'courseid' => $course1->id,
 611              ]);
 612          }
 613  
 614          for ($i = 6; $i < 12; $i++) {
 615              create_event([
 616                  'name' => sprintf('Event %d', $i),
 617                  'eventtype' => 'user',
 618                  'userid' => $user->id,
 619                  'timesort' => $i,
 620                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 621                  'courseid' => $course2->id,
 622              ]);
 623          }
 624  
 625          $events = $vault->get_action_events_by_course($user, $course1, 3);
 626          $this->assertCount(3, $events);
 627          $this->assertEquals('Event 3', $events[0]->get_name());
 628          $this->assertEquals('Event 4', $events[1]->get_name());
 629          $this->assertEquals('Event 5', $events[2]->get_name());
 630  
 631          $events = $vault->get_action_events_by_course($user, $course1, 3, null, null, 1);
 632  
 633          $this->assertCount(1, $events);
 634          $this->assertEquals('Event 3', $events[0]->get_name());
 635  
 636          $events = $vault->get_action_events_by_course($user, $course1, 6);
 637  
 638          $this->assertCount(0, $events);
 639      }
 640  
 641      /**
 642       * Test that get_action_events_by_course returns events before the
 643       * provided timesort value.
 644       */
 645      public function test_get_action_events_by_course_before_time() {
 646          $user = $this->getDataGenerator()->create_user();
 647          $course1 = $this->getDataGenerator()->create_course();
 648          $course2 = $this->getDataGenerator()->create_course();
 649          $factory = new action_event_test_factory();
 650          $strategy = new raw_event_retrieval_strategy();
 651          $vault = new event_vault($factory, $strategy);
 652  
 653          $this->resetAfterTest(true);
 654          $this->setAdminuser();
 655          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
 656          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
 657  
 658          for ($i = 1; $i < 6; $i++) {
 659              create_event([
 660                  'name' => sprintf('Event %d', $i),
 661                  'eventtype' => 'user',
 662                  'userid' => $user->id,
 663                  'timesort' => $i,
 664                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 665                  'courseid' => $course1->id,
 666              ]);
 667          }
 668  
 669          for ($i = 6; $i < 12; $i++) {
 670              create_event([
 671                  'name' => sprintf('Event %d', $i),
 672                  'eventtype' => 'user',
 673                  'userid' => $user->id,
 674                  'timesort' => $i,
 675                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 676                  'courseid' => $course2->id,
 677              ]);
 678          }
 679  
 680          $events = $vault->get_action_events_by_course($user, $course1, null, 3);
 681  
 682          $this->assertCount(3, $events);
 683          $this->assertEquals('Event 1', $events[0]->get_name());
 684          $this->assertEquals('Event 2', $events[1]->get_name());
 685          $this->assertEquals('Event 3', $events[2]->get_name());
 686  
 687          $events = $vault->get_action_events_by_course($user, $course1, null, 3, null, 1);
 688  
 689          $this->assertCount(1, $events);
 690          $this->assertEquals('Event 1', $events[0]->get_name());
 691  
 692          $events = $vault->get_action_events_by_course($user, $course1, 6);
 693  
 694          $this->assertCount(0, $events);
 695      }
 696  
 697      /**
 698       * Test that get_action_events_by_course returns events between the
 699       * provided timesort values.
 700       */
 701      public function test_get_action_events_by_course_between_time() {
 702          $user = $this->getDataGenerator()->create_user();
 703          $course1 = $this->getDataGenerator()->create_course();
 704          $course2 = $this->getDataGenerator()->create_course();
 705          $factory = new action_event_test_factory();
 706          $strategy = new raw_event_retrieval_strategy();
 707          $vault = new event_vault($factory, $strategy);
 708  
 709          $this->resetAfterTest(true);
 710          $this->setAdminuser();
 711          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
 712          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
 713  
 714          for ($i = 1; $i < 6; $i++) {
 715              create_event([
 716                  'name' => sprintf('Event %d', $i),
 717                  'eventtype' => 'user',
 718                  'userid' => $user->id,
 719                  'timesort' => $i,
 720                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 721                  'courseid' => $course1->id,
 722              ]);
 723          }
 724  
 725          for ($i = 6; $i < 12; $i++) {
 726              create_event([
 727                  'name' => sprintf('Event %d', $i),
 728                  'eventtype' => 'user',
 729                  'userid' => $user->id,
 730                  'timesort' => $i,
 731                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 732                  'courseid' => $course2->id,
 733              ]);
 734          }
 735  
 736          $events = $vault->get_action_events_by_course($user, $course1, 2, 4);
 737  
 738          $this->assertCount(3, $events);
 739          $this->assertEquals('Event 2', $events[0]->get_name());
 740          $this->assertEquals('Event 3', $events[1]->get_name());
 741          $this->assertEquals('Event 4', $events[2]->get_name());
 742  
 743          $events = $vault->get_action_events_by_course($user, $course1, 2, 4, null, 1);
 744  
 745          $this->assertCount(1, $events);
 746          $this->assertEquals('Event 2', $events[0]->get_name());
 747      }
 748  
 749      /**
 750       * Test that get_action_events_by_course returns events between the
 751       * provided timesort values and after the last seen event when one is
 752       * provided.
 753       */
 754      public function test_get_action_events_by_course_between_time_after_event() {
 755          $user = $this->getDataGenerator()->create_user();
 756          $course1 = $this->getDataGenerator()->create_course();
 757          $course2 = $this->getDataGenerator()->create_course();
 758          $factory = new action_event_test_factory();
 759          $strategy = new raw_event_retrieval_strategy();
 760          $vault = new event_vault($factory, $strategy);
 761          $records = [];
 762  
 763          $this->resetAfterTest(true);
 764          $this->setAdminuser();
 765          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
 766          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
 767  
 768          for ($i = 1; $i < 21; $i++) {
 769              $records[] = create_event([
 770                  'name' => sprintf('Event %d', $i),
 771                  'eventtype' => 'user',
 772                  'userid' => $user->id,
 773                  'timesort' => $i,
 774                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 775                  'courseid' => $course1->id,
 776              ]);
 777          }
 778  
 779          for ($i = 21; $i < 41; $i++) {
 780              $records[] = create_event([
 781                  'name' => sprintf('Event %d', $i),
 782                  'eventtype' => 'user',
 783                  'userid' => $user->id,
 784                  'timesort' => $i,
 785                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 786                  'courseid' => $course2->id,
 787              ]);
 788          }
 789  
 790          $aftereventid = $records[6]->id;
 791          $afterevent = $vault->get_event_by_id($aftereventid);
 792          $events = $vault->get_action_events_by_course($user, $course1, 3, 15, $afterevent);
 793  
 794          $this->assertCount(8, $events);
 795          $this->assertEquals('Event 8', $events[0]->get_name());
 796  
 797          $events = $vault->get_action_events_by_course($user, $course1, 3, 15, $afterevent, 3);
 798  
 799          $this->assertCount(3, $events);
 800      }
 801  
 802      /**
 803       * Test that get_action_events_by_course returns events between the
 804       * provided timesort values and the last seen event can be provided to
 805       * get paginated results.
 806       */
 807      public function test_get_action_events_by_course_between_time_skip_even_records() {
 808          $user = $this->getDataGenerator()->create_user();
 809          $course1 = $this->getDataGenerator()->create_course();
 810          $course2 = $this->getDataGenerator()->create_course();
 811          // The factory will return every event that is divisible by 2.
 812          $factory = new action_event_test_factory(function($actionevent) {
 813              static $count = 0;
 814              $count++;
 815              return ($count % 2) ? true : false;
 816          });
 817          $strategy = new raw_event_retrieval_strategy();
 818          $vault = new event_vault($factory, $strategy);
 819  
 820          $this->resetAfterTest(true);
 821          $this->setAdminuser();
 822          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
 823          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
 824  
 825          for ($i = 1; $i < 41; $i++) {
 826              create_event([
 827                  'name' => sprintf('Event %d', $i),
 828                  'eventtype' => 'user',
 829                  'userid' => $user->id,
 830                  'timesort' => $i,
 831                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 832                  'courseid' => $course1->id,
 833              ]);
 834          }
 835  
 836          for ($i = 41; $i < 81; $i++) {
 837              create_event([
 838                  'name' => sprintf('Event %d', $i),
 839                  'eventtype' => 'user',
 840                  'userid' => $user->id,
 841                  'timesort' => $i,
 842                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 843                  'courseid' => $course2->id,
 844              ]);
 845          }
 846  
 847          $events = $vault->get_action_events_by_course($user, $course1, 3, 35, null, 5);
 848  
 849          $this->assertCount(5, $events);
 850          $this->assertEquals('Event 3', $events[0]->get_name());
 851          $this->assertEquals('Event 5', $events[1]->get_name());
 852          $this->assertEquals('Event 7', $events[2]->get_name());
 853          $this->assertEquals('Event 9', $events[3]->get_name());
 854          $this->assertEquals('Event 11', $events[4]->get_name());
 855  
 856          $afterevent = $events[4];
 857          $events = $vault->get_action_events_by_course($user, $course1, 3, 35, $afterevent, 5);
 858  
 859          $this->assertCount(5, $events);
 860          $this->assertEquals('Event 13', $events[0]->get_name());
 861          $this->assertEquals('Event 15', $events[1]->get_name());
 862          $this->assertEquals('Event 17', $events[2]->get_name());
 863          $this->assertEquals('Event 19', $events[3]->get_name());
 864          $this->assertEquals('Event 21', $events[4]->get_name());
 865      }
 866  
 867      /**
 868       * Test that get_action_events_by_course returns events between the
 869       * provided timesort values. The database will continue to be read until the
 870       * number of events requested has been satisfied. In this case the first
 871       * five events are rejected so it should require two database requests.
 872       */
 873      public function test_get_action_events_by_course_between_time_skip_first_records() {
 874          $user = $this->getDataGenerator()->create_user();
 875          $course1 = $this->getDataGenerator()->create_course();
 876          $course2 = $this->getDataGenerator()->create_course();
 877          $limit = 5;
 878          $seen = 0;
 879          // The factory will skip the first $limit events.
 880          $factory = new action_event_test_factory(function($actionevent) use (&$seen, $limit) {
 881              if ($seen < $limit) {
 882                  $seen++;
 883                  return false;
 884              } else {
 885                  return true;
 886              }
 887          });
 888          $strategy = new raw_event_retrieval_strategy();
 889          $vault = new event_vault($factory, $strategy);
 890  
 891          $this->resetAfterTest(true);
 892          $this->setAdminuser();
 893          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
 894          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
 895  
 896          for ($i = 1; $i < 21; $i++) {
 897              create_event([
 898                  'name' => sprintf('Event %d', $i),
 899                  'eventtype' => 'user',
 900                  'userid' => $user->id,
 901                  'timesort' => $i,
 902                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 903                  'courseid' => $course1->id,
 904              ]);
 905          }
 906  
 907          for ($i = 21; $i < 41; $i++) {
 908              create_event([
 909                  'name' => sprintf('Event %d', $i),
 910                  'eventtype' => 'user',
 911                  'userid' => $user->id,
 912                  'timesort' => $i,
 913                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 914                  'courseid' => $course2->id,
 915              ]);
 916          }
 917  
 918          $events = $vault->get_action_events_by_course($user, $course1, 1, 20, null, $limit);
 919  
 920          $this->assertCount($limit, $events);
 921          $this->assertEquals(sprintf('Event %d', $limit + 1), $events[0]->get_name());
 922          $this->assertEquals(sprintf('Event %d', $limit + 2), $events[1]->get_name());
 923          $this->assertEquals(sprintf('Event %d', $limit + 3), $events[2]->get_name());
 924          $this->assertEquals(sprintf('Event %d', $limit + 4), $events[3]->get_name());
 925          $this->assertEquals(sprintf('Event %d', $limit + 5), $events[4]->get_name());
 926      }
 927  
 928      /**
 929       * Test that get_action_events_by_course returns events between the
 930       * provided timesort values and after the last seen event when one is
 931       * provided. This should work even when the event ids aren't ordered the
 932       * same as the timesort order.
 933       */
 934      public function test_get_action_events_by_course_non_consecutive_ids() {
 935          $this->resetAfterTest(true);
 936          $this->setAdminuser();
 937  
 938          $user = $this->getDataGenerator()->create_user();
 939          $course1 = $this->getDataGenerator()->create_course();
 940          $course2 = $this->getDataGenerator()->create_course();
 941          $factory = new action_event_test_factory();
 942          $strategy = new raw_event_retrieval_strategy();
 943          $vault = new event_vault($factory, $strategy);
 944  
 945          $this->setAdminuser();
 946          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
 947          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
 948  
 949          /*
 950           * The events should be ordered by timesort as follows:
 951           *
 952           * 1 event 1
 953           * 2 event 1
 954           * 1 event 2
 955           * 2 event 2
 956           * 1 event 3
 957           * 2 event 3
 958           * 1 event 4
 959           * 2 event 4
 960           * 1 event 5
 961           * 2 event 5
 962           * 1 event 6
 963           * 2 event 6
 964           * 1 event 7
 965           * 2 event 7
 966           * 1 event 8
 967           * 2 event 8
 968           * 1 event 9
 969           * 2 event 9
 970           * 1 event 10
 971           * 2 event 10
 972           */
 973          $records = [];
 974          for ($i = 1; $i < 11; $i++) {
 975              $records[] = create_event([
 976                  'name' => sprintf('1 event %d', $i),
 977                  'eventtype' => 'user',
 978                  'userid' => $user->id,
 979                  'timesort' => $i,
 980                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 981                  'courseid' => $course1->id,
 982              ]);
 983          }
 984  
 985          for ($i = 1; $i < 11; $i++) {
 986              $records[] = create_event([
 987                  'name' => sprintf('2 event %d', $i),
 988                  'eventtype' => 'user',
 989                  'userid' => $user->id,
 990                  'timesort' => $i,
 991                  'type' => CALENDAR_EVENT_TYPE_ACTION,
 992                  'courseid' => $course1->id,
 993              ]);
 994          }
 995  
 996          // Create events for the other course.
 997          for ($i = 1; $i < 11; $i++) {
 998              $records[] = create_event([
 999                  'name' => sprintf('3 event %d', $i),
1000                  'eventtype' => 'user',
1001                  'userid' => $user->id,
1002                  'timesort' => $i,
1003                  'type' => CALENDAR_EVENT_TYPE_ACTION,
1004                  'courseid' => $course2->id,
1005              ]);
1006          }
1007  
1008          /*
1009           * Expected result set:
1010           *
1011           * 2 event 4
1012           * 1 event 5
1013           * 2 event 5
1014           * 1 event 6
1015           * 2 event 6
1016           * 1 event 7
1017           * 2 event 7
1018           * 1 event 8
1019           * 2 event 8
1020           */
1021          $aftereventid = $records[3]->id;
1022          $afterevent = $vault->get_event_by_id($aftereventid);
1023          // Offset results by event with name "1 event 4" which has the same timesort
1024          // value as the lower boundary of this query (3). Confirm that the given
1025          // $afterevent is used to ignore events with the same timesortfrom values.
1026          $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1027  
1028          $this->assertCount(9, $events);
1029          $this->assertEquals('2 event 4', $events[0]->get_name());
1030          $this->assertEquals('2 event 8', $events[8]->get_name());
1031  
1032          /*
1033           * Expected result set:
1034           *
1035           * 2 event 4
1036           * 1 event 5
1037           */
1038          $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent, 2);
1039  
1040          $this->assertCount(2, $events);
1041          $this->assertEquals('2 event 4', $events[0]->get_name());
1042          $this->assertEquals('1 event 5', $events[1]->get_name());
1043  
1044          /*
1045           * Expected result set:
1046           *
1047           * 2 event 8
1048           */
1049          $aftereventid = $records[7]->id;
1050          $afterevent = $vault->get_event_by_id($aftereventid);
1051          // Offset results by event with name "1 event 8" which has the same timesort
1052          // value as the upper boundary of this query (8). Confirm that the given
1053          // $afterevent is used to ignore events with the same timesortto values.
1054          $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1055  
1056          $this->assertCount(1, $events);
1057          $this->assertEquals('2 event 8', $events[0]->get_name());
1058  
1059          /*
1060           * Expected empty result set.
1061           */
1062          $aftereventid = $records[18]->id;
1063          $afterevent = $vault->get_event_by_id($aftereventid);
1064          // Offset results by event with name "2 event 9" which has a timesort
1065          // value larger than the upper boundary of this query (9 > 8). Confirm
1066          // that the given $afterevent is used for filtering events.
1067          $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1068  
1069          $this->assertEmpty($events);
1070      }
1071  
1072      /**
1073       * There are subtle cases where the priority of an event override may be identical to another.
1074       * For example, if you duplicate a group override, but make it apply to a different group. Now
1075       * there are two overrides with exactly the same overridden dates. In this case the priority of
1076       * both is 1.
1077       *
1078       * In this situation:
1079       * - A user in group A should see only the A override
1080       * - A user in group B should see only the B override
1081       * - A user in both A and B should see both
1082       */
1083      public function test_get_action_events_by_course_with_identical_group_override_priorities() {
1084          $this->resetAfterTest();
1085          $this->setAdminuser();
1086  
1087          $course = $this->getDataGenerator()->create_course();
1088  
1089          // Create an assign instance.
1090          $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1091          $assigninstance = $assigngenerator->create_instance(['course' => $course->id]);
1092  
1093          // Create users.
1094          $users = [
1095              'Only in group A'  => $this->getDataGenerator()->create_user(),
1096              'Only in group B'  => $this->getDataGenerator()->create_user(),
1097              'In group A and B' => $this->getDataGenerator()->create_user(),
1098              'In no groups'     => $this->getDataGenerator()->create_user()
1099          ];
1100  
1101          // Enrol users.
1102          foreach ($users as $user) {
1103              $this->getDataGenerator()->enrol_user($user->id, $course->id);
1104          }
1105  
1106          // Create groups.
1107          $groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1108          $groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1109  
1110          // Add members to groups.
1111          // Group A.
1112          $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['Only in group A']->id]);
1113          $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['In group A and B']->id]);
1114  
1115          // Group B.
1116          $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['Only in group B']->id]);
1117          $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['In group A and B']->id]);
1118  
1119          // Events with the same module name, instance and event type.
1120          $events = [
1121              [
1122                  'name' => 'Assignment 1 due date - Group A override',
1123                  'description' => '',
1124                  'format' => 1,
1125                  'courseid' => $course->id,
1126                  'groupid' => $groupa->id,
1127                  'userid' => 2,
1128                  'modulename' => 'assign',
1129                  'instance' => $assigninstance->id,
1130                  'eventtype' => 'due',
1131                  'type' => CALENDAR_EVENT_TYPE_ACTION,
1132                  'timestart' => 1,
1133                  'timeduration' => 0,
1134                  'visible' => 1,
1135                  'priority' => 1
1136              ],
1137              [
1138                  'name' => 'Assignment 1 due date - Group B override',
1139                  'description' => '',
1140                  'format' => 1,
1141                  'courseid' => $course->id,
1142                  'groupid' => $groupb->id,
1143                  'userid' => 2,
1144                  'modulename' => 'assign',
1145                  'instance' => $assigninstance->id,
1146                  'eventtype' => 'due',
1147                  'type' => CALENDAR_EVENT_TYPE_ACTION,
1148                  'timestart' => 1,
1149                  'timeduration' => 0,
1150                  'visible' => 1,
1151                  'priority' => 1
1152              ],
1153              [
1154                  'name' => 'Assignment 1 due date',
1155                  'description' => '',
1156                  'format' => 1,
1157                  'courseid' => $course->id,
1158                  'groupid' => 0,
1159                  'userid' => 2,
1160                  'modulename' => 'assign',
1161                  'instance' => $assigninstance->id,
1162                  'eventtype' => 'due',
1163                  'type' => CALENDAR_EVENT_TYPE_ACTION,
1164                  'timestart' => 1,
1165                  'timeduration' => 0,
1166                  'visible' => 1,
1167                  'priority' => null,
1168              ]
1169          ];
1170  
1171          foreach ($events as $event) {
1172              \calendar_event::create($event, false);
1173          }
1174  
1175          $factory = new action_event_test_factory();
1176          $strategy = new raw_event_retrieval_strategy();
1177          $vault = new event_vault($factory, $strategy);
1178  
1179          $usersevents = array_reduce(array_keys($users), function($carry, $description) use ($users, $course, $vault) {
1180              // NB: This is currently needed to make get_action_events_by_timesort return the right thing.
1181              // It needs to be fixed, see MDL-58736.
1182              $this->setUser($users[$description]);
1183              return $carry + [
1184                  'For user ' . lcfirst($description) => $vault->get_action_events_by_course($users[$description], $course)
1185              ];
1186          }, []);
1187  
1188          foreach ($usersevents as $description => $userevents) {
1189              if ($description == 'For user in group A and B') {
1190                  // User is in both A and B, so they should see the override for both
1191                  // given that the priority is the same.
1192                  $this->assertCount(2, $userevents);
1193                  continue;
1194              }
1195  
1196              // Otherwise there should be only one assign event for each user.
1197              $this->assertCount(1, $userevents);
1198          }
1199  
1200          // User in only group A should see the group A override.
1201          $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user only in group A'][0]->get_name());
1202  
1203          // User in only group B should see the group B override.
1204          $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user only in group B'][0]->get_name());
1205  
1206          // User in group A and B should see see both overrides since the priorities are the same.
1207          $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user in group A and B'][0]->get_name());
1208          $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user in group A and B'][1]->get_name());
1209  
1210          // User in no groups should see the plain assignment event.
1211          $this->assertEquals('Assignment 1 due date', $usersevents['For user in no groups'][0]->get_name());
1212      }
1213  }