Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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