Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace gradereport_user;
  18  
  19  use externallib_advanced_testcase;
  20  use gradereport_user\external\user as user_external;
  21  
  22  defined('MOODLE_INTERNAL') || die();
  23  
  24  global $CFG;
  25  
  26  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  27  
  28  /**
  29   * User grade report functions unit tests
  30   *
  31   * @package    gradereport_user
  32   * @category   external
  33   * @copyright  2015 Juan Leyva <juan@moodle.com>
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class externallib_test extends externallib_advanced_testcase {
  37  
  38      /**
  39       * Loads some data to be used by the different tests
  40       * @param  int $s1grade Student 1 grade
  41       * @param  int $s2grade Student 2 grade
  42       * @return array Course and users instances
  43       */
  44      private function load_data(int $s1grade, int $s2grade): array {
  45          global $DB;
  46  
  47          $course = $this->getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1]);
  48  
  49          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
  50          $student1 = $this->getDataGenerator()->create_user(['idnumber' => 'testidnumber']);
  51          $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
  52  
  53          $student2 = $this->getDataGenerator()->create_user();
  54          $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
  55  
  56          $teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
  57          $teacher = $this->getDataGenerator()->create_user();
  58          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
  59  
  60          $context = \context_course::instance($course->id);
  61          assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $context);
  62          accesslib_clear_all_caches_for_unit_testing();
  63  
  64          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
  65          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
  66          groups_add_member($group1->id, $student1->id);
  67          groups_add_member($group1->id, $teacher->id);
  68          groups_add_member($group2->id, $student2->id);
  69  
  70          $assignment = $this->getDataGenerator()->create_module('assign', ['name' => "Test assign", 'course' => $course->id]);
  71          $modcontext = get_coursemodule_from_instance('assign', $assignment->id, $course->id);
  72          $assignment->cmidnumber = $modcontext->id;
  73  
  74          $student1grade = ['userid' => $student1->id, 'rawgrade' => $s1grade, 'idnumber' => 'testidnumber1'];
  75          $student2grade = ['userid' => $student2->id, 'rawgrade' => $s2grade, 'idnumber' => 'testidnumber2'];
  76          $studentgrades = [$student1->id => $student1grade, $student2->id => $student2grade];
  77          assign_grade_item_update($assignment, $studentgrades);
  78  
  79          return [
  80              $course,
  81              $teacher,
  82              $student1,
  83              $student2,
  84              $assignment
  85          ];
  86      }
  87  
  88      /**
  89       * Test get_grades_table function case teacher
  90       */
  91      public function test_get_grades_table_teacher() {
  92  
  93          $this->resetAfterTest(true);
  94  
  95          $s1grade = 80;
  96          $s2grade = 60;
  97  
  98          list($course, $teacher, $student1, $student2, $assignment) = $this->load_data($s1grade, $s2grade);
  99  
 100          // A teacher must see all student grades (in their group only).
 101          $this->setUser($teacher);
 102  
 103          $studentgrades = user_external::get_grades_table($course->id);
 104          $studentgrades = \external_api::clean_returnvalue(user_external::get_grades_table_returns(), $studentgrades);
 105  
 106          // No warnings returned.
 107          $this->assertCount(0, $studentgrades['warnings']);
 108  
 109          // Check that only grades for the student in the teacher group are returned.
 110          $this->assertCount(1, $studentgrades['tables']);
 111  
 112          // Read returned grades.
 113          $studentreturnedgrades = [];
 114  
 115          $studentreturnedgrades[$studentgrades['tables'][0]['userid']] =
 116              (int) $studentgrades['tables'][0]['tabledata'][2]['grade']['content'];
 117  
 118          $this->assertEquals($s1grade, $studentreturnedgrades[$student1->id]);
 119      }
 120  
 121      /**
 122       * Test get_grades_table function case student
 123       */
 124      public function test_get_grades_table_student() {
 125          global $CFG, $DB;
 126  
 127          $this->resetAfterTest(true);
 128  
 129          $s1grade = 80;
 130          $s2grade = 60;
 131  
 132          list($course, $teacher, $student1, $student2, $assignment) = $this->load_data($s1grade, $s2grade);
 133  
 134          // A user can see his own grades.
 135          $this->setUser($student1);
 136          $studentgrade = user_external::get_grades_table($course->id, $student1->id);
 137          $studentgrade = \external_api::clean_returnvalue(user_external::get_grades_table_returns(), $studentgrade);
 138  
 139          // No warnings returned.
 140          $this->assertTrue(count($studentgrade['warnings']) == 0);
 141  
 142          $this->assertTrue(count($studentgrade['tables']) == 1);
 143          $student1returnedgrade = (int) $studentgrade['tables'][0]['tabledata'][2]['grade']['content'];
 144          $this->assertEquals($s1grade, $student1returnedgrade);
 145  
 146      }
 147  
 148      /**
 149       * Test get_grades_table function case incorrect permissions
 150       */
 151      public function test_get_grades_table_permissions() {
 152          global $CFG, $DB;
 153  
 154          $this->resetAfterTest(true);
 155  
 156          $s1grade = 80;
 157          $s2grade = 60;
 158  
 159          list($course, $teacher, $student1, $student2, $assignment) = $this->load_data($s1grade, $s2grade);
 160  
 161          $this->setUser($student2);
 162  
 163          try {
 164              $studentgrade = user_external::get_grades_table($course->id, $student1->id);
 165              $this->fail('Exception expected due to not perissions to view other user grades.');
 166          } catch (\moodle_exception $e) {
 167              $this->assertEquals('notingroup', $e->errorcode);
 168          }
 169      }
 170  
 171      /**
 172       * Test view_grade_report function
 173       */
 174      public function test_view_grade_report() {
 175          global $USER;
 176  
 177          $this->resetAfterTest(true);
 178  
 179          $s1grade = 80;
 180          $s2grade = 60;
 181          list($course, $teacher, $student1, $student2, $assignment) = $this->load_data($s1grade, $s2grade);
 182  
 183          // Redirect events to the sink, so we can recover them later.
 184          $sink = $this->redirectEvents();
 185  
 186          $this->setUser($student1);
 187          $result = user_external::view_grade_report($course->id);
 188          $result = \external_api::clean_returnvalue(user_external::view_grade_report_returns(), $result);
 189          $events = $sink->get_events();
 190          $this->assertCount(1, $events);
 191          $event = reset($events);
 192  
 193          // Check the event details are correct.
 194          $this->assertInstanceOf('\gradereport_user\event\grade_report_viewed', $event);
 195          $this->assertEquals(\context_course::instance($course->id), $event->get_context());
 196          $this->assertEquals($USER->id, $event->get_data()['relateduserid']);
 197  
 198          $this->setUser($teacher);
 199          $result = user_external::view_grade_report($course->id, $student1->id);
 200          $result = \external_api::clean_returnvalue(user_external::view_grade_report_returns(), $result);
 201          $events = $sink->get_events();
 202          $event = reset($events);
 203          $sink->close();
 204  
 205          // Check the event details are correct.
 206          $this->assertInstanceOf('\gradereport_user\event\grade_report_viewed', $event);
 207          $this->assertEquals(\context_course::instance($course->id), $event->get_context());
 208          $this->assertEquals($student1->id, $event->get_data()['relateduserid']);
 209  
 210          $this->setUser($student2);
 211          try {
 212              $studentgrade = user_external::view_grade_report($course->id, $student1->id);
 213              $this->fail('Exception expected due to not permissions to view other user grades.');
 214          } catch (\moodle_exception $e) {
 215              $this->assertEquals('nopermissiontoviewgrades', $e->errorcode);
 216          }
 217      }
 218  
 219      /**
 220       * Test get_grades_items function case teacher
 221       */
 222      public function test_get_grade_items_teacher() {
 223  
 224          $this->resetAfterTest(true);
 225  
 226          $s1grade = 80;
 227          $s2grade = 60;
 228  
 229          list($course, $teacher, $student1, $student2, $assignment) = $this->load_data($s1grade, $s2grade);
 230  
 231          // A teacher must see all student grades (in their group only).
 232          $this->setUser($teacher);
 233  
 234          grade_set_setting($course->id, 'report_user_showrank', 1);
 235          grade_set_setting($course->id, 'report_user_showpercentage', 1);
 236          grade_set_setting($course->id, 'report_user_showhiddenitems', 1);
 237          grade_set_setting($course->id, 'report_user_showgrade', 1);
 238          grade_set_setting($course->id, 'report_user_showfeedback', 1);
 239          grade_set_setting($course->id, 'report_user_showweight', 1);
 240          grade_set_setting($course->id, 'report_user_showcontributiontocoursetotal', 1);
 241          grade_set_setting($course->id, 'report_user_showlettergrade', 1);
 242          grade_set_setting($course->id, 'report_user_showaverage', 1);
 243  
 244          $studentgrades = user_external::get_grade_items($course->id);
 245          $studentgrades = \external_api::clean_returnvalue(user_external::get_grade_items_returns(), $studentgrades);
 246          // No warnings returned.
 247          $this->assertCount(0, $studentgrades['warnings']);
 248  
 249          // Check that only grades for the student in the teacher group are returned.
 250          $this->assertCount(1, $studentgrades['usergrades']);
 251          $this->assertCount(2, $studentgrades['usergrades'][0]['gradeitems']);
 252  
 253          $this->assertEquals($course->id, $studentgrades['usergrades'][0]['courseid']);
 254          $this->assertEquals($student1->id, $studentgrades['usergrades'][0]['userid']);
 255          // Module grades.
 256          $this->assertEquals($assignment->name, $studentgrades['usergrades'][0]['gradeitems'][0]['itemname']);
 257          $this->assertEquals('mod', $studentgrades['usergrades'][0]['gradeitems'][0]['itemtype']);
 258          $this->assertEquals('assign', $studentgrades['usergrades'][0]['gradeitems'][0]['itemmodule']);
 259          $this->assertEquals($assignment->id, $studentgrades['usergrades'][0]['gradeitems'][0]['iteminstance']);
 260          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['locked']);
 261          $this->assertEquals($assignment->cmidnumber, $studentgrades['usergrades'][0]['gradeitems'][0]['cmid']);
 262          $this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][0]['itemnumber']);
 263          $this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['outcomeid']);
 264          $this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['scaleid']);
 265          $this->assertEquals(80, $studentgrades['usergrades'][0]['gradeitems'][0]['graderaw']);
 266          $this->assertEquals('80.00', $studentgrades['usergrades'][0]['gradeitems'][0]['gradeformatted']);
 267          $this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][0]['grademin']);
 268          $this->assertEquals(100, $studentgrades['usergrades'][0]['gradeitems'][0]['grademax']);
 269          $this->assertEquals('0&ndash;100', $studentgrades['usergrades'][0]['gradeitems'][0]['rangeformatted']);
 270          $this->assertEquals('80.00 %', $studentgrades['usergrades'][0]['gradeitems'][0]['percentageformatted']);
 271          $this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['feedback']);
 272          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradehiddenbydate']);
 273          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeneedsupdate']);
 274          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeishidden']);
 275          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeislocked']);
 276          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeisoverridden']);
 277          $this->assertEquals('B-', $studentgrades['usergrades'][0]['gradeitems'][0]['lettergradeformatted']);
 278          $this->assertEquals(1, $studentgrades['usergrades'][0]['gradeitems'][0]['rank']);
 279          $this->assertEquals(2, $studentgrades['usergrades'][0]['gradeitems'][0]['numusers']);
 280          $this->assertEquals(70, $studentgrades['usergrades'][0]['gradeitems'][0]['averageformatted']);
 281  
 282          // Course grades.
 283          $this->assertEquals('course', $studentgrades['usergrades'][0]['gradeitems'][1]['itemtype']);
 284          $this->assertEquals(80, $studentgrades['usergrades'][0]['gradeitems'][1]['graderaw']);
 285          $this->assertEquals('80.00', $studentgrades['usergrades'][0]['gradeitems'][1]['gradeformatted']);
 286          $this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][1]['grademin']);
 287          $this->assertEquals(100, $studentgrades['usergrades'][0]['gradeitems'][1]['grademax']);
 288          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['locked']);
 289          $this->assertEquals('0&ndash;100', $studentgrades['usergrades'][0]['gradeitems'][1]['rangeformatted']);
 290          $this->assertEquals('80.00 %', $studentgrades['usergrades'][0]['gradeitems'][1]['percentageformatted']);
 291          $this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][1]['feedback']);
 292          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradehiddenbydate']);
 293          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradeneedsupdate']);
 294          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradeishidden']);
 295          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradeislocked']);
 296          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradeisoverridden']);
 297          $this->assertEquals('B-', $studentgrades['usergrades'][0]['gradeitems'][1]['lettergradeformatted']);
 298          $this->assertEquals(1, $studentgrades['usergrades'][0]['gradeitems'][1]['rank']);
 299          $this->assertEquals(2, $studentgrades['usergrades'][0]['gradeitems'][1]['numusers']);
 300          $this->assertEquals(70, $studentgrades['usergrades'][0]['gradeitems'][1]['averageformatted']);
 301  
 302          // Now, override and lock a grade.
 303          $gradegrade = \grade_grade::fetch(['itemid' => $studentgrades['usergrades'][0]['gradeitems'][0]['id'],
 304              'userid' => $studentgrades['usergrades'][0]['userid']]);
 305          $gradegrade->set_overridden(true);
 306          $gradegrade->set_locked(1);
 307  
 308          $studentgrades = user_external::get_grade_items($course->id);
 309          $studentgrades = \external_api::clean_returnvalue(user_external::get_grade_items_returns(), $studentgrades);
 310          // No warnings returned.
 311          $this->assertCount(0, $studentgrades['warnings']);
 312  
 313          // Module grades.
 314          $this->assertTrue($studentgrades['usergrades'][0]['gradeitems'][0]['gradeislocked']);
 315          $this->assertTrue($studentgrades['usergrades'][0]['gradeitems'][0]['gradeisoverridden']);
 316      }
 317  
 318      /**
 319       * Test get_grades_items function case student
 320       */
 321      public function test_get_grade_items_student() {
 322  
 323          $this->resetAfterTest(true);
 324  
 325          $s1grade = 80;
 326          $s2grade = 60;
 327  
 328          list($course, $teacher, $student1, $student2, $assignment) = $this->load_data($s1grade, $s2grade);
 329  
 330          grade_set_setting($course->id, 'report_user_showrank', 1);
 331          grade_set_setting($course->id, 'report_user_showpercentage', 1);
 332          grade_set_setting($course->id, 'report_user_showgrade', 1);
 333          grade_set_setting($course->id, 'report_user_showfeedback', 1);
 334          grade_set_setting($course->id, 'report_user_showweight', 1);
 335          grade_set_setting($course->id, 'report_user_showcontributiontocoursetotal', 1);
 336          grade_set_setting($course->id, 'report_user_showlettergrade', 1);
 337          grade_set_setting($course->id, 'report_user_showaverage', 1);
 338  
 339          $this->setUser($student1);
 340  
 341          $studentgrades = user_external::get_grade_items($course->id, $student1->id);
 342          $studentgrades = \external_api::clean_returnvalue(user_external::get_grade_items_returns(), $studentgrades);
 343          // No warnings returned.
 344          $this->assertCount(0, $studentgrades['warnings']);
 345  
 346          // Check that only grades for the student in the teacher group are returned.
 347          $this->assertCount(1, $studentgrades['usergrades']);
 348          $this->assertCount(2, $studentgrades['usergrades'][0]['gradeitems']);
 349  
 350          $this->assertEquals($course->id, $studentgrades['usergrades'][0]['courseid']);
 351          $this->assertEquals($student1->id, $studentgrades['usergrades'][0]['userid']);
 352          $this->assertEquals($student1->idnumber, $studentgrades['usergrades'][0]['useridnumber']);
 353          $this->assertEquals($assignment->name, $studentgrades['usergrades'][0]['gradeitems'][0]['itemname']);
 354          $this->assertEquals('mod', $studentgrades['usergrades'][0]['gradeitems'][0]['itemtype']);
 355          $this->assertEquals('assign', $studentgrades['usergrades'][0]['gradeitems'][0]['itemmodule']);
 356          $this->assertEquals($assignment->id, $studentgrades['usergrades'][0]['gradeitems'][0]['iteminstance']);
 357          $this->assertNull($studentgrades['usergrades'][0]['gradeitems'][0]['locked']);
 358          $this->assertEquals($assignment->cmidnumber, $studentgrades['usergrades'][0]['gradeitems'][0]['cmid']);
 359          $this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][0]['itemnumber']);
 360          $this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['outcomeid']);
 361          $this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['scaleid']);
 362          $this->assertEquals(80, $studentgrades['usergrades'][0]['gradeitems'][0]['graderaw']);
 363          $this->assertEquals('80.00', $studentgrades['usergrades'][0]['gradeitems'][0]['gradeformatted']);
 364          $this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][0]['grademin']);
 365          $this->assertEquals(100, $studentgrades['usergrades'][0]['gradeitems'][0]['grademax']);
 366          $this->assertEquals('0&ndash;100', $studentgrades['usergrades'][0]['gradeitems'][0]['rangeformatted']);
 367          $this->assertEquals('80.00 %', $studentgrades['usergrades'][0]['gradeitems'][0]['percentageformatted']);
 368          $this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['feedback']);
 369          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradehiddenbydate']);
 370          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeneedsupdate']);
 371          $this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeishidden']);
 372          $this->assertNull($studentgrades['usergrades'][0]['gradeitems'][0]['gradeislocked']);
 373          $this->assertNull($studentgrades['usergrades'][0]['gradeitems'][0]['gradeisoverridden']);
 374          $this->assertEquals('B-', $studentgrades['usergrades'][0]['gradeitems'][0]['lettergradeformatted']);
 375          $this->assertEquals(1, $studentgrades['usergrades'][0]['gradeitems'][0]['rank']);
 376          $this->assertEquals(2, $studentgrades['usergrades'][0]['gradeitems'][0]['numusers']);
 377          $this->assertEquals(70, $studentgrades['usergrades'][0]['gradeitems'][0]['averageformatted']);
 378  
 379          // Check that the idnumber for assignment grades is equal to the cmid.
 380          $this->assertEquals((string) $studentgrades['usergrades'][0]['gradeitems'][0]['cmid'],
 381              $studentgrades['usergrades'][0]['gradeitems'][0]['idnumber']);
 382  
 383          // Hide one grade for the user.
 384          $gradegrade = new \grade_grade([
 385              'userid' => $student1->id,
 386              'itemid' => $studentgrades['usergrades'][0]['gradeitems'][0]['id']
 387          ], true);
 388          $gradegrade->set_hidden(1);
 389          $studentgrades = user_external::get_grade_items($course->id, $student1->id);
 390          $studentgrades = \external_api::clean_returnvalue(user_external::get_grade_items_returns(), $studentgrades);
 391  
 392          // Check we get only the course final grade.
 393          $this->assertCount(1, $studentgrades['usergrades']);
 394          $this->assertCount(1, $studentgrades['usergrades'][0]['gradeitems']);
 395          $this->assertEquals('course', $studentgrades['usergrades'][0]['gradeitems'][0]['itemtype']);
 396      }
 397  
 398  }