Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

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