Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

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

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