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 311 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  /**
  18   * Feedback module external functions tests
  19   *
  20   * @package    mod_feedback
  21   * @category   external
  22   * @copyright  2017 Juan Leyva <juan@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   * @since      Moodle 3.3
  25   */
  26  
  27  namespace mod_feedback\external;
  28  
  29  use core_external\external_api;
  30  use externallib_advanced_testcase;
  31  use feedback_item_multichoice;
  32  use mod_feedback_external;
  33  use moodle_exception;
  34  
  35  defined('MOODLE_INTERNAL') || die();
  36  
  37  global $CFG;
  38  
  39  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  40  require_once($CFG->dirroot . '/mod/feedback/lib.php');
  41  
  42  /**
  43   * Feedback module external functions tests
  44   *
  45   * @package    mod_feedback
  46   * @category   external
  47   * @copyright  2017 Juan Leyva <juan@moodle.com>
  48   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  49   * @since      Moodle 3.3
  50   * @covers     \mod_feedback_external
  51   */
  52  class external_test extends externallib_advanced_testcase {
  53  
  54      // TODO These should be removed.
  55      // Testcase classes should not have any properties or store state.
  56      protected $course;
  57      protected $feedback;
  58      protected $context;
  59      protected $cm;
  60      protected $student;
  61      protected $teacher;
  62      protected $studentrole;
  63      protected $teacherrole;
  64  
  65      /**
  66       * Set up for every test
  67       */
  68      public function setUp(): void {
  69          global $DB;
  70          $this->resetAfterTest();
  71          $this->setAdminUser();
  72  
  73          // Setup test data.
  74          $this->course = $this->getDataGenerator()->create_course();
  75          $this->feedback = $this->getDataGenerator()->create_module('feedback',
  76              array('course' => $this->course->id, 'email_notification' => 1));
  77          $this->context = \context_module::instance($this->feedback->cmid);
  78          $this->cm = get_coursemodule_from_instance('feedback', $this->feedback->id);
  79  
  80          // Create users.
  81          $this->student = self::getDataGenerator()->create_user();
  82          $this->teacher = self::getDataGenerator()->create_user();
  83  
  84          // Users enrolments.
  85          $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
  86          $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
  87          $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
  88          $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
  89      }
  90  
  91      /**
  92       * Helper method to add items to an existing feedback.
  93       *
  94       * @param \stdClass $feedback feedback instance
  95       * @param integer $pagescount the number of pages we want in the feedback
  96       * @return array list of items created
  97       */
  98      public function populate_feedback($feedback, $pagescount = 1) {
  99          $feedbackgenerator = $this->getDataGenerator()->get_plugin_generator('mod_feedback');
 100          $itemscreated = [];
 101  
 102          // Create at least one page.
 103          $itemscreated[] = $feedbackgenerator->create_item_label($feedback);
 104          $itemscreated[] = $feedbackgenerator->create_item_info($feedback);
 105          $itemscreated[] = $feedbackgenerator->create_item_numeric($feedback);
 106  
 107          // Check if we want more pages.
 108          for ($i = 1; $i < $pagescount; $i++) {
 109              $itemscreated[] = $feedbackgenerator->create_item_pagebreak($feedback);
 110              $itemscreated[] = $feedbackgenerator->create_item_multichoice($feedback);
 111              $itemscreated[] = $feedbackgenerator->create_item_multichoicerated($feedback);
 112              $itemscreated[] = $feedbackgenerator->create_item_textarea($feedback);
 113              $itemscreated[] = $feedbackgenerator->create_item_textfield($feedback);
 114              $itemscreated[] = $feedbackgenerator->create_item_numeric($feedback);
 115          }
 116          return $itemscreated;
 117      }
 118  
 119  
 120      /**
 121       * Test test_mod_feedback_get_feedbacks_by_courses
 122       */
 123      public function test_mod_feedback_get_feedbacks_by_courses() {
 124  
 125          // Create additional course.
 126          $course2 = self::getDataGenerator()->create_course();
 127  
 128          // Second feedback.
 129          $record = new \stdClass();
 130          $record->course = $course2->id;
 131          $feedback2 = self::getDataGenerator()->create_module('feedback', $record);
 132  
 133          // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
 134          $enrol = enrol_get_plugin('manual');
 135          $enrolinstances = enrol_get_instances($course2->id, true);
 136          $instance2 = (object) [];
 137          foreach ($enrolinstances as $courseenrolinstance) {
 138              if ($courseenrolinstance->enrol == "manual") {
 139                  $instance2 = $courseenrolinstance;
 140                  break;
 141              }
 142          }
 143  
 144          $enrol->enrol_user($instance2, $this->student->id, $this->studentrole->id);
 145  
 146          self::setUser($this->student);
 147  
 148          $returndescription = mod_feedback_external::get_feedbacks_by_courses_returns();
 149  
 150          // Create what we expect to be returned when querying the two courses.
 151          // First for the student user.
 152          $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang', 'anonymous',
 153              'multiple_submit', 'autonumbering', 'page_after_submitformat', 'publish_stats', 'completionsubmit');
 154  
 155          $properties = feedback_summary_exporter::read_properties_definition();
 156  
 157          // Add expected coursemodule and data.
 158          $feedback1 = $this->feedback;
 159          $feedback1->coursemodule = $feedback1->cmid;
 160          $feedback1->introformat = 1;
 161          $feedback1->introfiles = [];
 162          $feedback1->lang = '';
 163  
 164          $feedback2->coursemodule = $feedback2->cmid;
 165          $feedback2->introformat = 1;
 166          $feedback2->introfiles = [];
 167          $feedback2->lang = '';
 168  
 169          foreach ($expectedfields as $field) {
 170              if (!empty($properties[$field]) && $properties[$field]['type'] == PARAM_BOOL) {
 171                  $feedback1->{$field} = (bool) $feedback1->{$field};
 172                  $feedback2->{$field} = (bool) $feedback2->{$field};
 173              }
 174              $expected1[$field] = $feedback1->{$field};
 175              $expected2[$field] = $feedback2->{$field};
 176          }
 177  
 178          $expectedfeedbacks = array($expected2, $expected1);
 179  
 180          // Call the external function passing course ids.
 181          $result = mod_feedback_external::get_feedbacks_by_courses(array($course2->id, $this->course->id));
 182          $result = external_api::clean_returnvalue($returndescription, $result);
 183  
 184          $this->assertEquals($expectedfeedbacks, $result['feedbacks']);
 185          $this->assertCount(0, $result['warnings']);
 186  
 187          // Call the external function without passing course id.
 188          $result = mod_feedback_external::get_feedbacks_by_courses();
 189          $result = external_api::clean_returnvalue($returndescription, $result);
 190          $this->assertEquals($expectedfeedbacks, $result['feedbacks']);
 191          $this->assertCount(0, $result['warnings']);
 192  
 193          // Unenrol user from second course and alter expected feedbacks.
 194          $enrol->unenrol_user($instance2, $this->student->id);
 195          array_shift($expectedfeedbacks);
 196  
 197          // Call the external function without passing course id.
 198          $result = mod_feedback_external::get_feedbacks_by_courses();
 199          $result = external_api::clean_returnvalue($returndescription, $result);
 200          $this->assertEquals($expectedfeedbacks, $result['feedbacks']);
 201  
 202          // Call for the second course we unenrolled the user from, expected warning.
 203          $result = mod_feedback_external::get_feedbacks_by_courses(array($course2->id));
 204          $this->assertCount(1, $result['warnings']);
 205          $this->assertEquals('1', $result['warnings'][0]['warningcode']);
 206          $this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
 207  
 208          // Now, try as a teacher for getting all the additional fields.
 209          self::setUser($this->teacher);
 210  
 211          $additionalfields = array('email_notification', 'site_after_submit', 'page_after_submit', 'timeopen', 'timeclose',
 212              'timemodified', 'pageaftersubmitfiles');
 213  
 214          $feedback1->pageaftersubmitfiles = [];
 215  
 216          foreach ($additionalfields as $field) {
 217              if (!empty($properties[$field]) && $properties[$field]['type'] == PARAM_BOOL) {
 218                  $feedback1->{$field} = (bool) $feedback1->{$field};
 219              }
 220              $expectedfeedbacks[0][$field] = $feedback1->{$field};
 221          }
 222          $expectedfeedbacks[0]['page_after_submitformat'] = 1;
 223  
 224          $result = mod_feedback_external::get_feedbacks_by_courses();
 225          $result = external_api::clean_returnvalue($returndescription, $result);
 226          $this->assertEquals($expectedfeedbacks, $result['feedbacks']);
 227  
 228          // Admin also should get all the information.
 229          self::setAdminUser();
 230  
 231          $result = mod_feedback_external::get_feedbacks_by_courses(array($this->course->id));
 232          $result = external_api::clean_returnvalue($returndescription, $result);
 233          $this->assertEquals($expectedfeedbacks, $result['feedbacks']);
 234      }
 235  
 236      /**
 237       * Test get_feedback_access_information function with basic defaults for student.
 238       */
 239      public function test_get_feedback_access_information_student() {
 240  
 241          self::setUser($this->student);
 242          $result = mod_feedback_external::get_feedback_access_information($this->feedback->id);
 243          $result = external_api::clean_returnvalue(mod_feedback_external::get_feedback_access_information_returns(), $result);
 244  
 245          $this->assertFalse($result['canviewanalysis']);
 246          $this->assertFalse($result['candeletesubmissions']);
 247          $this->assertFalse($result['canviewreports']);
 248          $this->assertFalse($result['canedititems']);
 249          $this->assertTrue($result['cancomplete']);
 250          $this->assertTrue($result['cansubmit']);
 251          $this->assertTrue($result['isempty']);
 252          $this->assertTrue($result['isopen']);
 253          $this->assertTrue($result['isanonymous']);
 254          $this->assertFalse($result['isalreadysubmitted']);
 255      }
 256  
 257      /**
 258       * Test get_feedback_access_information function with basic defaults for teacher.
 259       */
 260      public function test_get_feedback_access_information_teacher() {
 261  
 262          self::setUser($this->teacher);
 263          $result = mod_feedback_external::get_feedback_access_information($this->feedback->id);
 264          $result = external_api::clean_returnvalue(mod_feedback_external::get_feedback_access_information_returns(), $result);
 265  
 266          $this->assertTrue($result['canviewanalysis']);
 267          $this->assertTrue($result['canviewreports']);
 268          $this->assertTrue($result['canedititems']);
 269          $this->assertTrue($result['candeletesubmissions']);
 270          $this->assertFalse($result['cancomplete']);
 271          $this->assertTrue($result['cansubmit']);
 272          $this->assertTrue($result['isempty']);
 273          $this->assertTrue($result['isopen']);
 274          $this->assertTrue($result['isanonymous']);
 275          $this->assertFalse($result['isalreadysubmitted']);
 276  
 277          // Add some items to the feedback and check is not empty any more.
 278          self::populate_feedback($this->feedback);
 279          $result = mod_feedback_external::get_feedback_access_information($this->feedback->id);
 280          $result = external_api::clean_returnvalue(mod_feedback_external::get_feedback_access_information_returns(), $result);
 281          $this->assertFalse($result['isempty']);
 282      }
 283  
 284      /**
 285       * Test view_feedback invalid id.
 286       */
 287      public function test_view_feedback_invalid_id() {
 288          // Test invalid instance id.
 289          $this->expectException(moodle_exception::class);
 290          mod_feedback_external::view_feedback(0);
 291      }
 292      /**
 293       * Test view_feedback not enrolled user.
 294       */
 295      public function test_view_feedback_not_enrolled_user() {
 296          $usernotenrolled = self::getDataGenerator()->create_user();
 297          $this->setUser($usernotenrolled);
 298          $this->expectException(moodle_exception::class);
 299          mod_feedback_external::view_feedback(0);
 300      }
 301      /**
 302       * Test view_feedback no capabilities.
 303       */
 304      public function test_view_feedback_no_capabilities() {
 305          // Test user with no capabilities.
 306          // We need a explicit prohibit since this capability is allowed for students by default.
 307          assign_capability('mod/feedback:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id);
 308          accesslib_clear_all_caches_for_unit_testing();
 309          $this->expectException(moodle_exception::class);
 310          mod_feedback_external::view_feedback(0);
 311      }
 312      /**
 313       * Test view_feedback.
 314       */
 315      public function test_view_feedback() {
 316          // Test user with full capabilities.
 317          $this->setUser($this->student);
 318          // Trigger and capture the event.
 319          $sink = $this->redirectEvents();
 320          $result = mod_feedback_external::view_feedback($this->feedback->id);
 321          $result = external_api::clean_returnvalue(mod_feedback_external::view_feedback_returns(), $result);
 322          $events = $sink->get_events();
 323          $this->assertCount(1, $events);
 324          $event = array_shift($events);
 325          // Checking that the event contains the expected values.
 326          $this->assertInstanceOf('\mod_feedback\event\course_module_viewed', $event);
 327          $this->assertEquals($this->context, $event->get_context());
 328          $moodledata = new \moodle_url('/mod/feedback/view.php', array('id' => $this->cm->id));
 329          $this->assertEquals($moodledata, $event->get_url());
 330          $this->assertEventContextNotUsed($event);
 331          $this->assertNotEmpty($event->get_name());
 332      }
 333  
 334      /**
 335       * Test get_current_completed_tmp.
 336       */
 337      public function test_get_current_completed_tmp() {
 338          global $DB;
 339  
 340          // Force non anonymous.
 341          $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_NO, array('id' => $this->feedback->id));
 342          // Add a completed_tmp record.
 343          $record = [
 344              'feedback' => $this->feedback->id,
 345              'userid' => $this->student->id,
 346              'guestid' => '',
 347              'timemodified' => time() - DAYSECS,
 348              'random_response' => 0,
 349              'anonymous_response' => FEEDBACK_ANONYMOUS_NO,
 350              'courseid' => $this->course->id,
 351          ];
 352          $record['id'] = $DB->insert_record('feedback_completedtmp', (object) $record);
 353  
 354          // Test user with full capabilities.
 355          $this->setUser($this->student);
 356  
 357          $result = mod_feedback_external::get_current_completed_tmp($this->feedback->id);
 358          $result = external_api::clean_returnvalue(mod_feedback_external::get_current_completed_tmp_returns(), $result);
 359          $this->assertEquals($record['id'], $result['feedback']['id']);
 360      }
 361  
 362      /**
 363       * Test get_items.
 364       */
 365      public function test_get_items() {
 366          // Test user with full capabilities.
 367          $this->setUser($this->student);
 368  
 369          // Add questions to the feedback, we are adding 2 pages of questions.
 370          $itemscreated = self::populate_feedback($this->feedback, 2);
 371  
 372          $result = mod_feedback_external::get_items($this->feedback->id);
 373          $result = external_api::clean_returnvalue(mod_feedback_external::get_items_returns(), $result);
 374          $this->assertCount(count($itemscreated), $result['items']);
 375          $index = 1;
 376          foreach ($result['items'] as $key => $item) {
 377              if (is_numeric($itemscreated[$key])) {
 378                  continue; // Page break.
 379              }
 380              // Cannot compare directly the exporter and the db object (exporter have more fields).
 381              $this->assertEquals($itemscreated[$key]->id, $item['id']);
 382              $this->assertEquals($itemscreated[$key]->typ, $item['typ']);
 383              $this->assertEquals($itemscreated[$key]->name, $item['name']);
 384              $this->assertEquals($itemscreated[$key]->label, $item['label']);
 385  
 386              if ($item['hasvalue']) {
 387                  $this->assertEquals($index, $item['itemnumber']);
 388                  $index++;
 389              }
 390          }
 391      }
 392  
 393      /**
 394       * Test get_items, to confirm validation is done too.
 395       *
 396       * @dataProvider items_provider
 397       * @param string $role Whether the current user should be a student or a teacher.
 398       * @param array $info Settings to create the feedback.
 399       * @param string|null $warning The warning message to display or null if warnings result is empty.
 400       */
 401      public function test_get_items_validation(string $role, array $info, ?string $warning): void {
 402          global $DB;
 403  
 404          // Test user with full capabilities.
 405          if ($role === 'teacher') {
 406              $this->setUser($this->teacher);
 407          } else {
 408              $this->setUser($this->student);
 409          }
 410  
 411          // Create the feedback.
 412          $data = ['course' => $this->course->id];
 413          if (array_key_exists('closed', $info) && $info['closed']) {
 414              $data['timeopen'] = time() + DAYSECS;
 415          }
 416          $feedback = $this->getDataGenerator()->create_module('feedback', $data);
 417  
 418          $empty = true;
 419          if (!array_key_exists('empty', $info) || !$info['empty']) {
 420              $empty = false;
 421              /** @var \mod_feedback_generator $feedbackgenerator */
 422              $feedbackgenerator = $this->getDataGenerator()->get_plugin_generator('mod_feedback');
 423              // Add,at least, one item to the feedback.
 424              $feedbackgenerator->create_item_label($feedback);
 425          }
 426  
 427          if (array_key_exists('complete', $info) && !$info['complete']) {
 428              $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 429              $coursecontext = \context_course::instance($this->course->id);
 430              assign_capability('mod/feedback:complete', CAP_PROHIBIT, $studentrole->id, $coursecontext->id);
 431              // Empty all the caches that may be affected by this change.
 432              accesslib_clear_all_caches_for_unit_testing();
 433              \course_modinfo::clear_instance_cache();
 434          }
 435  
 436          $result = mod_feedback_external::get_items($feedback->id);
 437          $result = external_api::clean_returnvalue(mod_feedback_external::get_items_returns(), $result);
 438          if ($warning) {
 439              $this->assertEmpty($result['items']);
 440              $this->assertCount(1, $result['warnings']);
 441              $resultwarning = reset($result['warnings']);
 442              if ($warning == 'required_capability_exception') {
 443                  $this->assertStringContainsString('error/nopermission', $resultwarning['message']);
 444              } else {
 445                  $this->assertEquals($warning, $resultwarning['message']);
 446              }
 447          } else {
 448              if ($empty) {
 449                  $this->assertEmpty($result['items']);
 450              } else {
 451                  $this->assertCount(1, $result['items']);
 452              }
 453              $this->assertEmpty($result['warnings']);
 454          }
 455      }
 456  
 457      /**
 458       * Data provider for test_get_items_validation() and test_get_page_items_validation().
 459       *
 460       * @return array
 461       */
 462      public function items_provider(): array {
 463          return [
 464              'Valid feedback (as student)' => [
 465                  'role' => 'student',
 466                  'info' => [],
 467                  'warning' => null,
 468              ],
 469              'Closed feedback (as student)' => [
 470                  'role' => 'student',
 471                  'info' => ['closed' => true],
 472                  'warning' => get_string('feedback_is_not_open', 'feedback'),
 473              ],
 474              'Empty feedback (as student)' => [
 475                  'role' => 'student',
 476                  'info' => ['empty' => true],
 477                  'warning' => get_string('no_items_available_yet', 'feedback'),
 478              ],
 479              'Closed feedback (as student)' => [
 480                  'role' => 'student',
 481                  'info' => ['closed' => true],
 482                  'warning' => get_string('feedback_is_not_open', 'feedback'),
 483              ],
 484              'Cannot complete feedback (as student)' => [
 485                  'role' => 'student',
 486                  'info' => ['complete' => false],
 487                  'warning' => 'required_capability_exception',
 488              ],
 489              'Valid feedback (as teacher)' => [
 490                  'role' => 'teacher',
 491                  'info' => [],
 492                  'warning' => null,
 493              ],
 494              'Closed feedback (as teacher)' => [
 495                  'role' => 'teacher',
 496                  'info' => ['closed' => true],
 497                  'warning' => null,
 498              ],
 499              'Empty feedback (as teacher)' => [
 500                  'role' => 'teacher',
 501                  'info' => ['empty' => true],
 502                  'warning' => null,
 503              ],
 504              'Closed feedback (as teacher)' => [
 505                  'role' => 'teacher',
 506                  'info' => ['closed' => true],
 507                  'warning' => null,
 508              ],
 509              'Cannot complete feedback (as teacher)' => [
 510                  'role' => 'teacher',
 511                  'info' => ['complete' => false],
 512                  'warning' => null,
 513              ],
 514          ];
 515      }
 516  
 517      /**
 518       * Test launch_feedback.
 519       */
 520      public function test_launch_feedback() {
 521          global $DB;
 522  
 523          // Test user with full capabilities.
 524          $this->setUser($this->student);
 525  
 526          // Add questions to the feedback, we are adding 2 pages of questions.
 527          $itemscreated = self::populate_feedback($this->feedback, 2);
 528  
 529          // First try a feedback we didn't attempt.
 530          $result = mod_feedback_external::launch_feedback($this->feedback->id);
 531          $result = external_api::clean_returnvalue(mod_feedback_external::launch_feedback_returns(), $result);
 532          $this->assertEquals(0, $result['gopage']);
 533  
 534          // Now, try a feedback that we attempted.
 535          // Force non anonymous.
 536          $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_NO, array('id' => $this->feedback->id));
 537          // Add a completed_tmp record.
 538          $record = [
 539              'feedback' => $this->feedback->id,
 540              'userid' => $this->student->id,
 541              'guestid' => '',
 542              'timemodified' => time() - DAYSECS,
 543              'random_response' => 0,
 544              'anonymous_response' => FEEDBACK_ANONYMOUS_NO,
 545              'courseid' => $this->course->id,
 546          ];
 547          $record['id'] = $DB->insert_record('feedback_completedtmp', (object) $record);
 548  
 549          // Add a response to the feedback for each question type with possible values.
 550          $response = [
 551              'course_id' => $this->course->id,
 552              'item' => $itemscreated[1]->id, // First item is the info question.
 553              'completed' => $record['id'],
 554              'tmp_completed' => $record['id'],
 555              'value' => 'A',
 556          ];
 557          $DB->insert_record('feedback_valuetmp', (object) $response);
 558          $response = [
 559              'course_id' => $this->course->id,
 560              'item' => $itemscreated[2]->id, // Second item is the numeric question.
 561              'completed' => $record['id'],
 562              'tmp_completed' => $record['id'],
 563              'value' => 5,
 564          ];
 565          $DB->insert_record('feedback_valuetmp', (object) $response);
 566  
 567          $result = mod_feedback_external::launch_feedback($this->feedback->id);
 568          $result = external_api::clean_returnvalue(mod_feedback_external::launch_feedback_returns(), $result);
 569          $this->assertEquals(1, $result['gopage']);
 570      }
 571  
 572      /**
 573       * Test get_page_items.
 574       */
 575      public function test_get_page_items() {
 576          // Test user with full capabilities.
 577          $this->setUser($this->student);
 578  
 579          // Add questions to the feedback, we are adding 2 pages of questions.
 580          $itemscreated = self::populate_feedback($this->feedback, 2);
 581  
 582          // Retrieve first page.
 583          $result = mod_feedback_external::get_page_items($this->feedback->id, 0);
 584          $result = external_api::clean_returnvalue(mod_feedback_external::get_page_items_returns(), $result);
 585          $this->assertCount(3, $result['items']);    // The first page has 3 items.
 586          $this->assertTrue($result['hasnextpage']);
 587          $this->assertFalse($result['hasprevpage']);
 588  
 589          // Retrieve second page.
 590          $result = mod_feedback_external::get_page_items($this->feedback->id, 1);
 591          $result = external_api::clean_returnvalue(mod_feedback_external::get_page_items_returns(), $result);
 592          $this->assertCount(5, $result['items']);    // The second page has 5 items (page break doesn't count).
 593          $this->assertFalse($result['hasnextpage']);
 594          $this->assertTrue($result['hasprevpage']);
 595      }
 596  
 597      /**
 598       * Test get_page_items, to confirm validation is done too.
 599       *
 600       * @dataProvider items_provider
 601       * @param string $role Whether the current user should be a student or a teacher.
 602       * @param array $info Settings to create the feedback.
 603       * @param string|null $warning The warning message to display or null if warnings result is empty.
 604       */
 605      public function test_get_page_items_validation(string $role, array $info, ?string $warning): void {
 606          global $DB;
 607  
 608          // Test user with full capabilities.
 609          if ($role === 'teacher') {
 610              $this->setUser($this->teacher);
 611          } else {
 612              $this->setUser($this->student);
 613          }
 614  
 615          // Create the feedback.
 616          $data = ['course' => $this->course->id];
 617          if (array_key_exists('closed', $info) && $info['closed']) {
 618              $data['timeopen'] = time() + DAYSECS;
 619          }
 620          $feedback = $this->getDataGenerator()->create_module('feedback', $data);
 621  
 622          $empty = true;
 623          if (!array_key_exists('empty', $info) || !$info['empty']) {
 624              $empty = false;
 625              /** @var \mod_feedback_generator $feedbackgenerator */
 626              $feedbackgenerator = $this->getDataGenerator()->get_plugin_generator('mod_feedback');
 627              // Add,at least, one item to the feedback.
 628              $feedbackgenerator->create_item_label($feedback);
 629          }
 630  
 631          if (array_key_exists('complete', $info) && !$info['complete']) {
 632              $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 633              $coursecontext = \context_course::instance($this->course->id);
 634              assign_capability('mod/feedback:complete', CAP_PROHIBIT, $studentrole->id, $coursecontext->id);
 635              // Empty all the caches that may be affected by this change.
 636              accesslib_clear_all_caches_for_unit_testing();
 637              \course_modinfo::clear_instance_cache();
 638          }
 639  
 640          $result = mod_feedback_external::get_page_items($feedback->id, 0);
 641          $result = external_api::clean_returnvalue(mod_feedback_external::get_items_returns(), $result);
 642          if ($warning) {
 643              $this->assertEmpty($result['items']);
 644              $this->assertCount(1, $result['warnings']);
 645              $resultwarning = reset($result['warnings']);
 646              if ($warning == 'required_capability_exception') {
 647                  $this->assertStringContainsString('error/nopermission', $resultwarning['message']);
 648              } else {
 649                  $this->assertEquals($warning, $resultwarning['message']);
 650              }
 651          } else {
 652              if ($empty) {
 653                  $this->assertEmpty($result['items']);
 654              } else {
 655                  $this->assertCount(1, $result['items']);
 656              }
 657              $this->assertEmpty($result['warnings']);
 658          }
 659      }
 660  
 661      /**
 662       * Test process_page.
 663       */
 664      public function test_process_page() {
 665          global $DB;
 666  
 667          // Test user with full capabilities.
 668          $this->setUser($this->student);
 669          $pagecontents = 'You finished it!';
 670          $DB->set_field('feedback', 'page_after_submit', $pagecontents, array('id' => $this->feedback->id));
 671  
 672          // Add questions to the feedback, we are adding 2 pages of questions.
 673          $itemscreated = self::populate_feedback($this->feedback, 2);
 674  
 675          $data = [];
 676          foreach ($itemscreated as $item) {
 677  
 678              if (empty($item->hasvalue)) {
 679                  continue;
 680              }
 681  
 682              switch ($item->typ) {
 683                  case 'textarea':
 684                  case 'textfield':
 685                      $value = 'Lorem ipsum';
 686                      break;
 687                  case 'numeric':
 688                      $value = 5;
 689                      break;
 690                  case 'multichoice':
 691                      $value = '1';
 692                      break;
 693                  case 'multichoicerated':
 694                      $value = '1';
 695                      break;
 696                  case 'info':
 697                      $value = format_string($this->course->shortname, true, array('context' => $this->context));
 698                      break;
 699                  default:
 700                      $value = '';
 701              }
 702              $data[] = ['name' => $item->typ . '_' . $item->id, 'value' => $value];
 703          }
 704  
 705          // Process first page.
 706          $firstpagedata = [$data[0], $data[1]];
 707          $result = mod_feedback_external::process_page($this->feedback->id, 0, $firstpagedata);
 708          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 709          $this->assertEquals(1, $result['jumpto']);
 710          $this->assertFalse($result['completed']);
 711  
 712          // Now, process the second page. But first we are going back to the first page.
 713          $secondpagedata = [$data[2], $data[3], $data[4], $data[5], $data[6]];
 714          $result = mod_feedback_external::process_page($this->feedback->id, 1, $secondpagedata, true);
 715          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 716          $this->assertFalse($result['completed']);
 717          $this->assertEquals(0, $result['jumpto']);  // We jumped to the first page.
 718          // Check the values were correctly saved.
 719          $tmpitems = $DB->get_records('feedback_valuetmp');
 720          $this->assertCount(7, $tmpitems);   // 2 from the first page + 5 from the second page.
 721  
 722          // Go forward again (sending the same data).
 723          $result = mod_feedback_external::process_page($this->feedback->id, 0, $firstpagedata);
 724          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 725          $this->assertEquals(1, $result['jumpto']);
 726          $this->assertFalse($result['completed']);
 727          $tmpitems = $DB->get_records('feedback_valuetmp');
 728          $this->assertCount(7, $tmpitems);   // 2 from the first page + 5 from the second page.
 729  
 730          // And finally, save everything! We are going to modify one previous recorded value.
 731          $messagessink = $this->redirectMessages();
 732          $data[2]['value'] = 2; // 2 is value of the option 'b'.
 733          $secondpagedata = [$data[2], $data[3], $data[4], $data[5], $data[6]];
 734          $result = mod_feedback_external::process_page($this->feedback->id, 1, $secondpagedata);
 735          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 736          $this->assertTrue($result['completed']);
 737          $this->assertTrue(strpos($result['completionpagecontents'], $pagecontents) !== false);
 738          // Check all the items were saved.
 739          $items = $DB->get_records('feedback_value');
 740          $this->assertCount(7, $items);
 741          // Check if the one we modified was correctly saved.
 742          $itemid = $itemscreated[4]->id;
 743          $itemsaved = $DB->get_field('feedback_value', 'value', array('item' => $itemid));
 744          $mcitem = new feedback_item_multichoice();
 745          $itemval = $mcitem->get_printval($itemscreated[4], (object) ['value' => $itemsaved]);
 746          $this->assertEquals('b', $itemval);
 747  
 748          // Check that the answers are saved for course 0.
 749          foreach ($items as $item) {
 750              $this->assertEquals(0, $item->course_id);
 751          }
 752          $completed = $DB->get_record('feedback_completed', []);
 753          $this->assertEquals(0, $completed->courseid);
 754  
 755          // Test notifications sent.
 756          $messages = $messagessink->get_messages();
 757          $messagessink->close();
 758          // Test customdata.
 759          $customdata = json_decode($messages[0]->customdata);
 760          $this->assertEquals($this->feedback->id, $customdata->instance);
 761          $this->assertEquals($this->feedback->cmid, $customdata->cmid);
 762          $this->assertObjectHasAttribute('notificationiconurl', $customdata);
 763      }
 764  
 765      /**
 766       * Test process_page for a site feedback.
 767       */
 768      public function test_process_page_site_feedback() {
 769          global $DB;
 770          $pagecontents = 'You finished it!';
 771          $this->feedback = $this->getDataGenerator()->create_module('feedback',
 772              array('course' => SITEID, 'page_after_submit' => $pagecontents));
 773  
 774          // Test user with full capabilities.
 775          $this->setUser($this->student);
 776  
 777          // Add questions to the feedback, we are adding 2 pages of questions.
 778          $itemscreated = self::populate_feedback($this->feedback, 2);
 779  
 780          $data = [];
 781          foreach ($itemscreated as $item) {
 782  
 783              if (empty($item->hasvalue)) {
 784                  continue;
 785              }
 786  
 787              switch ($item->typ) {
 788                  case 'textarea':
 789                  case 'textfield':
 790                      $value = 'Lorem ipsum';
 791                      break;
 792                  case 'numeric':
 793                      $value = 5;
 794                      break;
 795                  case 'multichoice':
 796                      $value = '1';
 797                      break;
 798                  case 'multichoicerated':
 799                      $value = '1';
 800                      break;
 801                  case 'info':
 802                      $value = format_string($this->course->shortname, true, array('context' => $this->context));
 803                      break;
 804                  default:
 805                      $value = '';
 806              }
 807              $data[] = ['name' => $item->typ . '_' . $item->id, 'value' => $value];
 808          }
 809  
 810          // Process first page.
 811          $firstpagedata = [$data[0], $data[1]];
 812          $result = mod_feedback_external::process_page($this->feedback->id, 0, $firstpagedata, false, $this->course->id);
 813          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 814          $this->assertEquals(1, $result['jumpto']);
 815          $this->assertFalse($result['completed']);
 816  
 817          // Process second page.
 818          $data[2]['value'] = 2; // 2 is value of the option 'b';
 819          $secondpagedata = [$data[2], $data[3], $data[4], $data[5], $data[6]];
 820          $result = mod_feedback_external::process_page($this->feedback->id, 1, $secondpagedata, false, $this->course->id);
 821          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 822          $this->assertTrue($result['completed']);
 823          $this->assertTrue(strpos($result['completionpagecontents'], $pagecontents) !== false);
 824          // Check all the items were saved.
 825          $items = $DB->get_records('feedback_value');
 826          $this->assertCount(7, $items);
 827          // Check if the one we modified was correctly saved.
 828          $itemid = $itemscreated[4]->id;
 829          $itemsaved = $DB->get_field('feedback_value', 'value', array('item' => $itemid));
 830          $mcitem = new feedback_item_multichoice();
 831          $itemval = $mcitem->get_printval($itemscreated[4], (object) ['value' => $itemsaved]);
 832          $this->assertEquals('b', $itemval);
 833  
 834          // Check that the answers are saved for the correct course.
 835          foreach ($items as $item) {
 836              $this->assertEquals($this->course->id, $item->course_id);
 837          }
 838          $completed = $DB->get_record('feedback_completed', []);
 839          $this->assertEquals($this->course->id, $completed->courseid);
 840      }
 841  
 842      /**
 843       * Test get_analysis.
 844       */
 845      public function test_get_analysis() {
 846          // Test user with full capabilities.
 847          $this->setUser($this->student);
 848  
 849          // Create a very simple feedback.
 850          $feedbackgenerator = $this->getDataGenerator()->get_plugin_generator('mod_feedback');
 851          $numericitem = $feedbackgenerator->create_item_numeric($this->feedback);
 852          $textfielditem = $feedbackgenerator->create_item_textfield($this->feedback);
 853  
 854          $pagedata = [
 855              ['name' => $numericitem->typ .'_'. $numericitem->id, 'value' => 5],
 856              ['name' => $textfielditem->typ .'_'. $textfielditem->id, 'value' => 'abc'],
 857          ];
 858          // Process the feedback, there is only one page so the feedback will be completed.
 859          $result = mod_feedback_external::process_page($this->feedback->id, 0, $pagedata);
 860          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 861          $this->assertTrue($result['completed']);
 862  
 863          // Retrieve analysis.
 864          $this->setUser($this->teacher);
 865          $result = mod_feedback_external::get_analysis($this->feedback->id);
 866          $result = external_api::clean_returnvalue(mod_feedback_external::get_analysis_returns(), $result);
 867          $this->assertEquals(1, $result['completedcount']);  // 1 feedback completed.
 868          $this->assertEquals(2, $result['itemscount']);  // 2 items in the feedback.
 869          $this->assertCount(2, $result['itemsdata']);
 870          $this->assertCount(1, $result['itemsdata'][0]['data']); // There are 1 response per item.
 871          $this->assertCount(1, $result['itemsdata'][1]['data']);
 872          // Check we receive the info the students filled.
 873          foreach ($result['itemsdata'] as $data) {
 874              if ($data['item']['id'] == $numericitem->id) {
 875                  $this->assertEquals(5, $data['data'][0]);
 876              } else {
 877                  $this->assertEquals('abc', $data['data'][0]);
 878              }
 879          }
 880  
 881          // Create another user / response.
 882          $anotherstudent = self::getDataGenerator()->create_user();
 883          $this->getDataGenerator()->enrol_user($anotherstudent->id, $this->course->id, $this->studentrole->id, 'manual');
 884          $this->setUser($anotherstudent);
 885  
 886          // Process the feedback, there is only one page so the feedback will be completed.
 887          $result = mod_feedback_external::process_page($this->feedback->id, 0, $pagedata);
 888          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 889          $this->assertTrue($result['completed']);
 890  
 891          // Retrieve analysis.
 892          $this->setUser($this->teacher);
 893          $result = mod_feedback_external::get_analysis($this->feedback->id);
 894          $result = external_api::clean_returnvalue(mod_feedback_external::get_analysis_returns(), $result);
 895          $this->assertEquals(2, $result['completedcount']);  // 2 feedback completed.
 896          $this->assertEquals(2, $result['itemscount']);
 897          $this->assertCount(2, $result['itemsdata'][0]['data']); // There are 2 responses per item.
 898          $this->assertCount(2, $result['itemsdata'][1]['data']);
 899      }
 900  
 901      /**
 902       * Test get_unfinished_responses.
 903       */
 904      public function test_get_unfinished_responses() {
 905          // Test user with full capabilities.
 906          $this->setUser($this->student);
 907  
 908          // Create a very simple feedback.
 909          $feedbackgenerator = $this->getDataGenerator()->get_plugin_generator('mod_feedback');
 910          $numericitem = $feedbackgenerator->create_item_numeric($this->feedback);
 911          $textfielditem = $feedbackgenerator->create_item_textfield($this->feedback);
 912          $feedbackgenerator->create_item_pagebreak($this->feedback);
 913          $labelitem = $feedbackgenerator->create_item_label($this->feedback);
 914          $numericitem2 = $feedbackgenerator->create_item_numeric($this->feedback);
 915  
 916          $pagedata = [
 917              ['name' => $numericitem->typ .'_'. $numericitem->id, 'value' => 5],
 918              ['name' => $textfielditem->typ .'_'. $textfielditem->id, 'value' => 'abc'],
 919          ];
 920          // Process the feedback, there are two pages so the feedback will be unfinished yet.
 921          $result = mod_feedback_external::process_page($this->feedback->id, 0, $pagedata);
 922          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 923          $this->assertFalse($result['completed']);
 924  
 925          // Retrieve the unfinished responses.
 926          $result = mod_feedback_external::get_unfinished_responses($this->feedback->id);
 927          $result = external_api::clean_returnvalue(mod_feedback_external::get_unfinished_responses_returns(), $result);
 928          // Check that ids and responses match.
 929          foreach ($result['responses'] as $r) {
 930              if ($r['item'] == $numericitem->id) {
 931                  $this->assertEquals(5, $r['value']);
 932              } else {
 933                  $this->assertEquals($textfielditem->id, $r['item']);
 934                  $this->assertEquals('abc', $r['value']);
 935              }
 936          }
 937      }
 938  
 939      /**
 940       * Test get_finished_responses.
 941       */
 942      public function test_get_finished_responses() {
 943          // Test user with full capabilities.
 944          $this->setUser($this->student);
 945  
 946          // Create a very simple feedback.
 947          $feedbackgenerator = $this->getDataGenerator()->get_plugin_generator('mod_feedback');
 948          $numericitem = $feedbackgenerator->create_item_numeric($this->feedback);
 949          $textfielditem = $feedbackgenerator->create_item_textfield($this->feedback);
 950  
 951          $pagedata = [
 952              ['name' => $numericitem->typ .'_'. $numericitem->id, 'value' => 5],
 953              ['name' => $textfielditem->typ .'_'. $textfielditem->id, 'value' => 'abc'],
 954          ];
 955  
 956          // Process the feedback, there is only one page so the feedback will be completed.
 957          $result = mod_feedback_external::process_page($this->feedback->id, 0, $pagedata);
 958          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
 959          $this->assertTrue($result['completed']);
 960  
 961          // Retrieve the responses.
 962          $result = mod_feedback_external::get_finished_responses($this->feedback->id);
 963          $result = external_api::clean_returnvalue(mod_feedback_external::get_finished_responses_returns(), $result);
 964          // Check that ids and responses match.
 965          foreach ($result['responses'] as $r) {
 966              if ($r['item'] == $numericitem->id) {
 967                  $this->assertEquals(5, $r['value']);
 968              } else {
 969                  $this->assertEquals($textfielditem->id, $r['item']);
 970                  $this->assertEquals('abc', $r['value']);
 971              }
 972          }
 973      }
 974  
 975      /**
 976       * Test get_non_respondents (student trying to get this information).
 977       */
 978      public function test_get_non_respondents_no_permissions() {
 979          $this->setUser($this->student);
 980          $this->expectException(moodle_exception::class);
 981          mod_feedback_external::get_non_respondents($this->feedback->id);
 982      }
 983  
 984      /**
 985       * Test get_non_respondents from an anonymous feedback.
 986       */
 987      public function test_get_non_respondents_from_anonymous_feedback() {
 988          $this->setUser($this->student);
 989          $this->expectException(moodle_exception::class);
 990          $this->expectExceptionMessage(get_string('anonymous', 'feedback'));
 991          mod_feedback_external::get_non_respondents($this->feedback->id);
 992      }
 993  
 994      /**
 995       * Test get_non_respondents.
 996       */
 997      public function test_get_non_respondents() {
 998          global $DB;
 999  
1000          // Force non anonymous.
1001          $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_NO, array('id' => $this->feedback->id));
1002  
1003          // Create another student.
1004          $anotherstudent = self::getDataGenerator()->create_user();
1005          $this->getDataGenerator()->enrol_user($anotherstudent->id, $this->course->id, $this->studentrole->id, 'manual');
1006          $this->setUser($anotherstudent);
1007  
1008          // Test user with full capabilities.
1009          $this->setUser($this->student);
1010  
1011          // Create a very simple feedback.
1012          $feedbackgenerator = $this->getDataGenerator()->get_plugin_generator('mod_feedback');
1013          $numericitem = $feedbackgenerator->create_item_numeric($this->feedback);
1014  
1015          $pagedata = [
1016              ['name' => $numericitem->typ .'_'. $numericitem->id, 'value' => 5],
1017          ];
1018  
1019          // Process the feedback, there is only one page so the feedback will be completed.
1020          $result = mod_feedback_external::process_page($this->feedback->id, 0, $pagedata);
1021          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
1022          $this->assertTrue($result['completed']);
1023  
1024          // Retrieve the non-respondent users.
1025          $this->setUser($this->teacher);
1026          $result = mod_feedback_external::get_non_respondents($this->feedback->id);
1027          $result = external_api::clean_returnvalue(mod_feedback_external::get_non_respondents_returns(), $result);
1028          $this->assertCount(0, $result['warnings']);
1029          $this->assertCount(1, $result['users']);
1030          $this->assertEquals($anotherstudent->id, $result['users'][0]['userid']);
1031  
1032          // Create another student.
1033          $anotherstudent2 = self::getDataGenerator()->create_user();
1034          $this->getDataGenerator()->enrol_user($anotherstudent2->id, $this->course->id, $this->studentrole->id, 'manual');
1035          $this->setUser($anotherstudent2);
1036          $this->setUser($this->teacher);
1037          $result = mod_feedback_external::get_non_respondents($this->feedback->id);
1038          $result = external_api::clean_returnvalue(mod_feedback_external::get_non_respondents_returns(), $result);
1039          $this->assertCount(0, $result['warnings']);
1040          $this->assertCount(2, $result['users']);
1041  
1042          // Test pagination.
1043          $result = mod_feedback_external::get_non_respondents($this->feedback->id, 0, 'lastaccess', 0, 1);
1044          $result = external_api::clean_returnvalue(mod_feedback_external::get_non_respondents_returns(), $result);
1045          $this->assertCount(0, $result['warnings']);
1046          $this->assertCount(1, $result['users']);
1047      }
1048  
1049      /**
1050       * Helper function that completes the feedback for two students.
1051       */
1052      protected function complete_basic_feedback() {
1053          global $DB;
1054  
1055          $generator = $this->getDataGenerator();
1056          // Create separated groups.
1057          $DB->set_field('course', 'groupmode', SEPARATEGROUPS);
1058          $DB->set_field('course', 'groupmodeforce', 1);
1059          assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $this->teacherrole->id, $this->context);
1060          accesslib_clear_all_caches_for_unit_testing();
1061  
1062          $group1 = $generator->create_group(array('courseid' => $this->course->id));
1063          $group2 = $generator->create_group(array('courseid' => $this->course->id));
1064  
1065          // Create another students.
1066          $anotherstudent1 = self::getDataGenerator()->create_user();
1067          $anotherstudent2 = self::getDataGenerator()->create_user();
1068          $generator->enrol_user($anotherstudent1->id, $this->course->id, $this->studentrole->id, 'manual');
1069          $generator->enrol_user($anotherstudent2->id, $this->course->id, $this->studentrole->id, 'manual');
1070  
1071          $generator->create_group_member(array('groupid' => $group1->id, 'userid' => $this->student->id));
1072          $generator->create_group_member(array('groupid' => $group1->id, 'userid' => $this->teacher->id));
1073          $generator->create_group_member(array('groupid' => $group1->id, 'userid' => $anotherstudent1->id));
1074          $generator->create_group_member(array('groupid' => $group2->id, 'userid' => $anotherstudent2->id));
1075  
1076          // Test user with full capabilities.
1077          $this->setUser($this->student);
1078  
1079          // Create a very simple feedback.
1080          $feedbackgenerator = $generator->get_plugin_generator('mod_feedback');
1081          $numericitem = $feedbackgenerator->create_item_numeric($this->feedback);
1082          $textfielditem = $feedbackgenerator->create_item_textfield($this->feedback);
1083  
1084          $pagedata = [
1085              ['name' => $numericitem->typ .'_'. $numericitem->id, 'value' => 5],
1086              ['name' => $textfielditem->typ .'_'. $textfielditem->id, 'value' => 'abc'],
1087          ];
1088  
1089          // Process the feedback, there is only one page so the feedback will be completed.
1090          $result = mod_feedback_external::process_page($this->feedback->id, 0, $pagedata);
1091          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
1092          $this->assertTrue($result['completed']);
1093  
1094          $this->setUser($anotherstudent1);
1095  
1096          $pagedata = [
1097              ['name' => $numericitem->typ .'_'. $numericitem->id, 'value' => 10],
1098              ['name' => $textfielditem->typ .'_'. $textfielditem->id, 'value' => 'def'],
1099          ];
1100  
1101          $result = mod_feedback_external::process_page($this->feedback->id, 0, $pagedata);
1102          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
1103          $this->assertTrue($result['completed']);
1104  
1105          $this->setUser($anotherstudent2);
1106  
1107          $pagedata = [
1108              ['name' => $numericitem->typ .'_'. $numericitem->id, 'value' => 10],
1109              ['name' => $textfielditem->typ .'_'. $textfielditem->id, 'value' => 'def'],
1110          ];
1111  
1112          $result = mod_feedback_external::process_page($this->feedback->id, 0, $pagedata);
1113          $result = external_api::clean_returnvalue(mod_feedback_external::process_page_returns(), $result);
1114          $this->assertTrue($result['completed']);
1115      }
1116  
1117      /**
1118       * Test get_responses_analysis for anonymous feedback.
1119       */
1120      public function test_get_responses_analysis_anonymous() {
1121          self::complete_basic_feedback();
1122  
1123          // Retrieve the responses analysis.
1124          $this->setUser($this->teacher);
1125          $result = mod_feedback_external::get_responses_analysis($this->feedback->id);
1126          $result = external_api::clean_returnvalue(mod_feedback_external::get_responses_analysis_returns(), $result);
1127          $this->assertCount(0, $result['warnings']);
1128          $this->assertEquals(0, $result['totalattempts']);
1129          $this->assertEquals(2, $result['totalanonattempts']);   // Only see my groups.
1130  
1131          foreach ($result['attempts'] as $attempt) {
1132              $this->assertEmpty($attempt['userid']); // Is anonymous.
1133          }
1134      }
1135  
1136      /**
1137       * Test get_responses_analysis for non-anonymous feedback.
1138       */
1139      public function test_get_responses_analysis_non_anonymous() {
1140          global $DB;
1141  
1142          // Force non anonymous.
1143          $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_NO, array('id' => $this->feedback->id));
1144  
1145          self::complete_basic_feedback();
1146          // Retrieve the responses analysis.
1147          $this->setUser($this->teacher);
1148          $result = mod_feedback_external::get_responses_analysis($this->feedback->id);
1149          $result = external_api::clean_returnvalue(mod_feedback_external::get_responses_analysis_returns(), $result);
1150          $this->assertCount(0, $result['warnings']);
1151          $this->assertEquals(2, $result['totalattempts']);
1152          $this->assertEquals(0, $result['totalanonattempts']);   // Only see my groups.
1153  
1154          foreach ($result['attempts'] as $attempt) {
1155              $this->assertNotEmpty($attempt['userid']);  // Is not anonymous.
1156          }
1157      }
1158  
1159      /**
1160       * Test get_last_completed for feedback anonymous not completed.
1161       */
1162      public function test_get_last_completed_anonymous_not_completed() {
1163          global $DB;
1164  
1165          // Force anonymous.
1166          $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_YES, array('id' => $this->feedback->id));
1167  
1168          // Test user with full capabilities that didn't complete the feedback.
1169          $this->setUser($this->student);
1170  
1171          $this->expectExceptionMessage(get_string('anonymous', 'feedback'));
1172          $this->expectException(moodle_exception::class);
1173          mod_feedback_external::get_last_completed($this->feedback->id);
1174      }
1175  
1176      /**
1177       * Test get_last_completed for feedback anonymous and completed.
1178       */
1179      public function test_get_last_completed_anonymous_completed() {
1180          global $DB;
1181  
1182          // Force anonymous.
1183          $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_YES, array('id' => $this->feedback->id));
1184          // Add one completion record..
1185          $record = [
1186              'feedback' => $this->feedback->id,
1187              'userid' => $this->student->id,
1188              'timemodified' => time() - DAYSECS,
1189              'random_response' => 0,
1190              'anonymous_response' => FEEDBACK_ANONYMOUS_YES,
1191              'courseid' => $this->course->id,
1192          ];
1193          $record['id'] = $DB->insert_record('feedback_completed', (object) $record);
1194  
1195          // Test user with full capabilities.
1196          $this->setUser($this->student);
1197  
1198          $this->expectExceptionMessage(get_string('anonymous', 'feedback'));
1199          $this->expectException(moodle_exception::class);
1200          mod_feedback_external::get_last_completed($this->feedback->id);
1201      }
1202  
1203      /**
1204       * Test get_last_completed for feedback not anonymous and completed.
1205       */
1206      public function test_get_last_completed_not_anonymous_completed() {
1207          global $DB;
1208  
1209          // Force non anonymous.
1210          $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_NO, array('id' => $this->feedback->id));
1211          // Add one completion record..
1212          $record = [
1213              'feedback' => $this->feedback->id,
1214              'userid' => $this->student->id,
1215              'timemodified' => time() - DAYSECS,
1216              'random_response' => 0,
1217              'anonymous_response' => FEEDBACK_ANONYMOUS_NO,
1218              'courseid' => $this->course->id,
1219          ];
1220          $record['id'] = $DB->insert_record('feedback_completed', (object) $record);
1221  
1222          // Test user with full capabilities.
1223          $this->setUser($this->student);
1224          $result = mod_feedback_external::get_last_completed($this->feedback->id);
1225          $result = external_api::clean_returnvalue(mod_feedback_external::get_last_completed_returns(), $result);
1226          $this->assertEquals($record, $result['completed']);
1227      }
1228  
1229      /**
1230       * Test get_last_completed for feedback not anonymous and not completed.
1231       */
1232      public function test_get_last_completed_not_anonymous_not_completed() {
1233          global $DB;
1234  
1235          // Force anonymous.
1236          $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_NO, array('id' => $this->feedback->id));
1237  
1238          // Test user with full capabilities that didn't complete the feedback.
1239          $this->setUser($this->student);
1240  
1241          $this->expectExceptionMessage(get_string('not_completed_yet', 'feedback'));
1242          $this->expectException(moodle_exception::class);
1243          mod_feedback_external::get_last_completed($this->feedback->id);
1244      }
1245  
1246      /**
1247       * Test get_feedback_access_information for site feedback.
1248       */
1249      public function test_get_feedback_access_information_for_site_feedback() {
1250  
1251          $sitefeedback = $this->getDataGenerator()->create_module('feedback', array('course' => SITEID));
1252          $this->setUser($this->student);
1253          // Access the site feedback via the site activity.
1254          $result = mod_feedback_external::get_feedback_access_information($sitefeedback->id);
1255          $result = external_api::clean_returnvalue(mod_feedback_external::get_feedback_access_information_returns(), $result);
1256          $this->assertTrue($result['cancomplete']);
1257          $this->assertTrue($result['cansubmit']);
1258  
1259          // Access the site feedback via course where I'm enrolled.
1260          $result = mod_feedback_external::get_feedback_access_information($sitefeedback->id, $this->course->id);
1261          $result = external_api::clean_returnvalue(mod_feedback_external::get_feedback_access_information_returns(), $result);
1262          $this->assertTrue($result['cancomplete']);
1263          $this->assertTrue($result['cansubmit']);
1264  
1265          // Access the site feedback via course where I'm not enrolled.
1266          $othercourse = $this->getDataGenerator()->create_course();
1267  
1268          $this->expectException(moodle_exception::class);
1269          mod_feedback_external::get_feedback_access_information($sitefeedback->id, $othercourse->id);
1270      }
1271  
1272      /**
1273       * Test get_feedback_access_information for site feedback mapped.
1274       */
1275      public function test_get_feedback_access_information_for_site_feedback_mapped() {
1276          global $DB;
1277  
1278          $sitefeedback = $this->getDataGenerator()->create_module('feedback', array('course' => SITEID));
1279          $this->setUser($this->student);
1280          $DB->insert_record('feedback_sitecourse_map', array('feedbackid' => $sitefeedback->id, 'courseid' => $this->course->id));
1281  
1282          // Access the site feedback via course where I'm enrolled and mapped.
1283          $result = mod_feedback_external::get_feedback_access_information($sitefeedback->id, $this->course->id);
1284          $result = external_api::clean_returnvalue(mod_feedback_external::get_feedback_access_information_returns(), $result);
1285          $this->assertTrue($result['cancomplete']);
1286          $this->assertTrue($result['cansubmit']);
1287  
1288          // Access the site feedback via course where I'm enrolled but not mapped.
1289          $othercourse = $this->getDataGenerator()->create_course();
1290          $this->getDataGenerator()->enrol_user($this->student->id, $othercourse->id, $this->studentrole->id, 'manual');
1291  
1292          $this->expectException(moodle_exception::class);
1293          $this->expectExceptionMessage(get_string('cannotaccess', 'mod_feedback'));
1294          mod_feedback_external::get_feedback_access_information($sitefeedback->id, $othercourse->id);
1295      }
1296  }