Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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