Search moodle.org's
Developer Documentation

See Release Notes

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

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