Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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

   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   * Unit tests for lib.php
  19   *
  20   * @package    mod_data
  21   * @category   phpunit
  22   * @copyright  2013 Adrian Greeve
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  namespace mod_data;
  26  
  27  use stdClass;
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  global $CFG;
  32  require_once($CFG->dirroot . '/mod/data/lib.php');
  33  
  34  /**
  35   * Unit tests for lib.php
  36   *
  37   * @package    mod_data
  38   * @copyright  2013 Adrian Greeve
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class lib_test extends \advanced_testcase {
  42  
  43      /**
  44       * @var moodle_database
  45       */
  46      protected $DB = null;
  47  
  48      /**
  49       * Tear Down to reset DB.
  50       */
  51      public function tearDown(): void {
  52          global $DB;
  53  
  54          if (isset($this->DB)) {
  55              $DB = $this->DB;
  56              $this->DB = null;
  57          }
  58      }
  59  
  60      /**
  61       * Confirms that completionentries is working
  62       * Sets it to 1, confirms that
  63       * it is not complete. Inserts a record and
  64       * confirms that it is complete.
  65       */
  66      public function test_data_completion() {
  67          global $DB, $CFG;
  68          $this->resetAfterTest();
  69          $this->setAdminUser();
  70          $CFG->enablecompletion = 1;
  71          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
  72          $record = new \stdClass();
  73          $record->course = $course->id;
  74          $record->name = "Mod data completion test";
  75          $record->intro = "Some intro of some sort";
  76          $record->completionentries = "1";
  77          /* completion=2 means Show activity commplete when condition is met and completionentries means 1 record is
  78           * required for the activity to be considered complete
  79           */
  80          $module = $this->getDataGenerator()->create_module('data', $record, array('completion' => 2, 'completionentries' => 1));
  81  
  82          $cm = get_coursemodule_from_instance('data', $module->id, $course->id);
  83          $completion = new \completion_info($course);
  84          $completiondata = $completion->get_data($cm, true, 0);
  85          /* Confirm it is not complete as there are no entries */
  86          $this->assertNotEquals(1, $completiondata->completionstate);
  87  
  88          $field = data_get_field_new('text', $module);
  89          $fielddetail = new \stdClass();
  90          $fielddetail->d = $module->id;
  91          $fielddetail->mode = 'add';
  92          $fielddetail->type = 'text';
  93          $fielddetail->sesskey = sesskey();
  94          $fielddetail->name = 'Name';
  95          $fielddetail->description = 'Some name';
  96  
  97          $field->define_field($fielddetail);
  98          $field->insert_field();
  99          $recordid = data_add_record($module);
 100  
 101          $datacontent = array();
 102          $datacontent['fieldid'] = $field->field->id;
 103          $datacontent['recordid'] = $recordid;
 104          $datacontent['content'] = 'Asterix';
 105          $contentid = $DB->insert_record('data_content', $datacontent);
 106  
 107          $cm = get_coursemodule_from_instance('data', $module->id, $course->id);
 108          $completion = new \completion_info($course);
 109          $completiondata = $completion->get_data($cm);
 110          /* Confirm it is complete because it has 1 entry */
 111          $this->assertEquals(1, $completiondata->completionstate);
 112      }
 113  
 114      public function test_data_delete_record() {
 115          global $DB;
 116  
 117          $this->resetAfterTest();
 118  
 119          // Create a record for deleting.
 120          $this->setAdminUser();
 121          $course = $this->getDataGenerator()->create_course();
 122          $record = new \stdClass();
 123          $record->course = $course->id;
 124          $record->name = "Mod data delete test";
 125          $record->intro = "Some intro of some sort";
 126  
 127          $module = $this->getDataGenerator()->create_module('data', $record);
 128  
 129          $field = data_get_field_new('text', $module);
 130  
 131          $fielddetail = new \stdClass();
 132          $fielddetail->d = $module->id;
 133          $fielddetail->mode = 'add';
 134          $fielddetail->type = 'text';
 135          $fielddetail->sesskey = sesskey();
 136          $fielddetail->name = 'Name';
 137          $fielddetail->description = 'Some name';
 138  
 139          $field->define_field($fielddetail);
 140          $field->insert_field();
 141          $recordid = data_add_record($module);
 142  
 143          $datacontent = array();
 144          $datacontent['fieldid'] = $field->field->id;
 145          $datacontent['recordid'] = $recordid;
 146          $datacontent['content'] = 'Asterix';
 147  
 148          $contentid = $DB->insert_record('data_content', $datacontent);
 149          $cm = get_coursemodule_from_instance('data', $module->id, $course->id);
 150  
 151          // Check to make sure that we have a database record.
 152          $data = $DB->get_records('data', array('id' => $module->id));
 153          $this->assertEquals(1, count($data));
 154  
 155          $datacontent = $DB->get_records('data_content', array('id' => $contentid));
 156          $this->assertEquals(1, count($datacontent));
 157  
 158          $datafields = $DB->get_records('data_fields', array('id' => $field->field->id));
 159          $this->assertEquals(1, count($datafields));
 160  
 161          $datarecords = $DB->get_records('data_records', array('id' => $recordid));
 162          $this->assertEquals(1, count($datarecords));
 163  
 164          // Test to see if a failed delete returns false.
 165          $result = data_delete_record(8798, $module, $course->id, $cm->id);
 166          $this->assertFalse($result);
 167  
 168          // Delete the record.
 169          $result = data_delete_record($recordid, $module, $course->id, $cm->id);
 170  
 171          // Check that all of the record is gone.
 172          $datacontent = $DB->get_records('data_content', array('id' => $contentid));
 173          $this->assertEquals(0, count($datacontent));
 174  
 175          $datarecords = $DB->get_records('data_records', array('id' => $recordid));
 176          $this->assertEquals(0, count($datarecords));
 177  
 178          // Make sure the function returns true on a successful deletion.
 179          $this->assertTrue($result);
 180      }
 181  
 182      /**
 183       * Test comment_created event.
 184       */
 185      public function test_data_comment_created_event() {
 186          global $CFG, $DB;
 187          require_once($CFG->dirroot . '/comment/lib.php');
 188  
 189          $this->resetAfterTest();
 190  
 191          // Create a record for deleting.
 192          $this->setAdminUser();
 193          $course = $this->getDataGenerator()->create_course();
 194          $record = new \stdClass();
 195          $record->course = $course->id;
 196          $record->name = "Mod data delete test";
 197          $record->intro = "Some intro of some sort";
 198          $record->comments = 1;
 199  
 200          $module = $this->getDataGenerator()->create_module('data', $record);
 201          $field = data_get_field_new('text', $module);
 202  
 203          $fielddetail = new \stdClass();
 204          $fielddetail->name = 'Name';
 205          $fielddetail->description = 'Some name';
 206  
 207          $field->define_field($fielddetail);
 208          $field->insert_field();
 209          $recordid = data_add_record($module);
 210  
 211          $datacontent = array();
 212          $datacontent['fieldid'] = $field->field->id;
 213          $datacontent['recordid'] = $recordid;
 214          $datacontent['content'] = 'Asterix';
 215  
 216          $contentid = $DB->insert_record('data_content', $datacontent);
 217          $cm = get_coursemodule_from_instance('data', $module->id, $course->id);
 218  
 219          $context = \context_module::instance($module->cmid);
 220          $cmt = new \stdClass();
 221          $cmt->context = $context;
 222          $cmt->course = $course;
 223          $cmt->cm = $cm;
 224          $cmt->area = 'database_entry';
 225          $cmt->itemid = $recordid;
 226          $cmt->showcount = true;
 227          $cmt->component = 'mod_data';
 228          $comment = new \comment($cmt);
 229  
 230          // Triggering and capturing the event.
 231          $sink = $this->redirectEvents();
 232          $comment->add('New comment');
 233          $events = $sink->get_events();
 234          $this->assertCount(1, $events);
 235          $event = reset($events);
 236  
 237          // Checking that the event contains the expected values.
 238          $this->assertInstanceOf('\mod_data\event\comment_created', $event);
 239          $this->assertEquals($context, $event->get_context());
 240          $url = new \moodle_url('/mod/data/view.php', array('id' => $cm->id));
 241          $this->assertEquals($url, $event->get_url());
 242          $this->assertEventContextNotUsed($event);
 243      }
 244  
 245      /**
 246       * Test comment_deleted event.
 247       */
 248      public function test_data_comment_deleted_event() {
 249          global $CFG, $DB;
 250          require_once($CFG->dirroot . '/comment/lib.php');
 251  
 252          $this->resetAfterTest();
 253  
 254          // Create a record for deleting.
 255          $this->setAdminUser();
 256          $course = $this->getDataGenerator()->create_course();
 257          $record = new \stdClass();
 258          $record->course = $course->id;
 259          $record->name = "Mod data delete test";
 260          $record->intro = "Some intro of some sort";
 261          $record->comments = 1;
 262  
 263          $module = $this->getDataGenerator()->create_module('data', $record);
 264          $field = data_get_field_new('text', $module);
 265  
 266          $fielddetail = new \stdClass();
 267          $fielddetail->name = 'Name';
 268          $fielddetail->description = 'Some name';
 269  
 270          $field->define_field($fielddetail);
 271          $field->insert_field();
 272          $recordid = data_add_record($module);
 273  
 274          $datacontent = array();
 275          $datacontent['fieldid'] = $field->field->id;
 276          $datacontent['recordid'] = $recordid;
 277          $datacontent['content'] = 'Asterix';
 278  
 279          $contentid = $DB->insert_record('data_content', $datacontent);
 280          $cm = get_coursemodule_from_instance('data', $module->id, $course->id);
 281  
 282          $context = \context_module::instance($module->cmid);
 283          $cmt = new \stdClass();
 284          $cmt->context = $context;
 285          $cmt->course = $course;
 286          $cmt->cm = $cm;
 287          $cmt->area = 'database_entry';
 288          $cmt->itemid = $recordid;
 289          $cmt->showcount = true;
 290          $cmt->component = 'mod_data';
 291          $comment = new \comment($cmt);
 292          $newcomment = $comment->add('New comment 1');
 293  
 294          // Triggering and capturing the event.
 295          $sink = $this->redirectEvents();
 296          $comment->delete($newcomment->id);
 297          $events = $sink->get_events();
 298          $this->assertCount(1, $events);
 299          $event = reset($events);
 300  
 301          // Checking that the event contains the expected values.
 302          $this->assertInstanceOf('\mod_data\event\comment_deleted', $event);
 303          $this->assertEquals($context, $event->get_context());
 304          $url = new \moodle_url('/mod/data/view.php', array('id' => $module->cmid));
 305          $this->assertEquals($url, $event->get_url());
 306          $this->assertEventContextNotUsed($event);
 307      }
 308  
 309      /**
 310       * Checks that data_user_can_manage_entry will return true if the user
 311       * has the mod/data:manageentries capability.
 312       */
 313      public function test_data_user_can_manage_entry_return_true_with_capability() {
 314  
 315          $this->resetAfterTest();
 316          $testdata = $this->create_user_test_data();
 317  
 318          $user = $testdata['user'];
 319          $course = $testdata['course'];
 320          $roleid = $testdata['roleid'];
 321          $context = $testdata['context'];
 322          $record = $testdata['record'];
 323          $data = new \stdClass();
 324  
 325          $this->setUser($user);
 326  
 327          assign_capability('mod/data:manageentries', CAP_ALLOW, $roleid, $context);
 328  
 329          $this->assertTrue(data_user_can_manage_entry($record, $data, $context),
 330              'data_user_can_manage_entry() returns true if the user has mod/data:manageentries capability');
 331      }
 332  
 333      /**
 334       * Checks that data_user_can_manage_entry will return false if the data
 335       * is set to readonly.
 336       */
 337      public function test_data_user_can_manage_entry_return_false_readonly() {
 338  
 339          $this->resetAfterTest();
 340          $testdata = $this->create_user_test_data();
 341  
 342          $user = $testdata['user'];
 343          $course = $testdata['course'];
 344          $roleid = $testdata['roleid'];
 345          $context = $testdata['context'];
 346          $record = $testdata['record'];
 347  
 348          $this->setUser($user);
 349  
 350          // Need to make sure they don't have this capability in order to fall back to
 351          // the other checks.
 352          assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
 353  
 354          // Causes readonly mode to be enabled.
 355          $data = new \stdClass();
 356          $now = time();
 357          // Add a small margin around the periods to prevent errors with slow tests.
 358          $data->timeviewfrom = $now - 1;
 359          $data->timeviewto = $now + 5;
 360  
 361          $this->assertFalse(data_user_can_manage_entry($record, $data, $context),
 362              'data_user_can_manage_entry() returns false if the data is read only');
 363      }
 364  
 365      /**
 366       * Checks that data_user_can_manage_entry will return false if the record
 367       * can't be found in the database.
 368       */
 369      public function test_data_user_can_manage_entry_return_false_no_record() {
 370  
 371          $this->resetAfterTest();
 372          $testdata = $this->create_user_test_data();
 373  
 374          $user = $testdata['user'];
 375          $course = $testdata['course'];
 376          $roleid = $testdata['roleid'];
 377          $context = $testdata['context'];
 378          $record = $testdata['record'];
 379          $data = new \stdClass();
 380          // Causes readonly mode to be disabled.
 381          $now = time();
 382          $data->timeviewfrom = $now + 100;
 383          $data->timeviewto = $now - 100;
 384  
 385          $this->setUser($user);
 386  
 387          // Need to make sure they don't have this capability in order to fall back to
 388          // the other checks.
 389          assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
 390  
 391          // Pass record id instead of object to force DB lookup.
 392          $this->assertFalse(data_user_can_manage_entry(1, $data, $context),
 393              'data_user_can_manage_entry() returns false if the record cannot be found');
 394      }
 395  
 396      /**
 397       * Checks that data_user_can_manage_entry will return false if the record
 398       * isn't owned by the user.
 399       */
 400      public function test_data_user_can_manage_entry_return_false_not_owned_record() {
 401  
 402          $this->resetAfterTest();
 403          $testdata = $this->create_user_test_data();
 404  
 405          $user = $testdata['user'];
 406          $course = $testdata['course'];
 407          $roleid = $testdata['roleid'];
 408          $context = $testdata['context'];
 409          $record = $testdata['record'];
 410          $data = new \stdClass();
 411          // Causes readonly mode to be disabled.
 412          $now = time();
 413          $data->timeviewfrom = $now + 100;
 414          $data->timeviewto = $now - 100;
 415          // Make sure the record isn't owned by this user.
 416          $record->userid = $user->id + 1;
 417  
 418          $this->setUser($user);
 419  
 420          // Need to make sure they don't have this capability in order to fall back to
 421          // the other checks.
 422          assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
 423  
 424          $this->assertFalse(data_user_can_manage_entry($record, $data, $context),
 425              'data_user_can_manage_entry() returns false if the record isnt owned by the user');
 426      }
 427  
 428      /**
 429       * Checks that data_user_can_manage_entry will return true if the data
 430       * doesn't require approval.
 431       */
 432      public function test_data_user_can_manage_entry_return_true_data_no_approval() {
 433  
 434          $this->resetAfterTest();
 435          $testdata = $this->create_user_test_data();
 436  
 437          $user = $testdata['user'];
 438          $course = $testdata['course'];
 439          $roleid = $testdata['roleid'];
 440          $context = $testdata['context'];
 441          $record = $testdata['record'];
 442          $data = new \stdClass();
 443          // Causes readonly mode to be disabled.
 444          $now = time();
 445          $data->timeviewfrom = $now + 100;
 446          $data->timeviewto = $now - 100;
 447          // The record doesn't need approval.
 448          $data->approval = false;
 449          // Make sure the record is owned by this user.
 450          $record->userid = $user->id;
 451  
 452          $this->setUser($user);
 453  
 454          // Need to make sure they don't have this capability in order to fall back to
 455          // the other checks.
 456          assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
 457  
 458          $this->assertTrue(data_user_can_manage_entry($record, $data, $context),
 459              'data_user_can_manage_entry() returns true if the record doesnt require approval');
 460      }
 461  
 462      /**
 463       * Checks that data_user_can_manage_entry will return true if the record
 464       * isn't yet approved.
 465       */
 466      public function test_data_user_can_manage_entry_return_true_record_unapproved() {
 467  
 468          $this->resetAfterTest();
 469          $testdata = $this->create_user_test_data();
 470  
 471          $user = $testdata['user'];
 472          $course = $testdata['course'];
 473          $roleid = $testdata['roleid'];
 474          $context = $testdata['context'];
 475          $record = $testdata['record'];
 476          $data = new \stdClass();
 477          // Causes readonly mode to be disabled.
 478          $now = time();
 479          $data->timeviewfrom = $now + 100;
 480          $data->timeviewto = $now - 100;
 481          // The record needs approval.
 482          $data->approval = true;
 483          // Make sure the record is owned by this user.
 484          $record->userid = $user->id;
 485          // The record hasn't yet been approved.
 486          $record->approved = false;
 487  
 488          $this->setUser($user);
 489  
 490          // Need to make sure they don't have this capability in order to fall back to
 491          // the other checks.
 492          assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
 493  
 494          $this->assertTrue(data_user_can_manage_entry($record, $data, $context),
 495              'data_user_can_manage_entry() returns true if the record is not yet approved');
 496      }
 497  
 498      /**
 499       * Checks that data_user_can_manage_entry will return the 'manageapproved'
 500       * value if the record has already been approved.
 501       */
 502      public function test_data_user_can_manage_entry_return_manageapproved() {
 503  
 504          $this->resetAfterTest();
 505          $testdata = $this->create_user_test_data();
 506  
 507          $user = $testdata['user'];
 508          $course = $testdata['course'];
 509          $roleid = $testdata['roleid'];
 510          $context = $testdata['context'];
 511          $record = $testdata['record'];
 512          $data = new \stdClass();
 513          // Causes readonly mode to be disabled.
 514          $now = time();
 515          $data->timeviewfrom = $now + 100;
 516          $data->timeviewto = $now - 100;
 517          // The record needs approval.
 518          $data->approval = true;
 519          // Can the user managed approved records?
 520          $data->manageapproved = false;
 521          // Make sure the record is owned by this user.
 522          $record->userid = $user->id;
 523          // The record has been approved.
 524          $record->approved = true;
 525  
 526          $this->setUser($user);
 527  
 528          // Need to make sure they don't have this capability in order to fall back to
 529          // the other checks.
 530          assign_capability('mod/data:manageentries', CAP_PROHIBIT, $roleid, $context);
 531  
 532          $canmanageentry = data_user_can_manage_entry($record, $data, $context);
 533  
 534          // Make sure the result of the check is what ever the manageapproved setting
 535          // is set to.
 536          $this->assertEquals($data->manageapproved, $canmanageentry,
 537              'data_user_can_manage_entry() returns the manageapproved setting on approved records');
 538      }
 539  
 540      /**
 541       * Helper method to create a set of test data for data_user_can_manage tests
 542       *
 543       * @return array contains user, course, roleid, module, context and record
 544       */
 545      private function create_user_test_data() {
 546          $user = $this->getDataGenerator()->create_user();
 547          $course = $this->getDataGenerator()->create_course();
 548          $roleid = $this->getDataGenerator()->create_role();
 549          $record = new \stdClass();
 550          $record->name = "test name";
 551          $record->intro = "test intro";
 552          $record->comments = 1;
 553          $record->course = $course->id;
 554          $record->userid = $user->id;
 555  
 556          $module = $this->getDataGenerator()->create_module('data', $record);
 557          $cm = get_coursemodule_from_instance('data', $module->id, $course->id);
 558          $context = \context_module::instance($module->cmid);
 559  
 560          $this->getDataGenerator()->role_assign($roleid, $user->id, $context->id);
 561  
 562          return array(
 563              'user' => $user,
 564              'course' => $course,
 565              'roleid' => $roleid,
 566              'module' => $module,
 567              'context' => $context,
 568              'record' => $record
 569          );
 570      }
 571  
 572      /**
 573       * Tests for mod_data_rating_can_see_item_ratings().
 574       *
 575       * @throws coding_exception
 576       * @throws rating_exception
 577       */
 578      public function test_mod_data_rating_can_see_item_ratings() {
 579          global $DB;
 580  
 581          $this->resetAfterTest();
 582  
 583          // Setup test data.
 584          $course = new \stdClass();
 585          $course->groupmode = SEPARATEGROUPS;
 586          $course->groupmodeforce = true;
 587          $course = $this->getDataGenerator()->create_course($course);
 588          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id));
 589          $cm = get_coursemodule_from_instance('data', $data->id);
 590          $context = \context_module::instance($cm->id);
 591  
 592          // Create users.
 593          $user1 = $this->getDataGenerator()->create_user();
 594          $user2 = $this->getDataGenerator()->create_user();
 595          $user3 = $this->getDataGenerator()->create_user();
 596          $user4 = $this->getDataGenerator()->create_user();
 597  
 598          // Groups and stuff.
 599          $role = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
 600          $this->getDataGenerator()->enrol_user($user1->id, $course->id, $role->id);
 601          $this->getDataGenerator()->enrol_user($user2->id, $course->id, $role->id);
 602          $this->getDataGenerator()->enrol_user($user3->id, $course->id, $role->id);
 603          $this->getDataGenerator()->enrol_user($user4->id, $course->id, $role->id);
 604  
 605          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 606          $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 607          groups_add_member($group1, $user1);
 608          groups_add_member($group1, $user2);
 609          groups_add_member($group2, $user3);
 610          groups_add_member($group2, $user4);
 611  
 612          // Add data.
 613          $field = data_get_field_new('text', $data);
 614  
 615          $fielddetail = new \stdClass();
 616          $fielddetail->name = 'Name';
 617          $fielddetail->description = 'Some name';
 618  
 619          $field->define_field($fielddetail);
 620          $field->insert_field();
 621  
 622          // Add a record with a group id of zero (all participants).
 623          $recordid1 = data_add_record($data, 0);
 624  
 625          $datacontent = array();
 626          $datacontent['fieldid'] = $field->field->id;
 627          $datacontent['recordid'] = $recordid1;
 628          $datacontent['content'] = 'Obelix';
 629          $DB->insert_record('data_content', $datacontent);
 630  
 631          $recordid = data_add_record($data, $group1->id);
 632  
 633          $datacontent = array();
 634          $datacontent['fieldid'] = $field->field->id;
 635          $datacontent['recordid'] = $recordid;
 636          $datacontent['content'] = 'Asterix';
 637          $DB->insert_record('data_content', $datacontent);
 638  
 639          // Now try to access it as various users.
 640          unassign_capability('moodle/site:accessallgroups', $role->id);
 641          // Eveyone should have access to the record with the group id of zero.
 642          $params1 = array('contextid' => 2,
 643                          'component' => 'mod_data',
 644                          'ratingarea' => 'entry',
 645                          'itemid' => $recordid1,
 646                          'scaleid' => 2);
 647  
 648          $params = array('contextid' => 2,
 649                          'component' => 'mod_data',
 650                          'ratingarea' => 'entry',
 651                          'itemid' => $recordid,
 652                          'scaleid' => 2);
 653  
 654          $this->setUser($user1);
 655          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 656          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 657          $this->setUser($user2);
 658          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 659          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 660          $this->setUser($user3);
 661          $this->assertFalse(mod_data_rating_can_see_item_ratings($params));
 662          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 663          $this->setUser($user4);
 664          $this->assertFalse(mod_data_rating_can_see_item_ratings($params));
 665          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 666  
 667          // Now try with accessallgroups cap and make sure everything is visible.
 668          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $role->id, $context->id);
 669          $this->setUser($user1);
 670          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 671          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 672          $this->setUser($user2);
 673          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 674          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 675          $this->setUser($user3);
 676          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 677          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 678          $this->setUser($user4);
 679          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 680          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 681  
 682          // Change group mode and verify visibility.
 683          $course->groupmode = VISIBLEGROUPS;
 684          $DB->update_record('course', $course);
 685          unassign_capability('moodle/site:accessallgroups', $role->id);
 686          $this->setUser($user1);
 687          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 688          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 689          $this->setUser($user2);
 690          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 691          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 692          $this->setUser($user3);
 693          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 694          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 695          $this->setUser($user4);
 696          $this->assertTrue(mod_data_rating_can_see_item_ratings($params));
 697          $this->assertTrue(mod_data_rating_can_see_item_ratings($params1));
 698  
 699      }
 700  
 701      /**
 702       * Tests for mod_data_refresh_events.
 703       */
 704      public function test_data_refresh_events() {
 705          global $DB;
 706          $this->resetAfterTest();
 707          $this->setAdminUser();
 708  
 709          $timeopen = time();
 710          $timeclose = time() + 86400;
 711  
 712          $course = $this->getDataGenerator()->create_course();
 713          $generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
 714          $params['course'] = $course->id;
 715          $params['timeavailablefrom'] = $timeopen;
 716          $params['timeavailableto'] = $timeclose;
 717          $data = $generator->create_instance($params);
 718  
 719          // Normal case, with existing course.
 720          $this->assertTrue(data_refresh_events($course->id));
 721          $eventparams = array('modulename' => 'data', 'instance' => $data->id, 'eventtype' => 'open');
 722          $openevent = $DB->get_record('event', $eventparams, '*', MUST_EXIST);
 723          $this->assertEquals($openevent->timestart, $timeopen);
 724  
 725          $eventparams = array('modulename' => 'data', 'instance' => $data->id, 'eventtype' => 'close');
 726          $closeevent = $DB->get_record('event', $eventparams, '*', MUST_EXIST);
 727          $this->assertEquals($closeevent->timestart, $timeclose);
 728          // In case the course ID is passed as a numeric string.
 729          $this->assertTrue(data_refresh_events('' . $course->id));
 730          // Course ID not provided.
 731          $this->assertTrue(data_refresh_events());
 732          $eventparams = array('modulename' => 'data');
 733          $events = $DB->get_records('event', $eventparams);
 734          foreach ($events as $event) {
 735              if ($event->modulename === 'data' && $event->instance === $data->id && $event->eventtype === 'open') {
 736                  $this->assertEquals($event->timestart, $timeopen);
 737              }
 738              if ($event->modulename === 'data' && $event->instance === $data->id && $event->eventtype === 'close') {
 739                  $this->assertEquals($event->timestart, $timeclose);
 740              }
 741          }
 742      }
 743  
 744      /**
 745       * Data provider for tests of data_get_config.
 746       *
 747       * @return array
 748       */
 749      public function data_get_config_provider() {
 750          $initialdata = (object) [
 751              'template_foo' => true,
 752              'template_bar' => false,
 753              'template_baz' => null,
 754          ];
 755  
 756          $database = (object) [
 757              'config' => json_encode($initialdata),
 758          ];
 759  
 760          return [
 761              'Return full dataset (no key/default)' => [
 762                  [$database],
 763                  $initialdata,
 764              ],
 765              'Return full dataset (no default)' => [
 766                  [$database, null],
 767                  $initialdata,
 768              ],
 769              'Return full dataset' => [
 770                  [$database, null, null],
 771                  $initialdata,
 772              ],
 773              'Return requested key only, value true, no default' => [
 774                  [$database, 'template_foo'],
 775                  true,
 776              ],
 777              'Return requested key only, value false, no default' => [
 778                  [$database, 'template_bar'],
 779                  false,
 780              ],
 781              'Return requested key only, value null, no default' => [
 782                  [$database, 'template_baz'],
 783                  null,
 784              ],
 785              'Return unknown key, value null, no default' => [
 786                  [$database, 'template_bum'],
 787                  null,
 788              ],
 789              'Return requested key only, value true, default null' => [
 790                  [$database, 'template_foo', null],
 791                  true,
 792              ],
 793              'Return requested key only, value false, default null' => [
 794                  [$database, 'template_bar', null],
 795                  false,
 796              ],
 797              'Return requested key only, value null, default null' => [
 798                  [$database, 'template_baz', null],
 799                  null,
 800              ],
 801              'Return unknown key, value null, default null' => [
 802                  [$database, 'template_bum', null],
 803                  null,
 804              ],
 805              'Return requested key only, value true, default 42' => [
 806                  [$database, 'template_foo', 42],
 807                  true,
 808              ],
 809              'Return requested key only, value false, default 42' => [
 810                  [$database, 'template_bar', 42],
 811                  false,
 812              ],
 813              'Return requested key only, value null, default 42' => [
 814                  [$database, 'template_baz', 42],
 815                  null,
 816              ],
 817              'Return unknown key, value null, default 42' => [
 818                  [$database, 'template_bum', 42],
 819                  42,
 820              ],
 821          ];
 822      }
 823  
 824      /**
 825       * Tests for data_get_config.
 826       *
 827       * @dataProvider    data_get_config_provider
 828       * @param   array   $funcargs       The args to pass to data_get_config
 829       * @param   mixed   $expectation    The expected value
 830       */
 831      public function test_data_get_config($funcargs, $expectation) {
 832          $this->assertEquals($expectation, call_user_func_array('data_get_config', $funcargs));
 833      }
 834  
 835      /**
 836       * Data provider for tests of data_set_config.
 837       *
 838       * @return array
 839       */
 840      public function data_set_config_provider() {
 841          $basevalue = (object) ['id' => rand(1, 1000)];
 842          $config = [
 843              'template_foo'  => true,
 844              'template_bar'  => false,
 845          ];
 846  
 847          $withvalues = clone $basevalue;
 848          $withvalues->config = json_encode((object) $config);
 849  
 850          return [
 851              'Empty config, New value' => [
 852                  $basevalue,
 853                  'etc',
 854                  'newvalue',
 855                  true,
 856                  json_encode((object) ['etc' => 'newvalue'])
 857              ],
 858              'Has config, New value' => [
 859                  clone $withvalues,
 860                  'etc',
 861                  'newvalue',
 862                  true,
 863                  json_encode((object) array_merge($config, ['etc' => 'newvalue']))
 864              ],
 865              'Has config, Update value, string' => [
 866                  clone $withvalues,
 867                  'template_foo',
 868                  'newvalue',
 869                  true,
 870                  json_encode((object) array_merge($config, ['template_foo' => 'newvalue']))
 871              ],
 872              'Has config, Update value, true' => [
 873                  clone $withvalues,
 874                  'template_bar',
 875                  true,
 876                  true,
 877                  json_encode((object) array_merge($config, ['template_bar' => true]))
 878              ],
 879              'Has config, Update value, false' => [
 880                  clone $withvalues,
 881                  'template_foo',
 882                  false,
 883                  true,
 884                  json_encode((object) array_merge($config, ['template_foo' => false]))
 885              ],
 886              'Has config, Update value, null' => [
 887                  clone $withvalues,
 888                  'template_foo',
 889                  null,
 890                  true,
 891                  json_encode((object) array_merge($config, ['template_foo' => null]))
 892              ],
 893              'Has config, No update, value true' => [
 894                  clone $withvalues,
 895                  'template_foo',
 896                  true,
 897                  false,
 898                  $withvalues->config,
 899              ],
 900          ];
 901      }
 902  
 903      /**
 904       * Tests for data_set_config.
 905       *
 906       * @dataProvider    data_set_config_provider
 907       * @param   object  $database       The example row for the entry
 908       * @param   string  $key            The config key to set
 909       * @param   mixed   $value          The value of the key
 910       * @param   bool    $expectupdate   Whether we expected an update
 911       * @param   mixed   $newconfigvalue The expected value
 912       */
 913      public function test_data_set_config($database, $key, $value, $expectupdate, $newconfigvalue) {
 914          global $DB;
 915  
 916          // Mock the database.
 917          // Note: Use the actual test class here rather than the abstract because are testing concrete methods.
 918          $this->DB = $DB;
 919          $DB = $this->getMockBuilder(get_class($DB))
 920              ->onlyMethods(['set_field'])
 921              ->getMock();
 922  
 923          $DB->expects($this->exactly((int) $expectupdate))
 924              ->method('set_field')
 925              ->with(
 926                  'data',
 927                  'config',
 928                  $newconfigvalue,
 929                  ['id' => $database->id]
 930              );
 931  
 932          // Perform the update.
 933          data_set_config($database, $key, $value);
 934  
 935          // Ensure that the value was updated by reference in $database.
 936          $config = json_decode($database->config);
 937          $this->assertEquals($value, $config->$key);
 938      }
 939  
 940      public function test_mod_data_get_tagged_records() {
 941          $this->resetAfterTest();
 942          $this->setAdminUser();
 943  
 944          // Setup test data.
 945          $datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
 946          $course1 = $this->getDataGenerator()->create_course();
 947  
 948          $fieldrecord = new \stdClass();
 949          $fieldrecord->name = 'field-1';
 950          $fieldrecord->type = 'text';
 951  
 952          $data1 = $this->getDataGenerator()->create_module('data', array('course' => $course1->id, 'approval' => true));
 953          $field1 = $datagenerator->create_field($fieldrecord, $data1);
 954  
 955          $datagenerator->create_entry($data1, [$field1->field->id => 'value11'], 0, ['Cats', 'Dogs']);
 956          $datagenerator->create_entry($data1, [$field1->field->id => 'value12'], 0, ['Cats', 'mice']);
 957          $datagenerator->create_entry($data1, [$field1->field->id => 'value13'], 0, ['Cats']);
 958          $datagenerator->create_entry($data1, [$field1->field->id => 'value14'], 0);
 959  
 960          $tag = \core_tag_tag::get_by_name(0, 'Cats');
 961  
 962          // Admin can see everything.
 963          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
 964          $this->assertStringContainsString('value11', $res->content);
 965          $this->assertStringContainsString('value12', $res->content);
 966          $this->assertStringContainsString('value13', $res->content);
 967          $this->assertStringNotContainsString('value14', $res->content);
 968      }
 969  
 970      public function test_mod_data_get_tagged_records_approval() {
 971          global $DB;
 972  
 973          $this->resetAfterTest();
 974          $this->setAdminUser();
 975  
 976          // Setup test data.
 977          $datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
 978          $course2 = $this->getDataGenerator()->create_course();
 979          $course1 = $this->getDataGenerator()->create_course();
 980  
 981          $fieldrecord = new \stdClass();
 982          $fieldrecord->name = 'field-1';
 983          $fieldrecord->type = 'text';
 984  
 985          $data1 = $this->getDataGenerator()->create_module('data', array('course' => $course1->id));
 986          $field1 = $datagenerator->create_field($fieldrecord, $data1);
 987          $data2 = $this->getDataGenerator()->create_module('data', array('course' => $course2->id, 'approval' => true));
 988          $field2 = $datagenerator->create_field($fieldrecord, $data2);
 989  
 990          $record11 = $datagenerator->create_entry($data1, [$field1->field->id => 'value11'], 0, ['Cats', 'Dogs']);
 991          $record21 = $datagenerator->create_entry($data2, [$field2->field->id => 'value21'], 0, ['Cats'], ['approved' => false]);
 992          $tag = \core_tag_tag::get_by_name(0, 'Cats');
 993  
 994          // Admin can see everything.
 995          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
 996          $this->assertStringContainsString('value11', $res->content);
 997          $this->assertStringContainsString('value21', $res->content);
 998          $this->assertEmpty($res->prevpageurl);
 999          $this->assertEmpty($res->nextpageurl);
1000  
1001          // Create and enrol a user.
1002          $student = self::getDataGenerator()->create_user();
1003          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1004          $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
1005          $this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentrole->id, 'manual');
1006          $this->setUser($student);
1007  
1008          // User can search data records inside a course.
1009          \core_tag_index_builder::reset_caches();
1010          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1011  
1012          $this->assertStringContainsString('value11', $res->content);
1013          $this->assertStringNotContainsString('value21', $res->content);
1014  
1015          $recordtoupdate = new \stdClass();
1016          $recordtoupdate->id = $record21;
1017          $recordtoupdate->approved = true;
1018          $DB->update_record('data_records', $recordtoupdate);
1019  
1020          \core_tag_index_builder::reset_caches();
1021          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1022  
1023          $this->assertStringContainsString('value11', $res->content);
1024          $this->assertStringContainsString('value21', $res->content);
1025      }
1026  
1027      public function test_mod_data_get_tagged_records_time() {
1028          global $DB;
1029  
1030          $this->resetAfterTest();
1031          $this->setAdminUser();
1032  
1033          // Setup test data.
1034          $datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
1035          $course2 = $this->getDataGenerator()->create_course();
1036          $course1 = $this->getDataGenerator()->create_course();
1037  
1038          $fieldrecord = new \stdClass();
1039          $fieldrecord->name = 'field-1';
1040          $fieldrecord->type = 'text';
1041  
1042          $timefrom = time() - YEARSECS;
1043          $timeto = time() - WEEKSECS;
1044  
1045          $data1 = $this->getDataGenerator()->create_module('data', array('course' => $course1->id, 'approval' => true));
1046          $field1 = $datagenerator->create_field($fieldrecord, $data1);
1047          $data2 = $this->getDataGenerator()->create_module('data', array('course' => $course2->id,
1048                                                                          'timeviewfrom' => $timefrom,
1049                                                                          'timeviewto'   => $timeto));
1050          $field2 = $datagenerator->create_field($fieldrecord, $data2);
1051          $record11 = $datagenerator->create_entry($data1, [$field1->field->id => 'value11'], 0, ['Cats', 'Dogs']);
1052          $record21 = $datagenerator->create_entry($data2, [$field2->field->id => 'value21'], 0, ['Cats']);
1053          $tag = \core_tag_tag::get_by_name(0, 'Cats');
1054  
1055          // Admin can see everything.
1056          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1057          $this->assertStringContainsString('value11', $res->content);
1058          $this->assertStringContainsString('value21', $res->content);
1059          $this->assertEmpty($res->prevpageurl);
1060          $this->assertEmpty($res->nextpageurl);
1061  
1062          // Create and enrol a user.
1063          $student = self::getDataGenerator()->create_user();
1064          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1065          $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
1066          $this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentrole->id, 'manual');
1067          $this->setUser($student);
1068  
1069          // User can search data records inside a course.
1070          \core_tag_index_builder::reset_caches();
1071          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1072  
1073          $this->assertStringContainsString('value11', $res->content);
1074          $this->assertStringNotContainsString('value21', $res->content);
1075  
1076          $data2->timeviewto = time() + YEARSECS;
1077          $DB->update_record('data', $data2);
1078  
1079          \core_tag_index_builder::reset_caches();
1080          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1081  
1082          $this->assertStringContainsString('value11', $res->content);
1083          $this->assertStringContainsString('value21', $res->content);
1084      }
1085  
1086      public function test_mod_data_get_tagged_records_course_enrolment() {
1087          global $DB;
1088  
1089          $this->resetAfterTest();
1090          $this->setAdminUser();
1091  
1092          // Setup test data.
1093          $datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
1094          $course2 = $this->getDataGenerator()->create_course();
1095          $course1 = $this->getDataGenerator()->create_course();
1096  
1097          $fieldrecord = new \stdClass();
1098          $fieldrecord->name = 'field-1';
1099          $fieldrecord->type = 'text';
1100  
1101          $data1 = $this->getDataGenerator()->create_module('data', array('course' => $course1->id, 'approval' => true));
1102          $field1 = $datagenerator->create_field($fieldrecord, $data1);
1103          $data2 = $this->getDataGenerator()->create_module('data', array('course' => $course2->id));
1104          $field2 = $datagenerator->create_field($fieldrecord, $data2);
1105  
1106          $record11 = $datagenerator->create_entry($data1, [$field1->field->id => 'value11'], 0, ['Cats', 'Dogs']);
1107          $record21 = $datagenerator->create_entry($data2, [$field2->field->id => 'value21'], 0, ['Cats']);
1108          $tag = \core_tag_tag::get_by_name(0, 'Cats');
1109  
1110          // Admin can see everything.
1111          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1112          $this->assertStringContainsString('value11', $res->content);
1113          $this->assertStringContainsString('value21', $res->content);
1114          $this->assertEmpty($res->prevpageurl);
1115          $this->assertEmpty($res->nextpageurl);
1116  
1117          // Create and enrol a user.
1118          $student = self::getDataGenerator()->create_user();
1119          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1120          $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
1121          $this->setUser($student);
1122          \core_tag_index_builder::reset_caches();
1123  
1124          // User can search data records inside a course.
1125          $coursecontext = \context_course::instance($course1->id);
1126          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1127  
1128          $this->assertStringContainsString('value11', $res->content);
1129          $this->assertStringNotContainsString('value21', $res->content);
1130  
1131          $this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentrole->id, 'manual');
1132  
1133          \core_tag_index_builder::reset_caches();
1134          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1135  
1136          $this->assertStringContainsString('value11', $res->content);
1137          $this->assertStringContainsString('value21', $res->content);
1138      }
1139  
1140      public function test_mod_data_get_tagged_records_course_groups() {
1141          global $DB;
1142  
1143          $this->resetAfterTest();
1144          $this->setAdminUser();
1145  
1146          // Setup test data.
1147          $datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
1148          $course2 = $this->getDataGenerator()->create_course();
1149          $course1 = $this->getDataGenerator()->create_course();
1150  
1151          $groupa = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'name' => 'groupA'));
1152          $groupb = $this->getDataGenerator()->create_group(array('courseid' => $course2->id, 'name' => 'groupB'));
1153  
1154          $fieldrecord = new \stdClass();
1155          $fieldrecord->name = 'field-1';
1156          $fieldrecord->type = 'text';
1157  
1158          $data1 = $this->getDataGenerator()->create_module('data', array('course' => $course1->id, 'approval' => true));
1159          $field1 = $datagenerator->create_field($fieldrecord, $data1);
1160          $data2 = $this->getDataGenerator()->create_module('data', array('course' => $course2->id));
1161          $field2 = $datagenerator->create_field($fieldrecord, $data2);
1162          set_coursemodule_groupmode($data2->cmid, SEPARATEGROUPS);
1163  
1164          $record11 = $datagenerator->create_entry($data1, [$field1->field->id => 'value11'],
1165                  0, ['Cats', 'Dogs']);
1166          $record21 = $datagenerator->create_entry($data2, [$field2->field->id => 'value21'],
1167                  $groupa->id, ['Cats']);
1168          $record22 = $datagenerator->create_entry($data2, [$field2->field->id => 'value22'],
1169                  $groupb->id, ['Cats']);
1170          $tag = \core_tag_tag::get_by_name(0, 'Cats');
1171  
1172          // Admin can see everything.
1173          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1174          $this->assertStringContainsString('value11', $res->content);
1175          $this->assertStringContainsString('value21', $res->content);
1176          $this->assertStringContainsString('value22', $res->content);
1177          $this->assertEmpty($res->prevpageurl);
1178          $this->assertEmpty($res->nextpageurl);
1179  
1180          // Create and enrol a user.
1181          $student = self::getDataGenerator()->create_user();
1182          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1183          $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
1184          $this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentrole->id, 'manual');
1185          groups_add_member($groupa, $student);
1186          $this->setUser($student);
1187          \core_tag_index_builder::reset_caches();
1188  
1189          // User can search data records inside a course.
1190          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1191  
1192          $this->assertStringContainsString('value11', $res->content);
1193          $this->assertStringContainsString('value21', $res->content);
1194          $this->assertStringNotContainsString('value22', $res->content);
1195  
1196          groups_add_member($groupb, $student);
1197          \core_tag_index_builder::reset_caches();
1198          $res = mod_data_get_tagged_records($tag, false, 0, 0, 1, 0);
1199  
1200          $this->assertStringContainsString('value11', $res->content);
1201          $this->assertStringContainsString('value21', $res->content);
1202          $this->assertStringContainsString('value22', $res->content);
1203      }
1204  
1205      /**
1206       * Test check_updates_since callback.
1207       */
1208      public function test_check_updates_since() {
1209          global $DB;
1210          $this->resetAfterTest();
1211          $this->setAdminUser();
1212          $course = $this->getDataGenerator()->create_course();
1213          // Create user.
1214          $student = self::getDataGenerator()->create_user();
1215          // User enrolment.
1216          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1217          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
1218          $this->setCurrentTimeStart();
1219          $record = array(
1220              'course' => $course->id,
1221          );
1222          $data = $this->getDataGenerator()->create_module('data', $record);
1223          $cm = get_coursemodule_from_instance('data', $data->id, $course->id);
1224          $cm = \cm_info::create($cm);
1225          $this->setUser($student);
1226  
1227          // Check that upon creation, the updates are only about the new configuration created.
1228          $onehourago = time() - HOURSECS;
1229          $updates = data_check_updates_since($cm, $onehourago);
1230          foreach ($updates as $el => $val) {
1231              if ($el == 'configuration') {
1232                  $this->assertTrue($val->updated);
1233                  $this->assertTimeCurrent($val->timeupdated);
1234              } else {
1235                  $this->assertFalse($val->updated);
1236              }
1237          }
1238  
1239          // Add a couple of entries.
1240          $datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
1241          $fieldtypes = array('checkbox', 'date');
1242  
1243          $count = 1;
1244          // Creating test Fields with default parameter values.
1245          foreach ($fieldtypes as $fieldtype) {
1246              // Creating variables dynamically.
1247              $fieldname = 'field-' . $count;
1248              $record = new \stdClass();
1249              $record->name = $fieldname;
1250              $record->type = $fieldtype;
1251              $record->required = 1;
1252  
1253              ${$fieldname} = $datagenerator->create_field($record, $data);
1254              $count++;
1255          }
1256  
1257          $fields = $DB->get_records('data_fields', array('dataid' => $data->id), 'id');
1258  
1259          $contents = array();
1260          $contents[] = array('opt1', 'opt2', 'opt3', 'opt4');
1261          $contents[] = '01-01-2037'; // It should be lower than 2038, to avoid failing on 32-bit windows.
1262          $count = 0;
1263          $fieldcontents = array();
1264          foreach ($fields as $fieldrecord) {
1265              $fieldcontents[$fieldrecord->id] = $contents[$count++];
1266          }
1267  
1268          $datarecor1did = $datagenerator->create_entry($data, $fieldcontents);
1269          $datarecor2did = $datagenerator->create_entry($data, $fieldcontents);
1270          $records = $DB->get_records('data_records', array('dataid' => $data->id));
1271          $this->assertCount(2, $records);
1272          // Check we received the entries updated.
1273          $updates = data_check_updates_since($cm, $onehourago);
1274          $this->assertTrue($updates->entries->updated);
1275          $this->assertEqualsCanonicalizing([$datarecor1did, $datarecor2did], $updates->entries->itemids);
1276      }
1277  
1278      public function test_data_core_calendar_provide_event_action_in_hidden_section() {
1279          global $CFG;
1280  
1281          $this->resetAfterTest();
1282  
1283          $this->setAdminUser();
1284  
1285          // Create a course.
1286          $course = $this->getDataGenerator()->create_course();
1287  
1288          // Create a student.
1289          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1290  
1291          // Create a database activity.
1292          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id,
1293                  'timeavailablefrom' => time() - DAYSECS, 'timeavailableto' => time() + DAYSECS));
1294  
1295          // Create a calendar event.
1296          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1297  
1298          // Set sections 0 as hidden.
1299          set_section_visible($course->id, 0, 0);
1300  
1301          // Now, log out.
1302          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
1303          $this->setUser();
1304  
1305          // Create an action factory.
1306          $factory = new \core_calendar\action_factory();
1307  
1308          // Decorate action event for the student.
1309          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory, $student->id);
1310  
1311          // Confirm the event is not shown at all.
1312          $this->assertNull($actionevent);
1313      }
1314  
1315      public function test_data_core_calendar_provide_event_action_for_non_user() {
1316          global $CFG;
1317  
1318          $this->resetAfterTest();
1319  
1320          $this->setAdminUser();
1321  
1322          // Create a course.
1323          $course = $this->getDataGenerator()->create_course();
1324  
1325          // Create a database activity.
1326          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id,
1327              'timeavailablefrom' => time() - DAYSECS, 'timeavailableto' => time() + DAYSECS));
1328  
1329          // Create a calendar event.
1330          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1331  
1332          // Now, log out.
1333          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
1334          $this->setUser();
1335  
1336          // Create an action factory.
1337          $factory = new \core_calendar\action_factory();
1338  
1339          // Decorate action event.
1340          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory);
1341  
1342          // Confirm the event is not shown at all.
1343          $this->assertNull($actionevent);
1344      }
1345  
1346      public function test_data_core_calendar_provide_event_action_open() {
1347          $this->resetAfterTest();
1348  
1349          $this->setAdminUser();
1350  
1351          // Create a course.
1352          $course = $this->getDataGenerator()->create_course();
1353  
1354          // Create a database activity.
1355          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id,
1356              'timeavailablefrom' => time() - DAYSECS, 'timeavailableto' => time() + DAYSECS));
1357  
1358          // Create a calendar event.
1359          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1360  
1361          // Create an action factory.
1362          $factory = new \core_calendar\action_factory();
1363  
1364          // Decorate action event.
1365          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory);
1366  
1367          // Confirm the event was decorated.
1368          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
1369          $this->assertEquals(get_string('add', 'data'), $actionevent->get_name());
1370          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
1371          $this->assertEquals(1, $actionevent->get_item_count());
1372          $this->assertTrue($actionevent->is_actionable());
1373      }
1374  
1375      public function test_data_core_calendar_provide_event_action_open_for_user() {
1376          global $CFG;
1377  
1378          $this->resetAfterTest();
1379  
1380          $this->setAdminUser();
1381  
1382          // Create a course.
1383          $course = $this->getDataGenerator()->create_course();
1384  
1385          // Create a student.
1386          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1387  
1388          // Create a database activity.
1389          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id,
1390              'timeavailablefrom' => time() - DAYSECS, 'timeavailableto' => time() + DAYSECS));
1391  
1392          // Create a calendar event.
1393          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1394  
1395          // Now log out.
1396          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
1397          $this->setUser();
1398  
1399          // Create an action factory.
1400          $factory = new \core_calendar\action_factory();
1401  
1402          // Decorate action event for the student.
1403          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory, $student->id);
1404  
1405          // Confirm the event was decorated.
1406          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
1407          $this->assertEquals(get_string('add', 'data'), $actionevent->get_name());
1408          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
1409          $this->assertEquals(1, $actionevent->get_item_count());
1410          $this->assertTrue($actionevent->is_actionable());
1411      }
1412  
1413      public function test_data_core_calendar_provide_event_action_closed() {
1414          $this->resetAfterTest();
1415  
1416          $this->setAdminUser();
1417  
1418          // Create a course.
1419          $course = $this->getDataGenerator()->create_course();
1420  
1421          // Create a database activity.
1422          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id,
1423              'timeavailableto' => time() - DAYSECS));
1424  
1425          // Create a calendar event.
1426          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1427  
1428          // Create an action factory.
1429          $factory = new \core_calendar\action_factory();
1430  
1431          // Decorate action event.
1432          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory);
1433  
1434          // No event on the dashboard if module is closed.
1435          $this->assertNull($actionevent);
1436      }
1437  
1438      public function test_data_core_calendar_provide_event_action_closed_for_user() {
1439          $this->resetAfterTest();
1440  
1441          $this->setAdminUser();
1442  
1443          // Create a course.
1444          $course = $this->getDataGenerator()->create_course();
1445  
1446          // Create a student.
1447          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1448  
1449          // Create a database activity.
1450          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id,
1451              'timeavailableto' => time() - DAYSECS));
1452  
1453          // Create a calendar event.
1454          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1455  
1456          // Now log out.
1457          $this->setUser();
1458  
1459          // Create an action factory.
1460          $factory = new \core_calendar\action_factory();
1461  
1462          // Decorate action event for the student.
1463          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory, $student->id);
1464  
1465          // No event on the dashboard if module is closed.
1466          $this->assertNull($actionevent);
1467      }
1468  
1469      public function test_data_core_calendar_provide_event_action_open_in_future() {
1470          $this->resetAfterTest();
1471  
1472          $this->setAdminUser();
1473  
1474          // Create a course.
1475          $course = $this->getDataGenerator()->create_course();
1476  
1477          // Create a database activity.
1478          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id,
1479              'timeavailablefrom' => time() + DAYSECS));
1480  
1481          // Create a calendar event.
1482          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1483  
1484          // Create an action factory.
1485          $factory = new \core_calendar\action_factory();
1486  
1487          // Decorate action event.
1488          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory);
1489  
1490          // Confirm the event was decorated.
1491          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
1492          $this->assertEquals(get_string('add', 'data'), $actionevent->get_name());
1493          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
1494          $this->assertEquals(1, $actionevent->get_item_count());
1495          $this->assertFalse($actionevent->is_actionable());
1496      }
1497  
1498      public function test_data_core_calendar_provide_event_action_open_in_future_for_user() {
1499          global $CFG;
1500  
1501          $this->resetAfterTest();
1502  
1503          $this->setAdminUser();
1504  
1505          // Create a course.
1506          $course = $this->getDataGenerator()->create_course();
1507  
1508          // Create a student.
1509          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1510  
1511          // Create a database activity.
1512          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id,
1513              'timeavailablefrom' => time() + DAYSECS));
1514  
1515          // Create a calendar event.
1516          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1517  
1518          // Now log out.
1519          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
1520          $this->setUser();
1521  
1522          // Create an action factory.
1523          $factory = new \core_calendar\action_factory();
1524  
1525          // Decorate action event for the student.
1526          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory, $student->id);
1527  
1528          // Confirm the event was decorated.
1529          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
1530          $this->assertEquals(get_string('add', 'data'), $actionevent->get_name());
1531          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
1532          $this->assertEquals(1, $actionevent->get_item_count());
1533          $this->assertFalse($actionevent->is_actionable());
1534      }
1535  
1536      public function test_data_core_calendar_provide_event_action_no_time_specified() {
1537          $this->resetAfterTest();
1538  
1539          $this->setAdminUser();
1540  
1541          // Create a course.
1542          $course = $this->getDataGenerator()->create_course();
1543  
1544          // Create a database activity.
1545          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id));
1546  
1547          // Create a calendar event.
1548          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1549  
1550          // Create an action factory.
1551          $factory = new \core_calendar\action_factory();
1552  
1553          // Decorate action event.
1554          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory);
1555  
1556          // Confirm the event was decorated.
1557          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
1558          $this->assertEquals(get_string('add', 'data'), $actionevent->get_name());
1559          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
1560          $this->assertEquals(1, $actionevent->get_item_count());
1561          $this->assertTrue($actionevent->is_actionable());
1562      }
1563  
1564      public function test_data_core_calendar_provide_event_action_no_time_specified_for_user() {
1565          global $CFG;
1566  
1567          $this->resetAfterTest();
1568  
1569          $this->setAdminUser();
1570  
1571          // Create a course.
1572          $course = $this->getDataGenerator()->create_course();
1573  
1574          // Create a student.
1575          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1576  
1577          // Create a database activity.
1578          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id));
1579  
1580          // Create a calendar event.
1581          $event = $this->create_action_event($course->id, $data->id, DATA_EVENT_TYPE_OPEN);
1582  
1583          // Now log out.
1584          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
1585          $this->setUser();
1586  
1587          // Create an action factory.
1588          $factory = new \core_calendar\action_factory();
1589  
1590          // Decorate action event for the student.
1591          $actionevent = mod_data_core_calendar_provide_event_action($event, $factory, $student->id);
1592  
1593          // Confirm the event was decorated.
1594          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
1595          $this->assertEquals(get_string('add', 'data'), $actionevent->get_name());
1596          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
1597          $this->assertEquals(1, $actionevent->get_item_count());
1598          $this->assertTrue($actionevent->is_actionable());
1599      }
1600  
1601      /**
1602       * Creates an action event.
1603       *
1604       * @param int $courseid
1605       * @param int $instanceid The data id.
1606       * @param string $eventtype The event type. eg. DATA_EVENT_TYPE_OPEN.
1607       * @param int|null $timestart The start timestamp for the event
1608       * @return bool|calendar_event
1609       */
1610      private function create_action_event($courseid, $instanceid, $eventtype, $timestart = null) {
1611          $event = new \stdClass();
1612          $event->name = 'Calendar event';
1613          $event->modulename  = 'data';
1614          $event->courseid = $courseid;
1615          $event->instance = $instanceid;
1616          $event->type = CALENDAR_EVENT_TYPE_ACTION;
1617          $event->eventtype = $eventtype;
1618          if ($timestart) {
1619              $event->timestart = $timestart;
1620          } else {
1621              $event->timestart = time();
1622          }
1623  
1624          return \calendar_event::create($event);
1625      }
1626  
1627      /**
1628       * Test the callback responsible for returning the completion rule descriptions.
1629       * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
1630       * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
1631       */
1632      public function test_mod_data_completion_get_active_rule_descriptions() {
1633          $this->resetAfterTest();
1634          $this->setAdminUser();
1635  
1636          // Two activities, both with automatic completion. One has the 'completionentries' rule, one doesn't.
1637          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]);
1638          $data1 = $this->getDataGenerator()->create_module('data', [
1639              'course' => $course->id,
1640              'completion' => 2,
1641              'completionentries' => 3
1642          ]);
1643          $data2 = $this->getDataGenerator()->create_module('data', [
1644              'course' => $course->id,
1645              'completion' => 2,
1646              'completionentries' => 0
1647          ]);
1648          $cm1 = \cm_info::create(get_coursemodule_from_instance('data', $data1->id));
1649          $cm2 = \cm_info::create(get_coursemodule_from_instance('data', $data2->id));
1650  
1651          // Data for the stdClass input type.
1652          // This type of input would occur when checking the default completion rules for an activity type, where we don't have
1653          // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
1654          $moddefaults = new \stdClass();
1655          $moddefaults->customdata = ['customcompletionrules' => ['completionentries' => 3]];
1656          $moddefaults->completion = 2;
1657  
1658          $activeruledescriptions = [get_string('completionentriesdesc', 'data', 3)];
1659          $this->assertEquals(mod_data_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
1660          $this->assertEquals(mod_data_get_completion_active_rule_descriptions($cm2), []);
1661          $this->assertEquals(mod_data_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
1662          $this->assertEquals(mod_data_get_completion_active_rule_descriptions(new \stdClass()), []);
1663      }
1664  
1665      /**
1666       * An unknown event type should not change the data instance.
1667       */
1668      public function test_mod_data_core_calendar_event_timestart_updated_unknown_event() {
1669          global $CFG, $DB;
1670          require_once($CFG->dirroot . "/calendar/lib.php");
1671  
1672          $this->resetAfterTest(true);
1673          $this->setAdminUser();
1674          $generator = $this->getDataGenerator();
1675          $course = $generator->create_course();
1676          $datagenerator = $generator->get_plugin_generator('mod_data');
1677          $timeopen = time();
1678          $timeclose = $timeopen + DAYSECS;
1679          $data = $datagenerator->create_instance(['course' => $course->id]);
1680          $data->timeavailablefrom = $timeopen;
1681          $data->timeavailableto = $timeclose;
1682          $DB->update_record('data', $data);
1683  
1684          // Create a valid event.
1685          $event = new \calendar_event([
1686              'name' => 'Test event',
1687              'description' => '',
1688              'format' => 1,
1689              'courseid' => $course->id,
1690              'groupid' => 0,
1691              'userid' => 2,
1692              'modulename' => 'data',
1693              'instance' => $data->id,
1694              'eventtype' => DATA_EVENT_TYPE_OPEN . "SOMETHING ELSE",
1695              'timestart' => 1,
1696              'timeduration' => 86400,
1697              'visible' => 1
1698          ]);
1699  
1700          mod_data_core_calendar_event_timestart_updated($event, $data);
1701          $data = $DB->get_record('data', ['id' => $data->id]);
1702          $this->assertEquals($timeopen, $data->timeavailablefrom);
1703          $this->assertEquals($timeclose, $data->timeavailableto);
1704      }
1705  
1706      /**
1707       * A DATA_EVENT_TYPE_OPEN event should update the timeavailablefrom property of the data activity.
1708       */
1709      public function test_mod_data_core_calendar_event_timestart_updated_open_event() {
1710          global $CFG, $DB;
1711          require_once($CFG->dirroot . "/calendar/lib.php");
1712  
1713          $this->resetAfterTest(true);
1714          $this->setAdminUser();
1715          $generator = $this->getDataGenerator();
1716          $course = $generator->create_course();
1717          $datagenerator = $generator->get_plugin_generator('mod_data');
1718          $timeopen = time();
1719          $timeclose = $timeopen + DAYSECS;
1720          $timemodified = 1;
1721          $newtimeopen = $timeopen - DAYSECS;
1722          $data = $datagenerator->create_instance(['course' => $course->id]);
1723          $data->timeavailablefrom = $timeopen;
1724          $data->timeavailableto = $timeclose;
1725          $data->timemodified = $timemodified;
1726          $DB->update_record('data', $data);
1727  
1728          // Create a valid event.
1729          $event = new \calendar_event([
1730              'name' => 'Test event',
1731              'description' => '',
1732              'format' => 1,
1733              'courseid' => $course->id,
1734              'groupid' => 0,
1735              'userid' => 2,
1736              'modulename' => 'data',
1737              'instance' => $data->id,
1738              'eventtype' => DATA_EVENT_TYPE_OPEN,
1739              'timestart' => $newtimeopen,
1740              'timeduration' => 86400,
1741              'visible' => 1
1742          ]);
1743  
1744          // Trigger and capture the event when adding a contact.
1745          $sink = $this->redirectEvents();
1746          mod_data_core_calendar_event_timestart_updated($event, $data);
1747          $triggeredevents = $sink->get_events();
1748          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
1749              return is_a($e, 'core\event\course_module_updated');
1750          });
1751          $data = $DB->get_record('data', ['id' => $data->id]);
1752  
1753          // Ensure the timeavailablefrom property matches the event timestart.
1754          $this->assertEquals($newtimeopen, $data->timeavailablefrom);
1755          // Ensure the timeavailableto isn't changed.
1756          $this->assertEquals($timeclose, $data->timeavailableto);
1757          // Ensure the timemodified property has been changed.
1758          $this->assertNotEquals($timemodified, $data->timemodified);
1759          // Confirm that a module updated event is fired when the module is changed.
1760          $this->assertNotEmpty($moduleupdatedevents);
1761      }
1762  
1763      /**
1764       * A DATA_EVENT_TYPE_CLOSE event should update the timeavailableto property of the data activity.
1765       */
1766      public function test_mod_data_core_calendar_event_timestart_updated_close_event() {
1767          global $CFG, $DB;
1768          require_once($CFG->dirroot . "/calendar/lib.php");
1769  
1770          $this->resetAfterTest(true);
1771          $this->setAdminUser();
1772          $generator = $this->getDataGenerator();
1773          $course = $generator->create_course();
1774          $datagenerator = $generator->get_plugin_generator('mod_data');
1775          $timeopen = time();
1776          $timeclose = $timeopen + DAYSECS;
1777          $timemodified = 1;
1778          $newtimeclose = $timeclose + DAYSECS;
1779          $data = $datagenerator->create_instance(['course' => $course->id]);
1780          $data->timeavailablefrom = $timeopen;
1781          $data->timeavailableto = $timeclose;
1782          $data->timemodified = $timemodified;
1783          $DB->update_record('data', $data);
1784  
1785          // Create a valid event.
1786          $event = new \calendar_event([
1787              'name' => 'Test event',
1788              'description' => '',
1789              'format' => 1,
1790              'courseid' => $course->id,
1791              'groupid' => 0,
1792              'userid' => 2,
1793              'modulename' => 'data',
1794              'instance' => $data->id,
1795              'eventtype' => DATA_EVENT_TYPE_CLOSE,
1796              'timestart' => $newtimeclose,
1797              'timeduration' => 86400,
1798              'visible' => 1
1799          ]);
1800  
1801          // Trigger and capture the event when adding a contact.
1802          $sink = $this->redirectEvents();
1803          mod_data_core_calendar_event_timestart_updated($event, $data);
1804          $triggeredevents = $sink->get_events();
1805          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
1806              return is_a($e, 'core\event\course_module_updated');
1807          });
1808          $data = $DB->get_record('data', ['id' => $data->id]);
1809  
1810          // Ensure the timeavailableto property matches the event timestart.
1811          $this->assertEquals($newtimeclose, $data->timeavailableto);
1812          // Ensure the timeavailablefrom isn't changed.
1813          $this->assertEquals($timeopen, $data->timeavailablefrom);
1814          // Ensure the timemodified property has been changed.
1815          $this->assertNotEquals($timemodified, $data->timemodified);
1816          // Confirm that a module updated event is fired when the module is changed.
1817          $this->assertNotEmpty($moduleupdatedevents);
1818      }
1819  
1820      /**
1821       * An unknown event type should not have any limits.
1822       */
1823      public function test_mod_data_core_calendar_get_valid_event_timestart_range_unknown_event() {
1824          global $CFG;
1825          require_once($CFG->dirroot . "/calendar/lib.php");
1826  
1827          $this->resetAfterTest(true);
1828          $this->setAdminUser();
1829          $generator = $this->getDataGenerator();
1830          $course = $generator->create_course();
1831          $timeopen = time();
1832          $timeclose = $timeopen + DAYSECS;
1833          $data = new \stdClass();
1834          $data->timeavailablefrom = $timeopen;
1835          $data->timeavailableto = $timeclose;
1836  
1837          // Create a valid event.
1838          $event = new \calendar_event([
1839              'name' => 'Test event',
1840              'description' => '',
1841              'format' => 1,
1842              'courseid' => $course->id,
1843              'groupid' => 0,
1844              'userid' => 2,
1845              'modulename' => 'data',
1846              'instance' => 1,
1847              'eventtype' => DATA_EVENT_TYPE_OPEN . "SOMETHING ELSE",
1848              'timestart' => 1,
1849              'timeduration' => 86400,
1850              'visible' => 1
1851          ]);
1852  
1853          list ($min, $max) = mod_data_core_calendar_get_valid_event_timestart_range($event, $data);
1854          $this->assertNull($min);
1855          $this->assertNull($max);
1856      }
1857  
1858      /**
1859       * The open event should be limited by the data's timeclose property, if it's set.
1860       */
1861      public function test_mod_data_core_calendar_get_valid_event_timestart_range_open_event() {
1862          global $CFG;
1863          require_once($CFG->dirroot . "/calendar/lib.php");
1864  
1865          $this->resetAfterTest(true);
1866          $this->setAdminUser();
1867          $generator = $this->getDataGenerator();
1868          $course = $generator->create_course();
1869          $timeopen = time();
1870          $timeclose = $timeopen + DAYSECS;
1871          $data = new \stdClass();
1872          $data->timeavailablefrom = $timeopen;
1873          $data->timeavailableto = $timeclose;
1874  
1875          // Create a valid event.
1876          $event = new \calendar_event([
1877              'name' => 'Test event',
1878              'description' => '',
1879              'format' => 1,
1880              'courseid' => $course->id,
1881              'groupid' => 0,
1882              'userid' => 2,
1883              'modulename' => 'data',
1884              'instance' => 1,
1885              'eventtype' => DATA_EVENT_TYPE_OPEN,
1886              'timestart' => 1,
1887              'timeduration' => 86400,
1888              'visible' => 1
1889          ]);
1890  
1891          // The max limit should be bounded by the timeclose value.
1892          list ($min, $max) = mod_data_core_calendar_get_valid_event_timestart_range($event, $data);
1893          $this->assertNull($min);
1894          $this->assertEquals($timeclose, $max[0]);
1895  
1896          // No timeclose value should result in no upper limit.
1897          $data->timeavailableto = 0;
1898          list ($min, $max) = mod_data_core_calendar_get_valid_event_timestart_range($event, $data);
1899          $this->assertNull($min);
1900          $this->assertNull($max);
1901      }
1902  
1903      /**
1904       * The close event should be limited by the data's timeavailablefrom property, if it's set.
1905       */
1906      public function test_mod_data_core_calendar_get_valid_event_timestart_range_close_event() {
1907          global $CFG;
1908  
1909          require_once($CFG->dirroot . "/calendar/lib.php");
1910  
1911          $this->resetAfterTest(true);
1912          $this->setAdminUser();
1913          $generator = $this->getDataGenerator();
1914          $course = $generator->create_course();
1915          $timeopen = time();
1916          $timeclose = $timeopen + DAYSECS;
1917          $data = new \stdClass();
1918          $data->timeavailablefrom = $timeopen;
1919          $data->timeavailableto = $timeclose;
1920  
1921          // Create a valid event.
1922          $event = new \calendar_event([
1923              'name' => 'Test event',
1924              'description' => '',
1925              'format' => 1,
1926              'courseid' => $course->id,
1927              'groupid' => 0,
1928              'userid' => 2,
1929              'modulename' => 'data',
1930              'instance' => 1,
1931              'eventtype' => DATA_EVENT_TYPE_CLOSE,
1932              'timestart' => 1,
1933              'timeduration' => 86400,
1934              'visible' => 1
1935          ]);
1936  
1937          // The max limit should be bounded by the timeclose value.
1938          list ($min, $max) = mod_data_core_calendar_get_valid_event_timestart_range($event, $data);
1939          $this->assertEquals($timeopen, $min[0]);
1940          $this->assertNull($max);
1941  
1942          // No timeavailableto value should result in no upper limit.
1943          $data->timeavailablefrom = 0;
1944          list ($min, $max) = mod_data_core_calendar_get_valid_event_timestart_range($event, $data);
1945          $this->assertNull($min);
1946          $this->assertNull($max);
1947      }
1948  
1949      /**
1950       * A user who does not have capabilities to add events to the calendar should be able to create an database.
1951       */
1952      public function test_creation_with_no_calendar_capabilities() {
1953          $this->resetAfterTest();
1954          $course = self::getDataGenerator()->create_course();
1955          $context = \context_course::instance($course->id);
1956          $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
1957          $roleid = self::getDataGenerator()->create_role();
1958          self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
1959          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
1960          $generator = self::getDataGenerator()->get_plugin_generator('mod_data');
1961          // Create an instance as a user without the calendar capabilities.
1962          $this->setUser($user);
1963          $time = time();
1964          $params = array(
1965              'course' => $course->id,
1966              'timeavailablefrom' => $time + 200,
1967              'timeavailableto' => $time + 2000,
1968              'timeviewfrom' => $time + 400,
1969              'timeviewto' => $time + 2000,
1970          );
1971          $generator->create_instance($params);
1972      }
1973  
1974      /**
1975       * Test for data_generate_default_template(). This method covers different scenarios for checking when the returned value
1976       * is empty or not, but doesn't check if the content has the expected value when it's not empty.
1977       *
1978       * @covers ::data_generate_default_template
1979       */
1980      public function test_data_generate_default_template(): void {
1981          $this->resetAfterTest();
1982          $this->setAdminUser();
1983  
1984          $course = $this->getDataGenerator()->create_course();
1985          $activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
1986  
1987          // Check the result is empty when $data and/or $template are null.
1988          $nullactivity = null;
1989          $result = data_generate_default_template($nullactivity, 'listtemplate', 0, false, false);
1990          $this->assertEmpty($result);
1991          $result = data_generate_default_template($activity, null, 0, false, false);
1992          $this->assertEmpty($result);
1993          $result = data_generate_default_template($nullactivity, null, 0, false, false);
1994          $this->assertEmpty($result);
1995  
1996          // Check the result is empty when any of the templates that are empty are given.
1997          $emptytemplates = [
1998              'csstemplate',
1999              'jstemplate',
2000              'listtemplateheader',
2001              'listtemplatefooter',
2002              'rsstitletemplate',
2003          ];
2004          foreach ($emptytemplates as $emptytemplate) {
2005              $result = data_generate_default_template($activity, $emptytemplate, 0, false, false);
2006              $this->assertEmpty($result);
2007          }
2008  
2009          $templates = [
2010              'listtemplate',
2011              'singletemplate',
2012              'asearchtemplate',
2013          ];
2014          // Check the result is empty when the database has no fields.
2015          foreach ($templates as $template) {
2016              $result = data_generate_default_template($activity, $template, 0, false, false);
2017              $this->assertEmpty($result);
2018              $this->assertEmpty($activity->{$template});
2019          }
2020  
2021          // Add a field to the activity.
2022          $fieldrecord = new stdClass();
2023          $fieldrecord->name = 'field-1';
2024          $fieldrecord->type = 'text';
2025          $datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
2026          $datagenerator->create_field($fieldrecord, $activity);
2027  
2028          // Check the result is not empty when the database has no entries.
2029          foreach ($templates as $template) {
2030              $result = data_generate_default_template($activity, $template, 0, false, false);
2031              $this->assertNotEmpty($result);
2032              $this->assertEmpty($activity->{$template});
2033          }
2034  
2035          // Check the result is not empty when the database has no entries and the result is saved when $update = true.
2036          foreach ($templates as $template) {
2037              $result = data_generate_default_template($activity, $template, 0, false, true);
2038              $this->assertNotEmpty($result);
2039              $this->assertNotEmpty($activity->{$template});
2040          }
2041      }
2042  
2043      /**
2044       * Test for data_replace_field_in_templates().
2045       *
2046       * @covers ::data_replace_field_in_templates
2047       */
2048      public function test_data_replace_field_in_templates(): void {
2049          global $DB;
2050          $this->resetAfterTest();
2051          $this->setAdminUser();
2052  
2053          $course = $this->getDataGenerator()->create_course();
2054          $templatecontent = "Field [[myfield]], [[myfield#id]], [[myfield#name]], [[myfield#description]], ";
2055  
2056          $params = ['course' => $course];
2057          foreach (manager::TEMPLATES_LIST as $templatename => $templatefile) {
2058              $params[$templatename] = $templatecontent;
2059          }
2060          $activity = $this->getDataGenerator()->create_module(manager::MODULE, $params);
2061  
2062          $generator = $this->getDataGenerator()->get_plugin_generator(manager::PLUGINNAME);
2063          $fieldrecord = (object)['name' => 'myfield', 'type' => 'text', 'description' => 'This is a field'];
2064          $generator->create_field($fieldrecord, $activity);
2065  
2066          data_replace_field_in_templates($activity, 'myfield', 'newfieldname');
2067          $dbactivity = $DB->get_record(manager::MODULE, ['id' => $activity->id]);
2068  
2069          $newcontent = "Field [[newfieldname]], [[newfieldname#id]], [[newfieldname#name]], [[newfieldname#description]], ";
2070          // Field compatible templates.
2071          $this->assertEquals($newcontent, $dbactivity->listtemplate);
2072          $this->assertEquals($newcontent, $dbactivity->singletemplate);
2073          $this->assertEquals($newcontent, $dbactivity->asearchtemplate);
2074          $this->assertEquals($newcontent, $dbactivity->addtemplate);
2075          $this->assertEquals($newcontent, $dbactivity->rsstemplate);
2076          // Other templates.
2077          $this->assertEquals($templatecontent, $dbactivity->listtemplateheader);
2078          $this->assertEquals($templatecontent, $dbactivity->listtemplatefooter);
2079          $this->assertEquals($templatecontent, $dbactivity->csstemplate);
2080          $this->assertEquals($templatecontent, $dbactivity->jstemplate);
2081          $this->assertEquals($templatecontent, $dbactivity->rsstitletemplate);
2082      }
2083  
2084      /**
2085       * Test for data_append_new_field_to_templates().
2086       *
2087       * @covers ::data_append_new_field_to_templates
2088       * @dataProvider data_append_new_field_to_templates_provider
2089       * @param bool $hasfield if the field is present in the templates
2090       * @param bool $hasotherfields if the field is not present in the templates
2091       * @param bool $expected the expected return
2092       */
2093      public function test_data_append_new_field_to_templates(bool $hasfield, bool $hasotherfields, bool $expected) {
2094          global $DB;
2095          $this->resetAfterTest();
2096          $this->setAdminUser();
2097  
2098          $templatecontent = "Template content";
2099          if ($hasfield) {
2100              $templatecontent .= "Has [[myfield]].";
2101          }
2102          if ($hasotherfields) {
2103              $templatecontent .= "And also ##otherfields##.";
2104          }
2105  
2106          $course = $this->getDataGenerator()->create_course();
2107          $params = ['course' => $course];
2108          foreach (manager::TEMPLATES_LIST as $templatename => $templatefile) {
2109              $params[$templatename] = $templatecontent;
2110          }
2111          $activity = $this->getDataGenerator()->create_module(manager::MODULE, $params);
2112  
2113          $result = data_append_new_field_to_templates($activity, 'myfield');
2114          $this->assertEquals($expected, $result);
2115  
2116          // Check fields with auto add fields.
2117          $dbactivity = $DB->get_record(manager::MODULE, ['id' => $activity->id]);
2118          if ($hasfield || $hasotherfields) {
2119              $this->assertEquals($dbactivity->singletemplate, $templatecontent);
2120              $this->assertEquals($dbactivity->addtemplate, $templatecontent);
2121              $this->assertEquals($dbactivity->rsstemplate, $templatecontent);
2122          } else {
2123              $regexp = '|Template content.*\[\[myfield\]\]|';
2124              // We don't want line breaks for the validations.
2125              $this->assertMatchesRegularExpression($regexp, str_replace("\n", '', $dbactivity->singletemplate));
2126              $this->assertMatchesRegularExpression($regexp, str_replace("\n", '', $dbactivity->addtemplate));
2127              $this->assertMatchesRegularExpression($regexp, str_replace("\n", '', $dbactivity->rsstemplate));
2128          }
2129          // No auto add field templates.
2130          $this->assertEquals($dbactivity->asearchtemplate, $templatecontent);
2131          $this->assertEquals($dbactivity->listtemplate, $templatecontent);
2132          $this->assertEquals($dbactivity->listtemplateheader, $templatecontent);
2133          $this->assertEquals($dbactivity->listtemplatefooter, $templatecontent);
2134          $this->assertEquals($dbactivity->csstemplate, $templatecontent);
2135          $this->assertEquals($dbactivity->jstemplate, $templatecontent);
2136          $this->assertEquals($dbactivity->rsstitletemplate, $templatecontent);
2137      }
2138  
2139      /**
2140       * Data provider for test_data_append_new_field_to_templates().
2141       *
2142       * @return array of scenarios
2143       */
2144      public function data_append_new_field_to_templates_provider(): array {
2145          return [
2146              'Plain template' => [
2147                  'hasfield' => false,
2148                  'hasotherfields' => false,
2149                  'expected' => true,
2150              ],
2151              'Field already present' => [
2152                  'hasfield' => true,
2153                  'hasotherfields' => false,
2154                  'expected' => false,
2155              ],
2156              '##otherfields## tag present' => [
2157                  'hasfield' => false,
2158                  'hasotherfields' => true,
2159                  'expected' => false,
2160              ],
2161              'Field already present and ##otherfields## tag present' => [
2162                  'hasfield' => true,
2163                  'hasotherfields' => true,
2164                  'expected' => false,
2165              ],
2166          ];
2167      }
2168  }