Search moodle.org's
Developer Documentation

See Release Notes

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

Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [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  namespace mod_data;
  18  
  19  use externallib_advanced_testcase;
  20  use mod_data_external;
  21  
  22  defined('MOODLE_INTERNAL') || die();
  23  
  24  global $CFG;
  25  
  26  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  27  
  28  /**
  29   * Database module external functions tests
  30   *
  31   * @package    mod_data
  32   * @category   external
  33   * @copyright  2015 Juan Leyva <juan@moodle.com>
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   * @since      Moodle 2.9
  36   */
  37  class externallib_test extends externallib_advanced_testcase {
  38  
  39      /** @var stdClass Test module context. */
  40      protected $context;
  41  
  42      /** @var stdClass Test course.*/
  43      protected $course;
  44  
  45      /** @var stdClass Test course module. */
  46      protected $cm;
  47  
  48      /** @var  stdClass Test database activity. */
  49      protected $database;
  50  
  51      /** @var stdClass Test group 1. */
  52      protected $group1;
  53  
  54      /** @var stdClass Test group 2. */
  55      protected $group2;
  56  
  57      /** @var stdClass Test student 1. */
  58      protected $student1;
  59  
  60      /** @var stdClass Test student 2. */
  61      protected $student2;
  62  
  63      /** @var stdClass Test student 3. */
  64      protected $student3;
  65  
  66      /** @var stdClass Test student 4. */
  67      protected $student4;
  68  
  69      /** @var stdClass Student role. */
  70      protected $studentrole;
  71  
  72      /** @var stdClass Test teacher. */
  73      protected $teacher;
  74  
  75      /** @var stdClass Teacher role. */
  76      protected $teacherrole;
  77  
  78      /**
  79       * Set up for every test
  80       */
  81      public function setUp(): void {
  82          global $DB;
  83          $this->resetAfterTest();
  84          $this->setAdminUser();
  85  
  86          // Setup test data.
  87          $course = new \stdClass();
  88          $course->groupmode = SEPARATEGROUPS;
  89          $course->groupmodeforce = true;
  90          $this->course = $this->getDataGenerator()->create_course($course);
  91          $this->database = $this->getDataGenerator()->create_module('data', array('course' => $this->course->id));
  92          $this->context = \context_module::instance($this->database->cmid);
  93          $this->cm = get_coursemodule_from_instance('data', $this->database->id);
  94  
  95          // Create users.
  96          $this->student1 = self::getDataGenerator()->create_user(['firstname' => 'Olivia', 'lastname' => 'Smith']);
  97          $this->student2 = self::getDataGenerator()->create_user(['firstname' => 'Ezra', 'lastname' => 'Johnson']);
  98          $this->student3 = self::getDataGenerator()->create_user(['firstname' => 'Amelia', 'lastname' => 'Williams']);
  99          $this->teacher = self::getDataGenerator()->create_user(['firstname' => 'Asher', 'lastname' => 'Jones']);
 100  
 101          // Users enrolments.
 102          $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
 103          $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
 104          $this->getDataGenerator()->enrol_user($this->student1->id, $this->course->id, $this->studentrole->id, 'manual');
 105          $this->getDataGenerator()->enrol_user($this->student2->id, $this->course->id, $this->studentrole->id, 'manual');
 106          $this->getDataGenerator()->enrol_user($this->student3->id, $this->course->id, $this->studentrole->id, 'manual');
 107          $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
 108  
 109          $this->group1 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
 110          $this->group2 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
 111          groups_add_member($this->group1, $this->student1);
 112          groups_add_member($this->group1, $this->student2);
 113          groups_add_member($this->group2, $this->student3);
 114      }
 115  
 116      /**
 117       * Test get databases by courses
 118       */
 119      public function test_mod_data_get_databases_by_courses() {
 120          global $DB, $CFG;
 121          require_once($CFG->libdir . '/externallib.php');
 122  
 123          $this->resetAfterTest(true);
 124  
 125          // Create users.
 126          $student = self::getDataGenerator()->create_user();
 127          $teacher = self::getDataGenerator()->create_user();
 128  
 129          // Set to the student user.
 130          self::setUser($student);
 131  
 132          // Create courses to add the modules.
 133          $course1 = self::getDataGenerator()->create_course();
 134          $course2 = self::getDataGenerator()->create_course();
 135  
 136          // First database.
 137          $record = new \stdClass();
 138          $record->introformat = FORMAT_HTML;
 139          $record->course = $course1->id;
 140          // Set multilang text to check that is properly filtered to "en" only.
 141          $record->name = '<span lang="en" class="multilang">English</span><span lang="es" class="multilang">EspaƱol</span>';
 142          $record->intro = '<button>Test with HTML allowed.</button>';
 143          $database1 = self::getDataGenerator()->create_module('data', $record);
 144  
 145          // Second database.
 146          $record = new \stdClass();
 147          $record->introformat = FORMAT_HTML;
 148          $record->course = $course2->id;
 149          $database2 = self::getDataGenerator()->create_module('data', $record);
 150  
 151          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 152          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
 153  
 154          // Users enrolments.
 155          $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
 156          $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id, 'manual');
 157  
 158          // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
 159          $enrol = enrol_get_plugin('manual');
 160          $enrolinstances = enrol_get_instances($course2->id, true);
 161          foreach ($enrolinstances as $courseenrolinstance) {
 162              if ($courseenrolinstance->enrol == "manual") {
 163                  $instance2 = $courseenrolinstance;
 164                  break;
 165              }
 166          }
 167          $enrol->enrol_user($instance2, $student->id, $studentrole->id);
 168  
 169          // Enable multilang filter to on content and heading.
 170          \filter_manager::reset_caches();
 171          filter_set_global_state('multilang', TEXTFILTER_ON);
 172          filter_set_applies_to_strings('multilang', true);
 173          // Set WS filtering.
 174          $wssettings = \external_settings::get_instance();
 175          $wssettings->set_filter(true);
 176  
 177          // Create what we expect to be returned when querying the two courses.
 178          // First for the student user.
 179          $expectedfields = array('id', 'coursemodule', 'course', 'name', 'comments', 'timeavailablefrom',
 180                              'timeavailableto', 'timeviewfrom', 'timeviewto', 'requiredentries', 'requiredentriestoview',
 181                              'intro', 'introformat', 'introfiles', 'maxentries', 'rssarticles', 'singletemplate', 'listtemplate',
 182                              'listtemplateheader', 'listtemplatefooter', 'addtemplate', 'rsstemplate', 'rsstitletemplate',
 183                              'csstemplate', 'jstemplate', 'asearchtemplate', 'approval', 'defaultsort', 'defaultsortdir', 'manageapproved');
 184  
 185          // Add expected coursemodule.
 186          $database1->coursemodule = $database1->cmid;
 187          $database1->introfiles = [];
 188          $database2->coursemodule = $database2->cmid;
 189          $database2->introfiles = [];
 190  
 191          $expected1 = array();
 192          $expected2 = array();
 193          foreach ($expectedfields as $field) {
 194              if ($field == 'approval' or $field == 'manageapproved') {
 195                  $database1->{$field} = (bool) $database1->{$field};
 196                  $database2->{$field} = (bool) $database2->{$field};
 197              }
 198              $expected1[$field] = $database1->{$field};
 199              $expected2[$field] = $database2->{$field};
 200          }
 201          $expected1['name'] = 'English'; // Lang filtered expected.
 202          $expected1['comments'] = (bool) $expected1['comments'];
 203          $expected2['comments'] = (bool) $expected2['comments'];
 204  
 205          $expecteddatabases = array();
 206          $expecteddatabases[] = $expected2;
 207          $expecteddatabases[] = $expected1;
 208  
 209          // Call the external function passing course ids.
 210          $result = mod_data_external::get_databases_by_courses(array($course2->id, $course1->id));
 211          $result = \external_api::clean_returnvalue(mod_data_external::get_databases_by_courses_returns(), $result);
 212          $this->assertEquals($expecteddatabases, $result['databases']);
 213  
 214          // Call the external function without passing course id.
 215          $result = mod_data_external::get_databases_by_courses();
 216          $result = \external_api::clean_returnvalue(mod_data_external::get_databases_by_courses_returns(), $result);
 217          $this->assertEquals($expecteddatabases, $result['databases']);
 218  
 219          // Unenrol user from second course and alter expected databases.
 220          $enrol->unenrol_user($instance2, $student->id);
 221          array_shift($expecteddatabases);
 222  
 223          // Call the external function without passing course id.
 224          $result = mod_data_external::get_databases_by_courses();
 225          $result = \external_api::clean_returnvalue(mod_data_external::get_databases_by_courses_returns(), $result);
 226          $this->assertEquals($expecteddatabases, $result['databases']);
 227  
 228          // Call for the second course we unenrolled the user from, expected warning.
 229          $result = mod_data_external::get_databases_by_courses(array($course2->id));
 230          $this->assertCount(1, $result['warnings']);
 231          $this->assertEquals('1', $result['warnings'][0]['warningcode']);
 232          $this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
 233  
 234          // Now, try as a teacher for getting all the additional fields.
 235          self::setUser($teacher);
 236  
 237          $additionalfields = array('scale', 'assessed', 'assesstimestart', 'assesstimefinish', 'editany', 'notification', 'timemodified');
 238  
 239          foreach ($additionalfields as $field) {
 240              if ($field == 'editany') {
 241                  $database1->{$field} = (bool) $database1->{$field};
 242              }
 243              $expecteddatabases[0][$field] = $database1->{$field};
 244          }
 245          $result = mod_data_external::get_databases_by_courses();
 246          $result = \external_api::clean_returnvalue(mod_data_external::get_databases_by_courses_returns(), $result);
 247          $this->assertEquals($expecteddatabases, $result['databases']);
 248  
 249          // Admin should get all the information.
 250          self::setAdminUser();
 251  
 252          $result = mod_data_external::get_databases_by_courses(array($course1->id));
 253          $result = \external_api::clean_returnvalue(mod_data_external::get_databases_by_courses_returns(), $result);
 254          $this->assertEquals($expecteddatabases, $result['databases']);
 255      }
 256  
 257      /**
 258       * Test view_database invalid id.
 259       */
 260      public function test_view_database_invalid_id() {
 261  
 262          // Test invalid instance id.
 263          $this->expectException('moodle_exception');
 264          mod_data_external::view_database(0);
 265      }
 266  
 267      /**
 268       * Test view_database not enrolled user.
 269       */
 270      public function test_view_database_not_enrolled_user() {
 271  
 272          $usernotenrolled = self::getDataGenerator()->create_user();
 273          $this->setUser($usernotenrolled);
 274  
 275          $this->expectException('moodle_exception');
 276          mod_data_external::view_database(0);
 277      }
 278  
 279      /**
 280       * Test view_database no capabilities.
 281       */
 282      public function test_view_database_no_capabilities() {
 283          // Test user with no capabilities.
 284          // We need a explicit prohibit since this capability is allowed for students by default.
 285          assign_capability('mod/data:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id);
 286          accesslib_clear_all_caches_for_unit_testing();
 287  
 288          $this->expectException('moodle_exception');
 289          mod_data_external::view_database(0);
 290      }
 291  
 292      /**
 293       * Test view_database.
 294       */
 295      public function test_view_database() {
 296  
 297          // Test user with full capabilities.
 298          $this->setUser($this->student1);
 299  
 300          // Trigger and capture the event.
 301          $sink = $this->redirectEvents();
 302  
 303          $result = mod_data_external::view_database($this->database->id);
 304          $result = \external_api::clean_returnvalue(mod_data_external::view_database_returns(), $result);
 305  
 306          $events = $sink->get_events();
 307          $this->assertCount(1, $events);
 308          $event = array_shift($events);
 309  
 310          // Checking that the event contains the expected values.
 311          $this->assertInstanceOf('\mod_data\event\course_module_viewed', $event);
 312          $this->assertEquals($this->context, $event->get_context());
 313          $moodledata = new \moodle_url('/mod/data/view.php', array('id' => $this->cm->id));
 314          $this->assertEquals($moodledata, $event->get_url());
 315          $this->assertEventContextNotUsed($event);
 316          $this->assertNotEmpty($event->get_name());
 317      }
 318  
 319      /**
 320       * Test get_data_access_information for student.
 321       */
 322      public function test_get_data_access_information_student() {
 323          global $DB;
 324          // Modify the database to add access restrictions.
 325          $this->database->timeavailablefrom = time() + DAYSECS;
 326          $this->database->requiredentries = 2;
 327          $this->database->requiredentriestoview = 2;
 328          $DB->update_record('data', $this->database);
 329  
 330          // Test user with full capabilities.
 331          $this->setUser($this->student1);
 332  
 333          $result = mod_data_external::get_data_access_information($this->database->id);
 334          $result = \external_api::clean_returnvalue(mod_data_external::get_data_access_information_returns(), $result);
 335  
 336          $this->assertEquals($this->group1->id, $result['groupid']);
 337  
 338          $this->assertFalse($result['canmanageentries']);
 339          $this->assertFalse($result['canapprove']);
 340          $this->assertTrue($result['canaddentry']);  // It return true because it doen't check time restrictions.
 341          $this->assertFalse($result['timeavailable']);
 342          $this->assertFalse($result['inreadonlyperiod']);
 343          $this->assertEquals(0, $result['numentries']);
 344          $this->assertEquals($this->database->requiredentries, $result['entrieslefttoadd']);
 345          $this->assertEquals($this->database->requiredentriestoview, $result['entrieslefttoview']);
 346      }
 347  
 348      /**
 349       * Test get_data_access_information for teacher.
 350       */
 351      public function test_get_data_access_information_teacher() {
 352          global $DB;
 353          // Modify the database to add access restrictions.
 354          $this->database->timeavailablefrom = time() + DAYSECS;
 355          $this->database->requiredentries = 2;
 356          $this->database->requiredentriestoview = 2;
 357          $DB->update_record('data', $this->database);
 358  
 359          // Test user with full capabilities.
 360          $this->setUser($this->teacher);
 361  
 362          $result = mod_data_external::get_data_access_information($this->database->id);
 363          $result = \external_api::clean_returnvalue(mod_data_external::get_data_access_information_returns(), $result);
 364  
 365          $this->assertEquals(0, $result['groupid']);
 366  
 367          $this->assertTrue($result['canmanageentries']);
 368          $this->assertTrue($result['canapprove']);
 369          $this->assertTrue($result['canaddentry']);  // It return true because it doen't check time restrictions.
 370          $this->assertTrue($result['timeavailable']);
 371          $this->assertFalse($result['inreadonlyperiod']);
 372          $this->assertEquals(0, $result['numentries']);
 373          $this->assertEquals(0, $result['entrieslefttoadd']);
 374          $this->assertEquals(0, $result['entrieslefttoview']);
 375      }
 376  
 377      /**
 378       * Test get_data_access_information with groups.
 379       */
 380      public function test_get_data_access_information_groups() {
 381          global $DB;
 382  
 383          $DB->set_field('course', 'groupmode', VISIBLEGROUPS, ['id' => $this->course->id]);
 384  
 385          // Check I can see my group.
 386          $this->setUser($this->student1);
 387  
 388          $result = mod_data_external::get_data_access_information($this->database->id);
 389          $result = \external_api::clean_returnvalue(mod_data_external::get_data_access_information_returns(), $result);
 390  
 391          $this->assertEquals($this->group1->id, $result['groupid']); // My group is correctly found.
 392          $this->assertFalse($result['canmanageentries']);
 393          $this->assertFalse($result['canapprove']);
 394          $this->assertTrue($result['canaddentry']);  // I can entries in my groups.
 395          $this->assertTrue($result['timeavailable']);
 396          $this->assertFalse($result['inreadonlyperiod']);
 397          $this->assertEquals(0, $result['numentries']);
 398          $this->assertEquals(0, $result['entrieslefttoadd']);
 399          $this->assertEquals(0, $result['entrieslefttoview']);
 400  
 401          // Check the other course group in visible groups mode.
 402          $result = mod_data_external::get_data_access_information($this->database->id, $this->group2->id);
 403          $result = \external_api::clean_returnvalue(mod_data_external::get_data_access_information_returns(), $result);
 404  
 405          $this->assertEquals($this->group2->id, $result['groupid']); // The group is correctly found.
 406          $this->assertFalse($result['canmanageentries']);
 407          $this->assertFalse($result['canapprove']);
 408          $this->assertFalse($result['canaddentry']);  // I cannot add entries in other groups.
 409          $this->assertTrue($result['timeavailable']);
 410          $this->assertFalse($result['inreadonlyperiod']);
 411          $this->assertEquals(0, $result['numentries']);
 412          $this->assertEquals(0, $result['entrieslefttoadd']);
 413          $this->assertEquals(0, $result['entrieslefttoview']);
 414      }
 415  
 416      /**
 417       * Helper method to populate the database with some entries.
 418       *
 419       * @return array the entry ids created
 420       */
 421      public function populate_database_with_entries() {
 422          global $DB;
 423  
 424          // Force approval.
 425          $DB->set_field('data', 'approval', 1, array('id' => $this->database->id));
 426          $generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
 427          $fieldtypes = array('checkbox', 'date', 'menu', 'multimenu', 'number', 'radiobutton', 'text', 'textarea', 'url');
 428  
 429          $count = 1;
 430          // Creating test Fields with default parameter values.
 431          foreach ($fieldtypes as $fieldtype) {
 432              $fieldname = 'field-' . $count;
 433              $record = new \stdClass();
 434              $record->name = $fieldname;
 435              $record->type = $fieldtype;
 436              $record->required = 1;
 437  
 438              $generator->create_field($record, $this->database);
 439              $count++;
 440          }
 441          // Get all the fields created.
 442          $fields = $DB->get_records('data_fields', array('dataid' => $this->database->id), 'id');
 443  
 444          // Populate with contents, creating a new entry.
 445          $contents = array();
 446          $contents[] = array('opt1', 'opt2', 'opt3', 'opt4');
 447          $contents[] = '01-01-2037'; // It should be lower than 2038, to avoid failing on 32-bit windows.
 448          $contents[] = 'menu1';
 449          $contents[] = array('multimenu1', 'multimenu2', 'multimenu3', 'multimenu4');
 450          $contents[] = '12345';
 451          $contents[] = 'radioopt1';
 452          $contents[] = 'text for testing';
 453          $contents[] = '<p>text area testing<br /></p>';
 454          $contents[] = array('example.url', 'sampleurl');
 455          $count = 0;
 456          $fieldcontents = array();
 457          foreach ($fields as $fieldrecord) {
 458              $fieldcontents[$fieldrecord->id] = $contents[$count++];
 459          }
 460  
 461          $this->setUser($this->student1);
 462          $entry11 = $generator->create_entry($this->database, $fieldcontents, $this->group1->id, ['Cats', 'Dogs']);
 463          $this->setUser($this->student2);
 464          $entry12 = $generator->create_entry($this->database, $fieldcontents, $this->group1->id, ['Cats']);
 465          $entry13 = $generator->create_entry($this->database, $fieldcontents, $this->group1->id);
 466          // Entry not in group.
 467          $entry14 = $generator->create_entry($this->database, $fieldcontents, 0);
 468  
 469          $this->setUser($this->student3);
 470          $entry21 = $generator->create_entry($this->database, $fieldcontents, $this->group2->id);
 471  
 472          // Approve all except $entry13.
 473          $DB->set_field('data_records', 'approved', 1, ['id' => $entry11]);
 474          $DB->set_field('data_records', 'approved', 1, ['id' => $entry12]);
 475          $DB->set_field('data_records', 'approved', 1, ['id' => $entry14]);
 476          $DB->set_field('data_records', 'approved', 1, ['id' => $entry21]);
 477  
 478          return [$entry11, $entry12, $entry13, $entry14, $entry21];
 479      }
 480  
 481      /**
 482       * Test get_entries
 483       */
 484      public function test_get_entries() {
 485          global $DB;
 486          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 487  
 488          // First of all, expect to see only my group entries (not other users in other groups ones).
 489          // We may expect entries without group also.
 490          $this->setUser($this->student1);
 491          $result = mod_data_external::get_entries($this->database->id);
 492          $result = \external_api::clean_returnvalue(mod_data_external::get_entries_returns(), $result);
 493          $this->assertCount(0, $result['warnings']);
 494          $this->assertCount(3, $result['entries']);
 495          $this->assertEquals(3, $result['totalcount']);
 496          $this->assertEquals($entry11, $result['entries'][0]['id']);
 497          $this->assertCount(2, $result['entries'][0]['tags']);
 498          $this->assertEquals($this->student1->id, $result['entries'][0]['userid']);
 499          $this->assertEquals($this->group1->id, $result['entries'][0]['groupid']);
 500          $this->assertEquals($this->database->id, $result['entries'][0]['dataid']);
 501          $this->assertEquals($entry12, $result['entries'][1]['id']);
 502          $this->assertCount(1, $result['entries'][1]['tags']);
 503          $this->assertEquals('Cats', $result['entries'][1]['tags'][0]['rawname']);
 504          $this->assertEquals($this->student2->id, $result['entries'][1]['userid']);
 505          $this->assertEquals($this->group1->id, $result['entries'][1]['groupid']);
 506          $this->assertEquals($this->database->id, $result['entries'][1]['dataid']);
 507          $this->assertEquals($entry14, $result['entries'][2]['id']);
 508          $this->assertEquals($this->student2->id, $result['entries'][2]['userid']);
 509          $this->assertEquals(0, $result['entries'][2]['groupid']);
 510          $this->assertEquals($this->database->id, $result['entries'][2]['dataid']);
 511          // Other user in same group.
 512          $this->setUser($this->student2);
 513          $result = mod_data_external::get_entries($this->database->id);
 514          $result = \external_api::clean_returnvalue(mod_data_external::get_entries_returns(), $result);
 515          $this->assertCount(0, $result['warnings']);
 516          $this->assertCount(4, $result['entries']);  // I can see my entry not approved yet.
 517          $this->assertEquals(4, $result['totalcount']);
 518  
 519          // Now try with the user in the second group that must see only two entries (his group entry and the one without group).
 520          $this->setUser($this->student3);
 521          $result = mod_data_external::get_entries($this->database->id);
 522          $result = \external_api::clean_returnvalue(mod_data_external::get_entries_returns(), $result);
 523          $this->assertCount(0, $result['warnings']);
 524          $this->assertCount(2, $result['entries']);
 525          $this->assertEquals(2, $result['totalcount']);
 526          $this->assertEquals($entry14, $result['entries'][0]['id']);
 527          $this->assertEquals($this->student2->id, $result['entries'][0]['userid']);
 528          $this->assertEquals(0, $result['entries'][0]['groupid']);
 529          $this->assertEquals($this->database->id, $result['entries'][0]['dataid']);
 530          $this->assertEquals($entry21, $result['entries'][1]['id']);
 531          $this->assertEquals($this->student3->id, $result['entries'][1]['userid']);
 532          $this->assertEquals($this->group2->id, $result['entries'][1]['groupid']);
 533          $this->assertEquals($this->database->id, $result['entries'][1]['dataid']);
 534  
 535          // Now, as teacher we should see all (we have permissions to view all groups).
 536          $this->setUser($this->teacher);
 537          $result = mod_data_external::get_entries($this->database->id);
 538          $result = \external_api::clean_returnvalue(mod_data_external::get_entries_returns(), $result);
 539          $this->assertCount(0, $result['warnings']);
 540          $this->assertCount(5, $result['entries']);  // I can see the not approved one.
 541          $this->assertEquals(5, $result['totalcount']);
 542  
 543          $entries = $DB->get_records('data_records', array('dataid' => $this->database->id), 'id');
 544          $this->assertCount(5, $entries);
 545          $count = 0;
 546          foreach ($entries as $entry) {
 547              $this->assertEquals($entry->id, $result['entries'][$count]['id']);
 548              $count++;
 549          }
 550  
 551          // Basic test passing the parameter (instead having to calculate it).
 552          $this->setUser($this->student1);
 553          $result = mod_data_external::get_entries($this->database->id, $this->group1->id);
 554          $result = \external_api::clean_returnvalue(mod_data_external::get_entries_returns(), $result);
 555          $this->assertCount(0, $result['warnings']);
 556          $this->assertCount(3, $result['entries']);
 557          $this->assertEquals(3, $result['totalcount']);
 558  
 559          // Test ordering (reverse).
 560          $this->setUser($this->student1);
 561          $result = mod_data_external::get_entries($this->database->id, $this->group1->id, false, null, 'DESC');
 562          $result = \external_api::clean_returnvalue(mod_data_external::get_entries_returns(), $result);
 563          $this->assertCount(0, $result['warnings']);
 564          $this->assertCount(3, $result['entries']);
 565          $this->assertEquals(3, $result['totalcount']);
 566          $this->assertEquals($entry14, $result['entries'][0]['id']);
 567  
 568          // Test pagination.
 569          $this->setUser($this->student1);
 570          $result = mod_data_external::get_entries($this->database->id, $this->group1->id, false, null, null, 0, 1);
 571          $result = \external_api::clean_returnvalue(mod_data_external::get_entries_returns(), $result);
 572          $this->assertCount(0, $result['warnings']);
 573          $this->assertCount(1, $result['entries']);
 574          $this->assertEquals(3, $result['totalcount']);
 575          $this->assertEquals($entry11, $result['entries'][0]['id']);
 576  
 577          $result = mod_data_external::get_entries($this->database->id, $this->group1->id, false, null, null, 1, 1);
 578          $result = \external_api::clean_returnvalue(mod_data_external::get_entries_returns(), $result);
 579          $this->assertCount(0, $result['warnings']);
 580          $this->assertCount(1, $result['entries']);
 581          $this->assertEquals(3, $result['totalcount']);
 582          $this->assertEquals($entry12, $result['entries'][0]['id']);
 583  
 584          // Now test the return contents.
 585          data_generate_default_template($this->database, 'listtemplate', 0, false, true); // Generate a default list template.
 586          $result = mod_data_external::get_entries($this->database->id, $this->group1->id, true, null, null, 0, 2);
 587          $result = \external_api::clean_returnvalue(mod_data_external::get_entries_returns(), $result);
 588          $this->assertCount(0, $result['warnings']);
 589          $this->assertCount(2, $result['entries']);
 590          $this->assertEquals(3, $result['totalcount']);
 591          $this->assertCount(9, $result['entries'][0]['contents']);
 592          $this->assertCount(9, $result['entries'][1]['contents']);
 593          // Search for some content.
 594          $this->assertTrue(strpos($result['listviewcontents'], 'opt1') !== false);
 595          $this->assertTrue(strpos($result['listviewcontents'], 'January') !== false);
 596          $this->assertTrue(strpos($result['listviewcontents'], 'menu1') !== false);
 597          $this->assertTrue(strpos($result['listviewcontents'], 'text for testing') !== false);
 598          $this->assertTrue(strpos($result['listviewcontents'], 'sampleurl') !== false);
 599      }
 600  
 601      /**
 602       * Test get_entry_visible_groups.
 603       */
 604      public function test_get_entry_visible_groups() {
 605          global $DB;
 606  
 607          $DB->set_field('course', 'groupmode', VISIBLEGROUPS, ['id' => $this->course->id]);
 608          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 609  
 610          // Check I can see my approved group entries.
 611          $this->setUser($this->student1);
 612          $result = mod_data_external::get_entry($entry11);
 613          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 614          $this->assertCount(0, $result['warnings']);
 615          $this->assertEquals($entry11, $result['entry']['id']);
 616          $this->assertTrue($result['entry']['approved']);
 617          $this->assertTrue($result['entry']['canmanageentry']); // Is mine, I can manage it.
 618  
 619          // Entry from other group.
 620          $result = mod_data_external::get_entry($entry21);
 621          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 622          $this->assertCount(0, $result['warnings']);
 623          $this->assertEquals($entry21, $result['entry']['id']);
 624      }
 625  
 626      /**
 627       * Test get_entry_separated_groups.
 628       */
 629      public function test_get_entry_separated_groups() {
 630          global $DB;
 631          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 632  
 633          // Check I can see my approved group entries.
 634          $this->setUser($this->student1);
 635          $result = mod_data_external::get_entry($entry11);
 636          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 637          $this->assertCount(0, $result['warnings']);
 638          $this->assertEquals($entry11, $result['entry']['id']);
 639          $this->assertTrue($result['entry']['approved']);
 640          $this->assertTrue($result['entry']['canmanageentry']); // Is mine, I can manage it.
 641  
 642          // Retrieve contents.
 643          data_generate_default_template($this->database, 'singletemplate', 0, false, true);
 644          $result = mod_data_external::get_entry($entry11, true);
 645          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 646          $this->assertCount(0, $result['warnings']);
 647          $this->assertCount(9, $result['entry']['contents']);
 648          $this->assertTrue(strpos($result['entryviewcontents'], 'opt1') !== false);
 649          $this->assertTrue(strpos($result['entryviewcontents'], 'January') !== false);
 650          $this->assertTrue(strpos($result['entryviewcontents'], 'menu1') !== false);
 651          $this->assertTrue(strpos($result['entryviewcontents'], 'text for testing') !== false);
 652          $this->assertTrue(strpos($result['entryviewcontents'], 'sampleurl') !== false);
 653  
 654          // This is in my group but I'm not the author.
 655          $result = mod_data_external::get_entry($entry12);
 656          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 657          $this->assertCount(0, $result['warnings']);
 658          $this->assertEquals($entry12, $result['entry']['id']);
 659          $this->assertTrue($result['entry']['approved']);
 660          $this->assertFalse($result['entry']['canmanageentry']); // Not mine.
 661  
 662          $this->setUser($this->student3);
 663          $result = mod_data_external::get_entry($entry21);
 664          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 665          $this->assertCount(0, $result['warnings']);
 666          $this->assertEquals($entry21, $result['entry']['id']);
 667          $this->assertTrue($result['entry']['approved']);
 668          $this->assertTrue($result['entry']['canmanageentry']); // Is mine, I can manage it.
 669  
 670          // As teacher I should be able to see all the entries.
 671          $this->setUser($this->teacher);
 672          $result = mod_data_external::get_entry($entry11);
 673          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 674          $this->assertEquals($entry11, $result['entry']['id']);
 675  
 676          $result = mod_data_external::get_entry($entry12);
 677          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 678          $this->assertEquals($entry12, $result['entry']['id']);
 679          // This is the not approved one.
 680          $result = mod_data_external::get_entry($entry13);
 681          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 682          $this->assertEquals($entry13, $result['entry']['id']);
 683  
 684          $result = mod_data_external::get_entry($entry21);
 685          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
 686          $this->assertEquals($entry21, $result['entry']['id']);
 687  
 688          // Now, try to get an entry not approved yet.
 689          $this->setUser($this->student1);
 690          $this->expectException('moodle_exception');
 691          $result = mod_data_external::get_entry($entry13);
 692      }
 693  
 694      /**
 695       * Test get_entry from other group in separated groups.
 696       */
 697      public function test_get_entry_other_group_separated_groups() {
 698          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 699  
 700          // We should not be able to view other gropu entries (in separated groups).
 701          $this->setUser($this->student1);
 702          $this->expectException('moodle_exception');
 703          $result = mod_data_external::get_entry($entry21);
 704      }
 705  
 706      /**
 707       * Test get_fields.
 708       */
 709      public function test_get_fields() {
 710          global $DB;
 711          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 712  
 713          $this->setUser($this->student1);
 714          $result = mod_data_external::get_fields($this->database->id);
 715          $result = \external_api::clean_returnvalue(mod_data_external::get_fields_returns(), $result);
 716  
 717          // Basically compare we retrieve all the fields and the correct values.
 718          $fields = $DB->get_records('data_fields', array('dataid' => $this->database->id), 'id');
 719          foreach ($result['fields'] as $field) {
 720              $this->assertEquals($field, (array) $fields[$field['id']]);
 721          }
 722      }
 723  
 724      /**
 725       * Test get_fields_database_without_fields.
 726       */
 727      public function test_get_fields_database_without_fields() {
 728  
 729          $this->setUser($this->student1);
 730          $result = mod_data_external::get_fields($this->database->id);
 731          $result = \external_api::clean_returnvalue(mod_data_external::get_fields_returns(), $result);
 732  
 733          $this->assertEmpty($result['fields']);
 734      }
 735  
 736      /**
 737       * Test search_entries.
 738       */
 739      public function test_search_entries() {
 740          global $DB;
 741          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 742  
 743          $this->setUser($this->student1);
 744          // Empty search, it should return all the visible entries.
 745          $result = mod_data_external::search_entries($this->database->id, 0, false);
 746          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 747          $this->assertCount(3, $result['entries']);
 748          $this->assertEquals(3, $result['totalcount']);
 749  
 750          // Search for something that does not exists.
 751          $result = mod_data_external::search_entries($this->database->id, 0, false, 'abc');
 752          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 753          $this->assertCount(0, $result['entries']);
 754          $this->assertEquals(0, $result['totalcount']);
 755  
 756          // Search by text matching all the entries.
 757          $result = mod_data_external::search_entries($this->database->id, 0, false, 'text');
 758          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 759          $this->assertCount(3, $result['entries']);
 760          $this->assertEquals(3, $result['totalcount']);
 761          $this->assertEquals(3, $result['maxcount']);
 762  
 763          // Now as the other student I should receive my not approved entry. Apply ordering here.
 764          $this->setUser($this->student2);
 765          $result = mod_data_external::search_entries($this->database->id, 0, false, 'text', [], DATA_APPROVED, 'ASC');
 766          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 767          $this->assertCount(4, $result['entries']);
 768          $this->assertEquals(4, $result['totalcount']);
 769          $this->assertEquals(4, $result['maxcount']);
 770          // The not approved one should be the first.
 771          $this->assertEquals($entry13, $result['entries'][0]['id']);
 772  
 773          // Now as the other group student.
 774          $this->setUser($this->student3);
 775          $result = mod_data_external::search_entries($this->database->id, 0, false, 'text');
 776          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 777          $this->assertCount(2, $result['entries']);
 778          $this->assertEquals(2, $result['totalcount']);
 779          $this->assertEquals(2, $result['maxcount']);
 780          $this->assertEquals($this->student2->id, $result['entries'][0]['userid']);
 781          $this->assertEquals($this->student3->id, $result['entries'][1]['userid']);
 782  
 783          // Same normal text search as teacher.
 784          $this->setUser($this->teacher);
 785          $result = mod_data_external::search_entries($this->database->id, 0, false, 'text');
 786          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 787          $this->assertCount(5, $result['entries']);  // I can see all groups and non approved.
 788          $this->assertEquals(5, $result['totalcount']);
 789          $this->assertEquals(5, $result['maxcount']);
 790  
 791          // Pagination.
 792          $this->setUser($this->teacher);
 793          $result = mod_data_external::search_entries($this->database->id, 0, false, 'text', [], DATA_TIMEADDED, 'ASC', 0, 2);
 794          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 795          $this->assertCount(2, $result['entries']);  // Only 2 per page.
 796          $this->assertEquals(5, $result['totalcount']);
 797          $this->assertEquals(5, $result['maxcount']);
 798  
 799          // Now advanced search or not dinamic fields (user firstname for example).
 800          $this->setUser($this->student1);
 801          $advsearch = [
 802              ['name' => 'fn', 'value' => json_encode($this->student2->firstname)]
 803          ];
 804          $result = mod_data_external::search_entries($this->database->id, 0, false, '', $advsearch);
 805          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 806          $this->assertCount(2, $result['entries']);
 807          $this->assertEquals(2, $result['totalcount']);
 808          $this->assertEquals(3, $result['maxcount']);
 809          $this->assertEquals($this->student2->id, $result['entries'][0]['userid']);  // I only found mine!
 810  
 811          // Advanced search for fields.
 812          $field = $DB->get_record('data_fields', array('type' => 'url'));
 813          $advsearch = [
 814              ['name' => 'f_' . $field->id , 'value' => 'sampleurl']
 815          ];
 816          $result = mod_data_external::search_entries($this->database->id, 0, false, '', $advsearch);
 817          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 818          $this->assertCount(3, $result['entries']);  // Found two entries matching this.
 819          $this->assertEquals(3, $result['totalcount']);
 820          $this->assertEquals(3, $result['maxcount']);
 821  
 822          // Combined search.
 823          $field2 = $DB->get_record('data_fields', array('type' => 'number'));
 824          $advsearch = [
 825              ['name' => 'f_' . $field->id , 'value' => 'sampleurl'],
 826              ['name' => 'f_' . $field2->id , 'value' => '12345'],
 827              ['name' => 'ln', 'value' => json_encode($this->student2->lastname)]
 828          ];
 829          $result = mod_data_external::search_entries($this->database->id, 0, false, '', $advsearch);
 830          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 831          $this->assertCount(2, $result['entries']);  // Only one matching everything.
 832          $this->assertEquals(2, $result['totalcount']);
 833          $this->assertEquals(3, $result['maxcount']);
 834  
 835          // Combined search (no results).
 836          $field2 = $DB->get_record('data_fields', array('type' => 'number'));
 837          $advsearch = [
 838              ['name' => 'f_' . $field->id , 'value' => 'sampleurl'],
 839              ['name' => 'f_' . $field2->id , 'value' => '98780333'], // Non existent number.
 840          ];
 841          $result = mod_data_external::search_entries($this->database->id, 0, false, '', $advsearch);
 842          $result = \external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
 843          $this->assertCount(0, $result['entries']);  // Only one matching everything.
 844          $this->assertEquals(0, $result['totalcount']);
 845          $this->assertEquals(3, $result['maxcount']);
 846      }
 847  
 848      /**
 849       * Test approve_entry.
 850       */
 851      public function test_approve_entry() {
 852          global $DB;
 853          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 854  
 855          $this->setUser($this->teacher);
 856          $this->assertEquals(0, $DB->get_field('data_records', 'approved', array('id' => $entry13)));
 857          $result = mod_data_external::approve_entry($entry13);
 858          $result = \external_api::clean_returnvalue(mod_data_external::approve_entry_returns(), $result);
 859          $this->assertEquals(1, $DB->get_field('data_records', 'approved', array('id' => $entry13)));
 860      }
 861  
 862      /**
 863       * Test unapprove_entry.
 864       */
 865      public function test_unapprove_entry() {
 866          global $DB;
 867          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 868  
 869          $this->setUser($this->teacher);
 870          $this->assertEquals(1, $DB->get_field('data_records', 'approved', array('id' => $entry11)));
 871          $result = mod_data_external::approve_entry($entry11, false);
 872          $result = \external_api::clean_returnvalue(mod_data_external::approve_entry_returns(), $result);
 873          $this->assertEquals(0, $DB->get_field('data_records', 'approved', array('id' => $entry11)));
 874      }
 875  
 876      /**
 877       * Test approve_entry missing permissions.
 878       */
 879      public function test_approve_entry_missing_permissions() {
 880          global $DB;
 881          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 882  
 883          $this->setUser($this->student1);
 884          $this->expectException('moodle_exception');
 885          mod_data_external::approve_entry($entry13);
 886      }
 887  
 888      /**
 889       * Test delete_entry as teacher. Check I can delete any entry.
 890       */
 891      public function test_delete_entry_as_teacher() {
 892          global $DB;
 893          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 894  
 895          $this->setUser($this->teacher);
 896          $result = mod_data_external::delete_entry($entry11);
 897          $result = \external_api::clean_returnvalue(mod_data_external::delete_entry_returns(), $result);
 898          $this->assertEquals(0, $DB->count_records('data_records', array('id' => $entry11)));
 899  
 900          // Entry in other group.
 901          $result = mod_data_external::delete_entry($entry21);
 902          $result = \external_api::clean_returnvalue(mod_data_external::delete_entry_returns(), $result);
 903          $this->assertEquals(0, $DB->count_records('data_records', array('id' => $entry21)));
 904      }
 905  
 906      /**
 907       * Test delete_entry as student. Check I can delete my own entries.
 908       */
 909      public function test_delete_entry_as_student() {
 910          global $DB;
 911          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 912  
 913          $this->setUser($this->student1);
 914          $result = mod_data_external::delete_entry($entry11);
 915          $result = \external_api::clean_returnvalue(mod_data_external::delete_entry_returns(), $result);
 916          $this->assertEquals(0, $DB->count_records('data_records', array('id' => $entry11)));
 917      }
 918  
 919      /**
 920       * Test delete_entry as student in read only mode period. Check I cannot delete my own entries in that period.
 921       */
 922      public function test_delete_entry_as_student_in_read_only_period() {
 923          global $DB;
 924          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 925          // Set a time period.
 926          $this->database->timeviewfrom = time() - HOURSECS;
 927          $this->database->timeviewto = time() + HOURSECS;
 928          $DB->update_record('data', $this->database);
 929  
 930          $this->setUser($this->student1);
 931          $this->expectException('moodle_exception');
 932          mod_data_external::delete_entry($entry11);
 933      }
 934  
 935      /**
 936       * Test delete_entry with an user missing permissions.
 937       */
 938      public function test_delete_entry_missing_permissions() {
 939          global $DB;
 940          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 941  
 942          $this->setUser($this->student1);
 943          $this->expectException('moodle_exception');
 944          mod_data_external::delete_entry($entry21);
 945      }
 946  
 947      /**
 948       * Test add_entry.
 949       */
 950      public function test_add_entry() {
 951          global $DB;
 952          // First create the record structure and add some entries.
 953          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
 954  
 955          $this->setUser($this->student1);
 956          $newentrydata = [];
 957          $fields = $DB->get_records('data_fields', array('dataid' => $this->database->id), 'id');
 958          // Prepare the new entry data.
 959          foreach ($fields as $field) {
 960              $subfield = $value = '';
 961  
 962              switch ($field->type) {
 963                  case 'checkbox':
 964                      $value = ['opt1', 'opt2'];
 965                      break;
 966                  case 'date':
 967                      // Add two extra.
 968                      $newentrydata[] = [
 969                          'fieldid' => $field->id,
 970                          'subfield' => 'day',
 971                          'value' => json_encode('5')
 972                      ];
 973                      $newentrydata[] = [
 974                          'fieldid' => $field->id,
 975                          'subfield' => 'month',
 976                          'value' => json_encode('1')
 977                      ];
 978                      $subfield = 'year';
 979                      $value = '1981';
 980                      break;
 981                  case 'menu':
 982                      $value = 'menu1';
 983                      break;
 984                  case 'multimenu':
 985                      $value = ['multimenu1', 'multimenu4'];
 986                      break;
 987                  case 'number':
 988                      $value = 6;
 989                      break;
 990                  case 'radiobutton':
 991                      $value = 'radioopt1';
 992                      break;
 993                  case 'text':
 994                      $value = 'some text';
 995                      break;
 996                  case 'textarea':
 997                      $newentrydata[] = [
 998                          'fieldid' => $field->id,
 999                          'subfield' => 'content1',
1000                          'value' => json_encode(FORMAT_MOODLE)
1001                      ];
1002                      $newentrydata[] = [
1003                          'fieldid' => $field->id,
1004                          'subfield' => 'itemid',
1005                          'value' => json_encode(0)
1006                      ];
1007                      $value = 'more text';
1008                      break;
1009                  case 'url':
1010                      $value = 'https://moodle.org';
1011                      $subfield = 0;
1012                      break;
1013              }
1014  
1015              $newentrydata[] = [
1016                  'fieldid' => $field->id,
1017                  'subfield' => $subfield,
1018                  'value' => json_encode($value)
1019              ];
1020          }
1021          $result = mod_data_external::add_entry($this->database->id, 0, $newentrydata);
1022          $result = \external_api::clean_returnvalue(mod_data_external::add_entry_returns(), $result);
1023  
1024          $newentryid = $result['newentryid'];
1025          $result = mod_data_external::get_entry($newentryid, true);
1026          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
1027          $this->assertEquals($this->student1->id, $result['entry']['userid']);
1028          $this->assertCount(9, $result['entry']['contents']);
1029          foreach ($result['entry']['contents'] as $content) {
1030              $field = $fields[$content['fieldid']];
1031              // Stored content same that the one retrieved by WS.
1032              $dbcontent = $DB->get_record('data_content', array('fieldid' => $field->id, 'recordid' => $newentryid));
1033              $this->assertEquals($dbcontent->content, $content['content']);
1034  
1035              // Now double check everything stored is correct.
1036              if ($field->type == 'checkbox') {
1037                  $this->assertEquals('opt1##opt2', $content['content']);
1038                  continue;
1039              }
1040              if ($field->type == 'date') {
1041                  $this->assertEquals(347500800, $content['content']); // Date in gregorian format.
1042                  continue;
1043              }
1044              if ($field->type == 'menu') {
1045                  $this->assertEquals('menu1', $content['content']);
1046                  continue;
1047              }
1048              if ($field->type == 'multimenu') {
1049                  $this->assertEquals('multimenu1##multimenu4', $content['content']);
1050                  continue;
1051              }
1052              if ($field->type == 'number') {
1053                  $this->assertEquals(6, $content['content']);
1054                  continue;
1055              }
1056              if ($field->type == 'radiobutton') {
1057                  $this->assertEquals('radioopt1', $content['content']);
1058                  continue;
1059              }
1060              if ($field->type == 'text') {
1061                  $this->assertEquals('some text', $content['content']);
1062                  continue;
1063              }
1064              if ($field->type == 'textarea') {
1065                  $this->assertEquals('more text', $content['content']);
1066                  $this->assertEquals(FORMAT_MOODLE, $content['content1']);
1067                  continue;
1068              }
1069              if ($field->type == 'url') {
1070                  $this->assertEquals('https://moodle.org', $content['content']);
1071                  continue;
1072              }
1073              $this->assertEquals('multimenu1##multimenu4', $content['content']);
1074          }
1075  
1076          // Now, try to add another entry but removing some required data.
1077          unset($newentrydata[0]);
1078          $result = mod_data_external::add_entry($this->database->id, 0, $newentrydata);
1079          $result = \external_api::clean_returnvalue(mod_data_external::add_entry_returns(), $result);
1080          $this->assertEquals(0, $result['newentryid']);
1081          $this->assertCount(0, $result['generalnotifications']);
1082          $this->assertCount(1, $result['fieldnotifications']);
1083          $this->assertEquals('field-1', $result['fieldnotifications'][0]['fieldname']);
1084          $this->assertEquals(get_string('errormustsupplyvalue', 'data'), $result['fieldnotifications'][0]['notification']);
1085      }
1086  
1087      /**
1088       * Test add_entry empty_form.
1089       */
1090      public function test_add_entry_empty_form() {
1091          $result = mod_data_external::add_entry($this->database->id, 0, []);
1092          $result = \external_api::clean_returnvalue(mod_data_external::add_entry_returns(), $result);
1093          $this->assertEquals(0, $result['newentryid']);
1094          $this->assertCount(1, $result['generalnotifications']);
1095          $this->assertCount(0, $result['fieldnotifications']);
1096          $this->assertEquals(get_string('emptyaddform', 'data'), $result['generalnotifications'][0]);
1097      }
1098  
1099      /**
1100       * Test add_entry read_only_period.
1101       */
1102      public function test_add_entry_read_only_period() {
1103          global $DB;
1104          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
1105          // Set a time period.
1106          $this->database->timeviewfrom = time() - HOURSECS;
1107          $this->database->timeviewto = time() + HOURSECS;
1108          $DB->update_record('data', $this->database);
1109  
1110          $this->setUser($this->student1);
1111          $this->expectExceptionMessage(get_string('noaccess', 'data'));
1112          $this->expectException('moodle_exception');
1113          mod_data_external::add_entry($this->database->id, 0, []);
1114      }
1115  
1116      /**
1117       * Test add_entry max_num_entries.
1118       */
1119      public function test_add_entry_max_num_entries() {
1120          global $DB;
1121          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
1122          // Set a time period.
1123          $this->database->maxentries = 1;
1124          $DB->update_record('data', $this->database);
1125  
1126          $this->setUser($this->student1);
1127          $this->expectExceptionMessage(get_string('noaccess', 'data'));
1128          $this->expectException('moodle_exception');
1129          mod_data_external::add_entry($this->database->id, 0, []);
1130      }
1131  
1132      /**
1133       * Test add_entry invalid group.
1134       */
1135      public function test_add_entry_invalid_group() {
1136          $this->setUser($this->student1);
1137          $this->expectExceptionMessage(get_string('noaccess', 'data'));
1138          $this->expectException('moodle_exception');
1139          mod_data_external::add_entry($this->database->id, $this->group2->id, []);
1140      }
1141  
1142      /**
1143       * Test update_entry.
1144       */
1145      public function test_update_entry() {
1146          global $DB;
1147          // First create the record structure and add some entries.
1148          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
1149  
1150          $this->setUser($this->student1);
1151          $newentrydata = [];
1152          $fields = $DB->get_records('data_fields', array('dataid' => $this->database->id), 'id');
1153          // Prepare the new entry data.
1154          foreach ($fields as $field) {
1155              $subfield = $value = '';
1156  
1157              switch ($field->type) {
1158                  case 'checkbox':
1159                      $value = ['opt1', 'opt2'];
1160                      break;
1161                  case 'date':
1162                      // Add two extra.
1163                      $newentrydata[] = [
1164                          'fieldid' => $field->id,
1165                          'subfield' => 'day',
1166                          'value' => json_encode('5')
1167                      ];
1168                      $newentrydata[] = [
1169                          'fieldid' => $field->id,
1170                          'subfield' => 'month',
1171                          'value' => json_encode('1')
1172                      ];
1173                      $subfield = 'year';
1174                      $value = '1981';
1175                      break;
1176                  case 'menu':
1177                      $value = 'menu1';
1178                      break;
1179                  case 'multimenu':
1180                      $value = ['multimenu1', 'multimenu4'];
1181                      break;
1182                  case 'number':
1183                      $value = 6;
1184                      break;
1185                  case 'radiobutton':
1186                      $value = 'radioopt2';
1187                      break;
1188                  case 'text':
1189                      $value = 'some text';
1190                      break;
1191                  case 'textarea':
1192                      $newentrydata[] = [
1193                          'fieldid' => $field->id,
1194                          'subfield' => 'content1',
1195                          'value' => json_encode(FORMAT_MOODLE)
1196                      ];
1197                      $newentrydata[] = [
1198                          'fieldid' => $field->id,
1199                          'subfield' => 'itemid',
1200                          'value' => json_encode(0)
1201                      ];
1202                      $value = 'more text';
1203                      break;
1204                  case 'url':
1205                      $value = 'https://moodle.org';
1206                      $subfield = 0;
1207                      break;
1208              }
1209  
1210              $newentrydata[] = [
1211                  'fieldid' => $field->id,
1212                  'subfield' => $subfield,
1213                  'value' => json_encode($value)
1214              ];
1215          }
1216          $result = mod_data_external::update_entry($entry11, $newentrydata);
1217          $result = \external_api::clean_returnvalue(mod_data_external::update_entry_returns(), $result);
1218          $this->assertTrue($result['updated']);
1219          $this->assertCount(0, $result['generalnotifications']);
1220          $this->assertCount(0, $result['fieldnotifications']);
1221  
1222          $result = mod_data_external::get_entry($entry11, true);
1223          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
1224          $this->assertEquals($this->student1->id, $result['entry']['userid']);
1225          $this->assertCount(9, $result['entry']['contents']);
1226          foreach ($result['entry']['contents'] as $content) {
1227              $field = $fields[$content['fieldid']];
1228              // Stored content same that the one retrieved by WS.
1229              $dbcontent = $DB->get_record('data_content', array('fieldid' => $field->id, 'recordid' => $entry11));
1230              $this->assertEquals($dbcontent->content, $content['content']);
1231  
1232              // Now double check everything stored is correct.
1233              if ($field->type == 'checkbox') {
1234                  $this->assertEquals('opt1##opt2', $content['content']);
1235                  continue;
1236              }
1237              if ($field->type == 'date') {
1238                  $this->assertEquals(347500800, $content['content']); // Date in gregorian format.
1239                  continue;
1240              }
1241              if ($field->type == 'menu') {
1242                  $this->assertEquals('menu1', $content['content']);
1243                  continue;
1244              }
1245              if ($field->type == 'multimenu') {
1246                  $this->assertEquals('multimenu1##multimenu4', $content['content']);
1247                  continue;
1248              }
1249              if ($field->type == 'number') {
1250                  $this->assertEquals(6, $content['content']);
1251                  continue;
1252              }
1253              if ($field->type == 'radiobutton') {
1254                  $this->assertEquals('radioopt2', $content['content']);
1255                  continue;
1256              }
1257              if ($field->type == 'text') {
1258                  $this->assertEquals('some text', $content['content']);
1259                  continue;
1260              }
1261              if ($field->type == 'textarea') {
1262                  $this->assertEquals('more text', $content['content']);
1263                  $this->assertEquals(FORMAT_MOODLE, $content['content1']);
1264                  continue;
1265              }
1266              if ($field->type == 'url') {
1267                  $this->assertEquals('https://moodle.org', $content['content']);
1268                  continue;
1269              }
1270              $this->assertEquals('multimenu1##multimenu4', $content['content']);
1271          }
1272  
1273          // Now, try to update the entry but removing some required data.
1274          unset($newentrydata[0]);
1275          $result = mod_data_external::update_entry($entry11, $newentrydata);
1276          $result = \external_api::clean_returnvalue(mod_data_external::update_entry_returns(), $result);
1277          $this->assertFalse($result['updated']);
1278          $this->assertCount(0, $result['generalnotifications']);
1279          $this->assertCount(1, $result['fieldnotifications']);
1280          $this->assertEquals('field-1', $result['fieldnotifications'][0]['fieldname']);
1281          $this->assertEquals(get_string('errormustsupplyvalue', 'data'), $result['fieldnotifications'][0]['notification']);
1282      }
1283  
1284      /**
1285       * Test update_entry sending empty data.
1286       */
1287      public function test_update_entry_empty_data() {
1288          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
1289  
1290          $this->setUser($this->student1);
1291          $result = mod_data_external::update_entry($entry11, []);
1292          $result = \external_api::clean_returnvalue(mod_data_external::update_entry_returns(), $result);
1293          $this->assertFalse($result['updated']);
1294          $this->assertCount(1, $result['generalnotifications']);
1295          $this->assertCount(9, $result['fieldnotifications']);
1296          $this->assertEquals(get_string('emptyaddform', 'data'), $result['generalnotifications'][0]);
1297      }
1298  
1299      /**
1300       * Test update_entry in read only period.
1301       */
1302      public function test_update_entry_read_only_period() {
1303          global $DB;
1304          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
1305          // Set a time period.
1306          $this->database->timeviewfrom = time() - HOURSECS;
1307          $this->database->timeviewto = time() + HOURSECS;
1308          $DB->update_record('data', $this->database);
1309  
1310          $this->setUser($this->student1);
1311          $this->expectExceptionMessage(get_string('noaccess', 'data'));
1312          $this->expectException('moodle_exception');
1313          mod_data_external::update_entry($entry11, []);
1314      }
1315  
1316      /**
1317       * Test update_entry other_user.
1318       */
1319      public function test_update_entry_other_user() {
1320          // Try to update other user entry.
1321          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
1322          $this->setUser($this->student2);
1323          $this->expectExceptionMessage(get_string('noaccess', 'data'));
1324          $this->expectException('moodle_exception');
1325          mod_data_external::update_entry($entry11, []);
1326      }
1327  
1328      /**
1329       * Test get_entry_rating_information.
1330       */
1331      public function test_get_entry_rating_information() {
1332          global $DB, $CFG;
1333          require_once($CFG->dirroot . '/rating/lib.php');
1334  
1335          $DB->set_field('data', 'assessed', RATING_AGGREGATE_SUM, array('id' => $this->database->id));
1336          $DB->set_field('data', 'scale', 100, array('id' => $this->database->id));
1337          list($entry11, $entry12, $entry13, $entry14, $entry21) = self::populate_database_with_entries();
1338  
1339          $user1 = self::getDataGenerator()->create_user();
1340          $user2 = self::getDataGenerator()->create_user();
1341          $this->getDataGenerator()->enrol_user($user1->id, $this->course->id, $this->studentrole->id, 'manual');
1342          $this->getDataGenerator()->enrol_user($user2->id, $this->course->id, $this->studentrole->id, 'manual');
1343  
1344          // Rate the entry as user1.
1345          $rating1 = new \stdClass();
1346          $rating1->contextid = $this->context->id;
1347          $rating1->component = 'mod_data';
1348          $rating1->ratingarea = 'entry';
1349          $rating1->itemid = $entry11;
1350          $rating1->rating = 50;
1351          $rating1->scaleid = 100;
1352          $rating1->userid = $user1->id;
1353          $rating1->timecreated = time();
1354          $rating1->timemodified = time();
1355          $rating1->id = $DB->insert_record('rating', $rating1);
1356  
1357          // Rate the entry as user2.
1358          $rating2 = new \stdClass();
1359          $rating2->contextid = $this->context->id;
1360          $rating2->component = 'mod_data';
1361          $rating2->ratingarea = 'entry';
1362          $rating2->itemid = $entry11;
1363          $rating2->rating = 100;
1364          $rating2->scaleid = 100;
1365          $rating2->userid = $user2->id;
1366          $rating2->timecreated = time() + 1;
1367          $rating2->timemodified = time() + 1;
1368          $rating2->id = $DB->insert_record('rating', $rating2);
1369  
1370          // As student, retrieve ratings information.
1371          $this->setUser($this->student2);
1372          $result = mod_data_external::get_entry($entry11);
1373          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
1374          $this->assertCount(1, $result['ratinginfo']['ratings']);
1375          $this->assertFalse($result['ratinginfo']['ratings'][0]['canviewaggregate']);
1376          $this->assertFalse($result['ratinginfo']['canviewall']);
1377          $this->assertFalse($result['ratinginfo']['ratings'][0]['canrate']);
1378          $this->assertTrue(!isset($result['ratinginfo']['ratings'][0]['count']));
1379  
1380          // Now, as teacher, I should see the info correctly.
1381          $this->setUser($this->teacher);
1382          $result = mod_data_external::get_entry($entry11);
1383          $result = \external_api::clean_returnvalue(mod_data_external::get_entry_returns(), $result);
1384          $this->assertCount(1, $result['ratinginfo']['ratings']);
1385          $this->assertTrue($result['ratinginfo']['ratings'][0]['canviewaggregate']);
1386          $this->assertTrue($result['ratinginfo']['canviewall']);
1387          $this->assertTrue($result['ratinginfo']['ratings'][0]['canrate']);
1388          $this->assertEquals(2, $result['ratinginfo']['ratings'][0]['count']);
1389          $this->assertEquals(100, $result['ratinginfo']['ratings'][0]['aggregate']); // Expect maximium scale value.
1390      }
1391  }