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 39 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 core;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  require_once (__DIR__.'/fixtures/lib.php');
  22  
  23  /**
  24   * Test grade grades
  25   *
  26   * @package    core
  27   * @category   test
  28   * @copyright  nicolas@moodle.com
  29   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class grade_grade_test extends \grade_base_testcase {
  32  
  33      public function test_grade_grade() {
  34          $this->sub_test_grade_grade_construct();
  35          $this->sub_test_grade_grade_insert();
  36          $this->sub_test_grade_grade_update();
  37          $this->sub_test_grade_grade_fetch();
  38          $this->sub_test_grade_grade_fetch_all();
  39          $this->sub_test_grade_grade_load_grade_item();
  40          $this->sub_test_grade_grade_standardise_score();
  41          $this->sub_test_grade_grade_is_locked();
  42          $this->sub_test_grade_grade_set_hidden();
  43          $this->sub_test_grade_grade_is_hidden();
  44          $this->sub_test_grade_grade_deleted();
  45          $this->sub_test_grade_grade_deleted_event();
  46      }
  47  
  48      protected function sub_test_grade_grade_construct() {
  49          $params = new \stdClass();
  50  
  51          $params->itemid = $this->grade_items[0]->id;
  52          $params->userid = 1;
  53          $params->rawgrade = 88;
  54          $params->rawgrademax = 110;
  55          $params->rawgrademin = 18;
  56  
  57          $grade_grade = new \grade_grade($params, false);
  58          $this->assertEquals($params->itemid, $grade_grade->itemid);
  59          $this->assertEquals($params->rawgrade, $grade_grade->rawgrade);
  60      }
  61  
  62      protected function sub_test_grade_grade_insert() {
  63          $grade_grade = new \grade_grade();
  64          $this->assertTrue(method_exists($grade_grade, 'insert'));
  65  
  66          $grade_grade->itemid = $this->grade_items[0]->id;
  67          $grade_grade->userid = 10;
  68          $grade_grade->rawgrade = 88;
  69          $grade_grade->rawgrademax = 110;
  70          $grade_grade->rawgrademin = 18;
  71  
  72          // Check the grade_item's needsupdate variable first.
  73          $grade_grade->load_grade_item();
  74          $this->assertEmpty($grade_grade->grade_item->needsupdate);
  75  
  76          $grade_grade->insert();
  77  
  78          $last_grade_grade = end($this->grade_grades);
  79  
  80          $this->assertEquals($grade_grade->id, $last_grade_grade->id + 1);
  81  
  82          // Timecreated will only be set if the grade was submitted by an activity module.
  83          $this->assertTrue(empty($grade_grade->timecreated));
  84          // Timemodified will only be set if the grade was submitted by an activity module.
  85          $this->assertTrue(empty($grade_grade->timemodified));
  86  
  87          // Keep our collection the same as is in the database.
  88          $this->grade_grades[] = $grade_grade;
  89      }
  90  
  91      protected function sub_test_grade_grade_update() {
  92          $grade_grade = new \grade_grade($this->grade_grades[0], false);
  93          $this->assertTrue(method_exists($grade_grade, 'update'));
  94      }
  95  
  96      protected function sub_test_grade_grade_fetch() {
  97          $grade_grade = new \grade_grade();
  98          $this->assertTrue(method_exists($grade_grade, 'fetch'));
  99  
 100          $grades = \grade_grade::fetch(array('id'=>$this->grade_grades[0]->id));
 101          $this->assertEquals($this->grade_grades[0]->id, $grades->id);
 102          $this->assertEquals($this->grade_grades[0]->rawgrade, $grades->rawgrade);
 103      }
 104  
 105      protected function sub_test_grade_grade_fetch_all() {
 106          $grade_grade = new \grade_grade();
 107          $this->assertTrue(method_exists($grade_grade, 'fetch_all'));
 108  
 109          $grades = \grade_grade::fetch_all(array());
 110          $this->assertEquals(count($this->grade_grades), count($grades));
 111      }
 112  
 113      protected function sub_test_grade_grade_load_grade_item() {
 114          $grade_grade = new \grade_grade($this->grade_grades[0], false);
 115          $this->assertTrue(method_exists($grade_grade, 'load_grade_item'));
 116          $this->assertNull($grade_grade->grade_item);
 117          $this->assertNotEmpty($grade_grade->itemid);
 118          $this->assertNotNull($grade_grade->load_grade_item());
 119          $this->assertNotNull($grade_grade->grade_item);
 120          $this->assertEquals($this->grade_items[0]->id, $grade_grade->grade_item->id);
 121      }
 122  
 123  
 124      protected function sub_test_grade_grade_standardise_score() {
 125          $this->assertEquals(4, round(\grade_grade::standardise_score(6, 0, 7, 0, 5)));
 126          $this->assertEquals(40, \grade_grade::standardise_score(50, 30, 80, 0, 100));
 127      }
 128  
 129  
 130      /*
 131       * Disabling this test: the set_locked() arguments have been modified, rendering these tests useless until they are re-written
 132  
 133      protected function test_grade_grade_set_locked() {
 134          $grade_item = new \grade_item($this->grade_items[0]);
 135          $grade = new \grade_grade($grade_item->get_final(1));
 136          $this->assertTrue(method_exists($grade, 'set_locked'));
 137  
 138          $this->assertTrue(empty($grade_item->locked));
 139          $this->assertTrue(empty($grade->locked));
 140  
 141          $this->assertTrue($grade->set_locked(true));
 142          $this->assertFalse(empty($grade->locked));
 143          $this->assertTrue($grade->set_locked(false));
 144          $this->assertTrue(empty($grade->locked));
 145  
 146          $this->assertTrue($grade_item->set_locked(true, true));
 147          $grade = new \grade_grade($grade_item->get_final(1));
 148  
 149          $this->assertFalse(empty($grade->locked));
 150          $this->assertFalse($grade->set_locked(true, false));
 151  
 152          $this->assertTrue($grade_item->set_locked(true, false));
 153          $grade = new \grade_grade($grade_item->get_final(1));
 154  
 155          $this->assertTrue($grade->set_locked(true, false));
 156      }
 157      */
 158  
 159      protected function sub_test_grade_grade_is_locked() {
 160          $grade = new \grade_grade($this->grade_grades[0], false);
 161          $this->assertTrue(method_exists($grade, 'is_locked'));
 162  
 163          $this->assertFalse($grade->is_locked());
 164          $grade->locked = time();
 165          $this->assertTrue($grade->is_locked());
 166      }
 167  
 168      protected function sub_test_grade_grade_set_hidden() {
 169          $grade = new \grade_grade($this->grade_grades[0], false);
 170          $grade_item = new \grade_item($this->grade_items[0], false);
 171          $this->assertTrue(method_exists($grade, 'set_hidden'));
 172  
 173          $this->assertEquals(0, $grade_item->hidden);
 174          $this->assertEquals(0, $grade->hidden);
 175  
 176          $grade->set_hidden(0);
 177          $this->assertEquals(0, $grade->hidden);
 178  
 179          $grade->set_hidden(1);
 180          $this->assertEquals(1, $grade->hidden);
 181  
 182          $grade->set_hidden(0);
 183          $this->assertEquals(0, $grade->hidden);
 184      }
 185  
 186      protected function sub_test_grade_grade_is_hidden() {
 187          $grade = new \grade_grade($this->grade_grades[0], false);
 188          $this->assertTrue(method_exists($grade, 'is_hidden'));
 189  
 190          $this->assertFalse($grade->is_hidden());
 191          $grade->hidden = 1;
 192          $this->assertTrue($grade->is_hidden());
 193  
 194          $grade->hidden = time()-666;
 195          $this->assertFalse($grade->is_hidden());
 196  
 197          $grade->hidden = time()+666;
 198          $this->assertTrue($grade->is_hidden());
 199      }
 200  
 201      /**
 202       * Test \grade_grade::flatten_dependencies_array()
 203       *
 204       * @covers \grade_grade::flatten_dependencies_array()
 205       */
 206      public function test_flatten_dependencies() {
 207          // First test a simple normal case.
 208          $a = array(1 => array(2, 3), 2 => array(), 3 => array(4), 4 => array());
 209          $b = array();
 210          $expecteda = array(1 => array(2, 3, 4), 2 => array(), 3 => array(4), 4 => array());
 211          $expectedb = array(1 => 1);
 212  
 213          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 214          $this->assertSame($expecteda, $a);
 215          $this->assertSame($expectedb, $b);
 216  
 217          // Edge case - empty arrays.
 218          $a = $b = $expecteda = $expectedb = array();
 219  
 220          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 221          $this->assertSame($expecteda, $a);
 222          $this->assertSame($expectedb, $b);
 223  
 224          // Circular dependency.
 225          $a = array(1 => array(2), 2 => array(3), 3 => array(1));
 226          $b = array();
 227          $expecteda = array(1 => array(1, 2, 3), 2 => array(1, 2, 3), 3 => array(1, 2, 3));
 228  
 229          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 230          $this->assertSame($expecteda, $a);
 231          // Note - we don't test the depth when we got circular dependencies - the main thing we wanted to test was that there was
 232          // no ka-boom. The result would be hard to understand and doesn't matter.
 233  
 234          // Circular dependency 2.
 235          $a = array(1 => array(2), 2 => array(3), 3 => array(4), 4 => array(2, 1));
 236          $b = array();
 237          $expecteda = array(1 => array(1, 2, 3, 4), 2 => array(1, 2, 3, 4), 3 => array(1, 2, 3, 4), 4 => array(1, 2, 3, 4));
 238  
 239          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 240          $this->assertSame($expecteda, $a);
 241  
 242          // Missing first level dependency.
 243          $a = array(1 => array(2, 3), 3 => array(4), 4 => array());
 244          $b = array();
 245          $expecteda = array(1 => array(2, 3, 4), 3 => array(4), 4 => array());
 246          $expectedb = array(1 => 1);
 247  
 248          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 249          $this->assertSame($expecteda, $a);
 250          $this->assertSame($expectedb, $b);
 251  
 252          // Missing 2nd level dependency.
 253          $a = array(1 => array(2, 3), 2 => array(), 3 => array(4));
 254          $b = array();
 255          $expecteda = array(1 => array(2, 3, 4), 2 => array(), 3 => array(4));
 256          $expectedb = array(1 => 1);
 257  
 258          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 259          $this->assertSame($expecteda, $a);
 260          $this->assertSame($expectedb, $b);
 261  
 262          // Null first level dependency.
 263          $a = array(1 => array(2, null), 2 => array(3), 3 => array(4), 4 => array());
 264          $b = array();
 265          $expecteda = array(1 => array(2, 3, 4), 2 => array(3, 4), 3 => array(4), 4 => array());
 266          $expectedb = array(1 => 2, 2 => 1);
 267  
 268          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 269          $this->assertSame($expecteda, $a);
 270          $this->assertSame($expectedb, $b);
 271  
 272          // Null 2nd level dependency.
 273          $a = array(1 => array(2, 3), 2 => array(), 3 => array(4), 4 => array(null));
 274          $b = array();
 275          $expecteda = array(1 => array(2, 3, 4), 2 => array(), 3 => array(4), 4 => array());
 276          $expectedb = array(1 => 1);
 277  
 278          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 279          $this->assertSame($expecteda, $a);
 280          $this->assertSame($expectedb, $b);
 281  
 282          // Straight null dependency.
 283          $a = array(1 => array(2, 3), 2 => array(), 3 => array(4), 4 => null);
 284          $b = array();
 285          $expecteda = array(1 => array(2, 3, 4), 2 => array(), 3 => array(4), 4 => array());
 286          $expectedb = array(1 => 1);
 287  
 288          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 289          $this->assertSame($expecteda, $a);
 290          $this->assertSame($expectedb, $b);
 291  
 292          // Also incorrect non-array dependency.
 293          $a = array(1 => array(2, 3), 2 => array(), 3 => array(4), 4 => 23);
 294          $b = array();
 295          $expecteda = array(1 => array(2, 3, 4), 2 => array(), 3 => array(4), 4 => array());
 296          $expectedb = array(1 => 1);
 297  
 298          \test_grade_grade_flatten_dependencies_array::test_flatten_dependencies_array($a, $b);
 299          $this->assertSame($expecteda, $a);
 300          $this->assertSame($expectedb, $b);
 301      }
 302  
 303      public function test_grade_grade_min_max() {
 304          global $CFG;
 305          $initialminmaxtouse = $CFG->grade_minmaxtouse;
 306  
 307          $this->setAdminUser();
 308          $course = $this->getDataGenerator()->create_course();
 309          $user = $this->getDataGenerator()->create_user();
 310          $assignrecord = $this->getDataGenerator()->create_module('assign', array('course' => $course, 'grade' => 100));
 311          $cm = get_coursemodule_from_instance('assign', $assignrecord->id);
 312          $assigncontext = \context_module::instance($cm->id);
 313          $assign = new \assign($assigncontext, $cm, $course);
 314  
 315          // Fetch the assignment item.
 316          $giparams = array('itemtype' => 'mod', 'itemmodule' => 'assign', 'iteminstance' => $assignrecord->id,
 317                  'courseid' => $course->id, 'itemnumber' => 0);
 318          $gi = \grade_item::fetch($giparams);
 319          $this->assertEquals(0, $gi->grademin);
 320          $this->assertEquals(100, $gi->grademax);
 321  
 322          // Give a grade to the student.
 323          $usergrade = $assign->get_user_grade($user->id, true);
 324          $usergrade->grade = 10;
 325          $assign->update_grade($usergrade);
 326  
 327          // Check the grade stored in gradebook.
 328          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 329          $this->assertEquals(10, $gg->rawgrade);
 330          $this->assertEquals(0, $gg->get_grade_min());
 331          $this->assertEquals(100, $gg->get_grade_max());
 332  
 333          // Change the min/max grade of the item.
 334          $gi->grademax = 50;
 335          $gi->grademin = 2;
 336          $gi->update();
 337  
 338          // Fetch the updated item.
 339          $gi = \grade_item::fetch($giparams);
 340  
 341          // Now check the grade grade min/max with system setting.
 342          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_ITEM;
 343          grade_set_setting($course->id, 'minmaxtouse', null); // Ensure no course setting.
 344  
 345          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 346          $this->assertEquals(2, $gg->get_grade_min());
 347          $this->assertEquals(50, $gg->get_grade_max());
 348  
 349          // Now with other system setting.
 350          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_GRADE;
 351          grade_set_setting($course->id, 'minmaxtouse', null); // Ensure no course setting, and reset static cache.
 352          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 353          $this->assertEquals(0, $gg->get_grade_min());
 354          $this->assertEquals(100, $gg->get_grade_max());
 355  
 356          // Now with overriden setting in course.
 357          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_ITEM;
 358          grade_set_setting($course->id, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_GRADE);
 359          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 360          $this->assertEquals(0, $gg->get_grade_min());
 361          $this->assertEquals(100, $gg->get_grade_max());
 362  
 363          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_GRADE;
 364          grade_set_setting($course->id, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_ITEM);
 365          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 366          $this->assertEquals(2, $gg->get_grade_min());
 367          $this->assertEquals(50, $gg->get_grade_max());
 368  
 369          $CFG->grade_minmaxtouse = $initialminmaxtouse;
 370      }
 371  
 372      public function test_grade_grade_min_max_with_course_item() {
 373          global $CFG, $DB;
 374          $initialminmaxtouse = $CFG->grade_minmaxtouse;
 375  
 376          $this->setAdminUser();
 377          $course = $this->getDataGenerator()->create_course();
 378          $user = $this->getDataGenerator()->create_user();
 379          $gi = \grade_item::fetch_course_item($course->id);
 380  
 381          // Fetch the category item.
 382          $this->assertEquals(0, $gi->grademin);
 383          $this->assertEquals(100, $gi->grademax);
 384  
 385          // Give a grade to the student.
 386          $gi->update_final_grade($user->id, 10);
 387  
 388          // Check the grade min/max stored in gradebook.
 389          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 390          $this->assertEquals(0, $gg->get_grade_min());
 391          $this->assertEquals(100, $gg->get_grade_max());
 392  
 393          // Change the min/max grade of the item.
 394          $gi->grademin = 2;
 395          $gi->grademax = 50;
 396          $gi->update();
 397  
 398          // Fetch the updated item.
 399          $gi = \grade_item::fetch_course_item($course->id);
 400  
 401          // Now check the grade grade min/max with system setting.
 402          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_ITEM;
 403          grade_set_setting($course->id, 'minmaxtouse', null); // Ensure no course setting.
 404  
 405          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 406          $this->assertEquals(0, $gg->get_grade_min());
 407          $this->assertEquals(100, $gg->get_grade_max());
 408  
 409          // Now with other system setting.
 410          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_GRADE;
 411          grade_set_setting($course->id, 'minmaxtouse', null); // Ensure no course setting, and reset static cache.
 412          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 413          $this->assertEquals(0, $gg->get_grade_min());
 414          $this->assertEquals(100, $gg->get_grade_max());
 415  
 416          // Now with overriden setting in course.
 417          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_ITEM;
 418          grade_set_setting($course->id, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_GRADE);
 419          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 420          $this->assertEquals(0, $gg->get_grade_min());
 421          $this->assertEquals(100, $gg->get_grade_max());
 422  
 423          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_GRADE;
 424          grade_set_setting($course->id, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_ITEM);
 425          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 426          $this->assertEquals(0, $gg->get_grade_min());
 427          $this->assertEquals(100, $gg->get_grade_max());
 428  
 429          $CFG->grade_minmaxtouse = $initialminmaxtouse;
 430      }
 431  
 432      public function test_grade_grade_min_max_with_category_item() {
 433          global $CFG, $DB;
 434          $initialminmaxtouse = $CFG->grade_minmaxtouse;
 435  
 436          $this->setAdminUser();
 437          $course = $this->getDataGenerator()->create_course();
 438          $user = $this->getDataGenerator()->create_user();
 439          $coursegi = \grade_item::fetch_course_item($course->id);
 440  
 441          // Create a category item.
 442          $gc = new \grade_category(array('courseid' => $course->id, 'fullname' => 'test'), false);
 443          $gc->insert();
 444          $gi = $gc->get_grade_item();
 445          $gi->grademax = 100;
 446          $gi->grademin = 0;
 447          $gi->update();
 448  
 449          // Fetch the category item.
 450          $giparams = array('itemtype' => 'category', 'iteminstance' => $gc->id);
 451          $gi = \grade_item::fetch($giparams);
 452          $this->assertEquals(0, $gi->grademin);
 453          $this->assertEquals(100, $gi->grademax);
 454  
 455          // Give a grade to the student.
 456          $gi->update_final_grade($user->id, 10);
 457  
 458          // Check the grade min/max stored in gradebook.
 459          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 460          $this->assertEquals(0, $gg->get_grade_min());
 461          $this->assertEquals(100, $gg->get_grade_max());
 462  
 463          // Change the min/max grade of the item.
 464          $gi->grademin = 2;
 465          $gi->grademax = 50;
 466          $gi->update();
 467  
 468          // Fetch the updated item.
 469          $gi = \grade_item::fetch($giparams);
 470  
 471          // Now check the grade grade min/max with system setting.
 472          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_ITEM;
 473          grade_set_setting($course->id, 'minmaxtouse', null); // Ensure no course setting.
 474  
 475          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 476          $this->assertEquals(0, $gg->get_grade_min());
 477          $this->assertEquals(100, $gg->get_grade_max());
 478  
 479          // Now with other system setting.
 480          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_GRADE;
 481          grade_set_setting($course->id, 'minmaxtouse', null); // Ensure no course setting, and reset static cache.
 482          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 483          $this->assertEquals(0, $gg->get_grade_min());
 484          $this->assertEquals(100, $gg->get_grade_max());
 485  
 486          // Now with overriden setting in course.
 487          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_ITEM;
 488          grade_set_setting($course->id, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_GRADE);
 489          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 490          $this->assertEquals(0, $gg->get_grade_min());
 491          $this->assertEquals(100, $gg->get_grade_max());
 492  
 493          $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_GRADE;
 494          grade_set_setting($course->id, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_ITEM);
 495          $gg = \grade_grade::fetch(array('userid' => $user->id, 'itemid' => $gi->id));
 496          $this->assertEquals(0, $gg->get_grade_min());
 497          $this->assertEquals(100, $gg->get_grade_max());
 498  
 499          $CFG->grade_minmaxtouse = $initialminmaxtouse;
 500      }
 501  
 502      /**
 503       * Tests when a grade_grade has been deleted.
 504       */
 505      public function sub_test_grade_grade_deleted() {
 506          $dg = $this->getDataGenerator();
 507  
 508          // Create the data we need for the tests.
 509          $fs = new \file_storage();
 510          $u1 = $dg->create_user();
 511          $c1 = $dg->create_course();
 512          $a1 = $dg->create_module('assign', ['course' => $c1->id]);
 513          $a1context = \context_module::instance($a1->cmid);
 514  
 515          $gi = new \grade_item($dg->create_grade_item(
 516              [
 517                  'courseid' => $c1->id,
 518                  'itemtype' => 'mod',
 519                  'itemmodule' => 'assign',
 520                  'iteminstance' => $a1->id
 521              ]
 522          ), false);
 523  
 524          // Add feedback files to copy as our update.
 525          $this->add_feedback_file_to_copy();
 526  
 527          $grades['feedback'] = 'Nice feedback!';
 528          $grades['feedbackformat'] = FORMAT_MOODLE;
 529          $grades['feedbackfiles'] = [
 530              'contextid' => 1,
 531              'component' => 'test',
 532              'filearea' => 'testarea',
 533              'itemid' => 1
 534          ];
 535  
 536          $grades['userid'] = $u1->id;
 537          grade_update('mod/assign', $gi->courseid, $gi->itemtype, $gi->itemmodule, $gi->iteminstance,
 538              $gi->itemnumber, $grades);
 539  
 540          // Feedback file area.
 541          $files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
 542          $this->assertEquals(2, count($files));
 543  
 544          // History file area.
 545          $files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
 546          $this->assertEquals(2, count($files));
 547  
 548          $gg = \grade_grade::fetch(array('userid' => $u1->id, 'itemid' => $gi->id));
 549  
 550          $gg->delete();
 551  
 552          // Feedback file area.
 553          $files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
 554          $this->assertEquals(0, count($files));
 555  
 556          // History file area.
 557          $files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
 558          $this->assertEquals(2, count($files));
 559      }
 560  
 561      /**
 562       * Creates a feedback file to copy to the gradebook area.
 563       */
 564      private function add_feedback_file_to_copy() {
 565          $dummy = array(
 566              'contextid' => 1,
 567              'component' => 'test',
 568              'filearea' => 'testarea',
 569              'itemid' => 1,
 570              'filepath' => '/',
 571              'filename' => 'feedback1.txt'
 572          );
 573  
 574          $fs = get_file_storage();
 575          $fs->create_file_from_string($dummy, '');
 576      }
 577  
 578      /**
 579       * Tests grade_deleted event.
 580       */
 581      public function sub_test_grade_grade_deleted_event() {
 582          global $DB;
 583          $dg = $this->getDataGenerator();
 584  
 585          // Create the data we need for the tests.
 586          $u1 = $dg->create_user();
 587          $u2 = $dg->create_user();
 588          $c1 = $dg->create_course();
 589          $a1 = $dg->create_module('assign', ['course' => $c1->id]);
 590  
 591          $gi = new \grade_item($dg->create_grade_item(
 592              [
 593                  'courseid' => $c1->id,
 594                  'itemtype' => 'mod',
 595                  'itemmodule' => 'assign',
 596                  'iteminstance' => $a1->id
 597              ]
 598          ), false);
 599  
 600          grade_update('mod/assign', $gi->courseid, $gi->itemtype, $gi->itemmodule, $gi->iteminstance,
 601              $gi->itemnumber, ['userid' => $u1->id]);
 602          grade_update('mod/assign', $gi->courseid, $gi->itemtype, $gi->itemmodule, $gi->iteminstance,
 603              $gi->itemnumber, ['userid' => $u2->id]);
 604  
 605          $gg = \grade_grade::fetch(array('userid' => $u1->id, 'itemid' => $gi->id));
 606          $this->assertEquals($u1->id, $gg->userid);
 607          $gg->load_grade_item();
 608          $this->assertEquals($gi->id, $gg->grade_item->id);
 609  
 610          // Delete user with valid grade item.
 611          $sink = $this->redirectEvents();
 612          grade_user_delete($u1->id);
 613          $events = $sink->get_events();
 614          $event = reset($events);
 615          $sink->close();
 616          $this->assertInstanceOf('core\event\grade_deleted', $event);
 617  
 618          $gg = \grade_grade::fetch(array('userid' => $u2->id, 'itemid' => $gi->id));
 619          $this->assertEquals($u2->id, $gg->userid);
 620          $gg->load_grade_item();
 621          $this->assertEquals($gi->id, $gg->grade_item->id);
 622  
 623          // Delete grade item, mock up orphaned grade_grades.
 624          $DB->delete_records('grade_items', ['id' => $gi->id]);
 625          $gg = \grade_grade::fetch(array('userid' => $u2->id, 'itemid' => $gi->id));
 626          $this->assertEquals($u2->id, $gg->userid);
 627  
 628          // No event is triggered and there is a debugging message.
 629          $sink = $this->redirectEvents();
 630          grade_user_delete($u2->id);
 631          $this->assertDebuggingCalled("Missing grade item id $gi->id");
 632          $events = $sink->get_events();
 633          $sink->close();
 634          $this->assertEmpty($events);
 635  
 636          // The grade should be deleted.
 637          $gg = \grade_grade::fetch(array('userid' => $u2->id, 'itemid' => $gi->id));
 638          $this->assertEmpty($gg);
 639      }
 640  
 641      /**
 642       * Tests get_hiding_affected by locked category and overridden grades.
 643       */
 644      public function test_category_get_hiding_affected() {
 645          $generator = $this->getDataGenerator();
 646  
 647          // Create the data we need for the tests.
 648          $course1 = $generator->create_course();
 649          $user1 = $generator->create_and_enrol($course1, 'student');
 650          $assignment2 = $generator->create_module('assign', ['course' => $course1->id]);
 651  
 652          // Create a category item.
 653          $gradecategory = new \grade_category(array('courseid' => $course1->id, 'fullname' => 'test'), false);
 654          $gradecategoryid = $gradecategory->insert();
 655  
 656          // Create one hidden grade item.
 657          $gradeitem1a = new \grade_item($generator->create_grade_item(
 658              [
 659                  'courseid' => $course1->id,
 660                  'itemtype' => 'mod',
 661                  'itemmodule' => 'assign',
 662                  'iteminstance' => $assignment2->id,
 663                  'categoryid' => $gradecategoryid,
 664                  'hidden' => 1,
 665              ]
 666          ), false);
 667          grade_update('mod/assign', $gradeitem1a->courseid, $gradeitem1a->itemtype, $gradeitem1a->itemmodule, $gradeitem1a->iteminstance,
 668          $gradeitem1a->itemnumber, ['userid' => $user1->id]);
 669  
 670          // Get category grade item.
 671          $gradeitem = $gradecategory->get_grade_item();
 672          // Reset needsupdate to allow set_locked.
 673          $gradeitem->needsupdate = 0;
 674          $gradeitem->update();
 675          // Lock category grade item.
 676          $gradeitem->set_locked(1);
 677  
 678          $hidingaffectedlocked = $this->call_get_hiding_affected($course1, $user1);
 679          // Since locked category now should be recalculated.
 680          // The number of unknown items is 2, this includes category item and course item.
 681          $this->assertEquals(2, count($hidingaffectedlocked['unknown']));
 682  
 683          // Unlock category.
 684          $gradeitem->set_locked(0);
 685          $hidingaffectedunlocked = $this->call_get_hiding_affected($course1, $user1);
 686          // When category unlocked, hidden item should exist in altered items.
 687          $this->assertTrue(in_array($gradeitem1a->id, array_keys($hidingaffectedunlocked['altered'])));
 688  
 689          // This creates all the grade_grades we need.
 690          grade_regrade_final_grades($course1->id);
 691  
 692          // Set grade override.
 693          $gradegrade = \grade_grade::fetch([
 694              'userid' => $user1->id,
 695              'itemid' => $gradeitem->id,
 696          ]);
 697          // Set override grade grade, and check that grade submission has been overridden.
 698          $gradegrade->set_overridden(true);
 699          $this->assertEquals(true, $gradegrade->is_overridden());
 700          $hidingaffectedoverridden = $this->call_get_hiding_affected($course1, $user1);
 701          // No need to recalculate overridden grades.
 702          $this->assertTrue(in_array($gradegrade->itemid, array_keys($hidingaffectedoverridden['alteredaggregationstatus'])));
 703          $this->assertEquals('used', $hidingaffectedoverridden['alteredaggregationstatus'][$gradegrade->itemid]);
 704      }
 705  
 706      /**
 707       * Call get_hiding_affected().
 708       * @param \stdClass $course The course object
 709       * @param \stdClass $user The student object
 710       * @return array
 711       */
 712      private function call_get_hiding_affected($course, $user) {
 713          global $DB;
 714  
 715          $items = \grade_item::fetch_all(array('courseid' => $course->id));
 716          $grades = array();
 717          $sql = "SELECT g.*
 718                    FROM {grade_grades} g
 719                    JOIN {grade_items} gi ON gi.id = g.itemid
 720                   WHERE g.userid = :userid AND gi.courseid = :courseid";
 721          if ($gradesrecords = $DB->get_records_sql($sql, ['userid' => $user->id, 'courseid' => $course->id])) {
 722              foreach ($gradesrecords as $grade) {
 723                  $grades[$grade->itemid] = new \grade_grade($grade, false);
 724              }
 725              unset($gradesrecords);
 726          }
 727          foreach ($items as $itemid => $gradeitem) {
 728              if (!isset($grades[$itemid])) {
 729                  $gradegrade = new \grade_grade();
 730                  $gradegrade->userid = $user->id;
 731                  $gradegrade->itemid = $gradeitem->id;
 732                  $grades[$itemid] = $gradegrade;
 733              }
 734              $gradeitem->grade_item = $gradeitem;
 735          }
 736  
 737          return \grade_grade::get_hiding_affected($grades, $items);
 738      }
 739  }