Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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  class core_grade_item_testcase extends grade_base_testcase {
  29      public function test_grade_item() {
  30          $this->sub_test_grade_item_construct();
  31          $this->sub_test_grade_item_insert();
  32          $this->sub_test_grade_item_delete();
  33          $this->sub_test_grade_item_update();
  34          $this->sub_test_grade_item_load_scale();
  35          $this->sub_test_grade_item_load_outcome();
  36          $this->sub_test_grade_item_qualifies_for_regrading();
  37          $this->sub_test_grade_item_force_regrading();
  38          $this->sub_test_grade_item_fetch();
  39          $this->sub_test_grade_item_fetch_all();
  40          $this->sub_test_grade_item_get_all_finals();
  41          $this->sub_test_grade_item_get_final();
  42          $this->sub_test_grade_item_get_sortorder();
  43          $this->sub_test_grade_item_set_sortorder();
  44          $this->sub_test_grade_item_move_after_sortorder();
  45          $this->sub_test_grade_item_get_name();
  46          $this->sub_test_grade_item_set_parent();
  47          $this->sub_test_grade_item_get_parent_category();
  48          $this->sub_test_grade_item_load_parent_category();
  49          $this->sub_test_grade_item_get_item_category();
  50          $this->sub_test_grade_item_load_item_category();
  51          $this->sub_test_grade_item_regrade_final_grades();
  52          $this->sub_test_grade_item_adjust_raw_grade();
  53          $this->sub_test_grade_item_rescale_grades_keep_percentage();
  54          $this->sub_test_grade_item_set_locked();
  55          $this->sub_test_grade_item_is_locked();
  56          $this->sub_test_grade_item_set_hidden();
  57          $this->sub_test_grade_item_is_hidden();
  58          $this->sub_test_grade_item_is_category_item();
  59          $this->sub_test_grade_item_is_course_item();
  60          $this->sub_test_grade_item_fetch_course_item();
  61          $this->sub_test_grade_item_depends_on();
  62          $this->sub_test_refresh_grades();
  63          $this->sub_test_grade_item_is_calculated();
  64          $this->sub_test_grade_item_set_calculation();
  65          $this->sub_test_grade_item_get_calculation();
  66          $this->sub_test_grade_item_compute();
  67          $this->sub_test_update_final_grade();
  68          $this->sub_test_grade_item_can_control_visibility();
  69          $this->sub_test_grade_item_fix_sortorder();
  70          $this->sub_test_grade_item_created_event();
  71          $this->sub_test_grade_item_updated_event();
  72      }
  73  
  74      protected function sub_test_grade_item_construct() {
  75          $params = new stdClass();
  76  
  77          $params->courseid = $this->courseid;
  78          $params->categoryid = $this->grade_categories[1]->id;
  79          $params->itemname = 'unittestgradeitem4';
  80          $params->itemtype = 'mod';
  81          $params->itemmodule = 'database';
  82          $params->iteminfo = 'Grade item used for unit testing';
  83  
  84          $grade_item = new grade_item($params, false);
  85  
  86          $this->assertEquals($params->courseid, $grade_item->courseid);
  87          $this->assertEquals($params->categoryid, $grade_item->categoryid);
  88          $this->assertEquals($params->itemmodule, $grade_item->itemmodule);
  89      }
  90  
  91      protected function sub_test_grade_item_insert() {
  92          $grade_item = new grade_item();
  93          $this->assertTrue(method_exists($grade_item, 'insert'));
  94  
  95          $grade_item->courseid = $this->courseid;
  96          $grade_item->categoryid = $this->grade_categories[1]->id;
  97          $grade_item->itemname = 'unittestgradeitem4';
  98          $grade_item->itemtype = 'mod';
  99          $grade_item->itemmodule = 'quiz';
 100          $grade_item->iteminfo = 'Grade item used for unit testing';
 101  
 102          $grade_item->insert();
 103  
 104          $last_grade_item = end($this->grade_items);
 105  
 106          $this->assertEquals($grade_item->id, $last_grade_item->id + 1);
 107          $this->assertEquals(18, $grade_item->sortorder);
 108  
 109          // Keep our reference collection the same as what is in the database.
 110          $this->grade_items[] = $grade_item;
 111      }
 112  
 113      protected function sub_test_grade_item_delete() {
 114          global $DB;
 115          $grade_item = new grade_item($this->grade_items[7], false); // Use a grade item not touched by previous (or future) tests.
 116          $this->assertTrue(method_exists($grade_item, 'delete'));
 117  
 118          // Add two files.
 119          $dummy = array(
 120              'contextid' => $grade_item->get_context()->id,
 121              'component' => GRADE_FILE_COMPONENT,
 122              'filearea' => GRADE_HISTORY_FEEDBACK_FILEAREA,
 123              'itemid' => 1,
 124              'filepath' => '/',
 125              'filename' => 'feedback1.txt'
 126          );
 127  
 128          $fs = get_file_storage();
 129          $fs->create_file_from_string($dummy, '');
 130  
 131          $dummy['itemid'] = 2;
 132          $fs->create_file_from_string($dummy, '');
 133  
 134          $files = $fs->get_area_files($grade_item->get_context()->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
 135          // Includes directories.
 136          $this->assertCount(4, $files);
 137  
 138          $this->assertTrue($grade_item->delete());
 139  
 140          $this->assertFalse($DB->get_record('grade_items', array('id' => $grade_item->id)));
 141  
 142          $files = $fs->get_area_files($grade_item->get_context()->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
 143          $this->assertEmpty($files);
 144  
 145          // Keep our reference collection the same as the database.
 146          unset($this->grade_items[7]);
 147      }
 148  
 149      protected function sub_test_grade_item_update() {
 150          global $DB;
 151          $grade_item = new grade_item($this->grade_items[0], false);
 152          $this->assertTrue(method_exists($grade_item, 'update'));
 153  
 154          $grade_item->iteminfo = 'Updated info for this unittest grade_item';
 155  
 156          $this->assertTrue($grade_item->update());
 157  
 158          $grade_item->grademin = 14;
 159          $this->assertTrue($grade_item->qualifies_for_regrading());
 160          $this->assertTrue($grade_item->update());
 161  
 162          $iteminfo = $DB->get_field('grade_items', 'iteminfo', array('id' => $this->grade_items[0]->id));
 163          $this->assertEquals($grade_item->iteminfo, $iteminfo);
 164      }
 165  
 166      protected function sub_test_grade_item_load_scale() {
 167          $grade_item = new grade_item($this->grade_items[2], false);
 168          $this->assertTrue(method_exists($grade_item, 'load_scale'));
 169          $scale = $grade_item->load_scale();
 170          $this->assertFalse(empty($grade_item->scale));
 171          $this->assertEquals($scale->id, $this->grade_items[2]->scaleid);
 172      }
 173  
 174      protected function sub_test_grade_item_load_outcome() {
 175          $grade_item = new grade_item($this->grade_items[0], false);
 176          $this->assertTrue(method_exists($grade_item, 'load_outcome'));
 177          // TODO: add tests.
 178      }
 179  
 180      protected function sub_test_grade_item_qualifies_for_regrading() {
 181          $grade_item = new grade_item($this->grade_items[3], false); // Use a grade item not touched by previous tests.
 182          $this->assertTrue(method_exists($grade_item, 'qualifies_for_regrading'));
 183  
 184          $this->assertFalse($grade_item->qualifies_for_regrading());
 185  
 186          $grade_item->iteminfo = 'Updated info for this unittest grade_item';
 187  
 188          $this->assertFalse($grade_item->qualifies_for_regrading());
 189  
 190          $grade_item->grademin = 14;
 191  
 192          $this->assertTrue($grade_item->qualifies_for_regrading());
 193      }
 194  
 195      protected function sub_test_grade_item_force_regrading() {
 196          $grade_item = new grade_item($this->grade_items[3], false); // Use a grade item not touched by previous tests.
 197          $this->assertTrue(method_exists($grade_item, 'force_regrading'));
 198  
 199          $this->assertEquals(0, $grade_item->needsupdate);
 200  
 201          $grade_item->force_regrading();
 202          $this->assertEquals(1, $grade_item->needsupdate);
 203          $grade_item->update_from_db();
 204          $this->assertEquals(1, $grade_item->needsupdate);
 205      }
 206  
 207      protected function sub_test_grade_item_fetch() {
 208          $grade_item = new grade_item();
 209          $this->assertTrue(method_exists($grade_item, 'fetch'));
 210  
 211          // Not using $this->grade_items[0] as it's iteminfo was modified by sub_test_grade_item_qualifies_for_regrading().
 212          $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
 213          $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
 214          $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
 215  
 216          $grade_item = grade_item::fetch(array('itemtype'=>$this->grade_items[1]->itemtype, 'itemmodule'=>$this->grade_items[1]->itemmodule));
 217          $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
 218          $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
 219      }
 220  
 221      protected function sub_test_grade_item_fetch_all() {
 222          $grade_item = new grade_item();
 223          $this->assertTrue(method_exists($grade_item, 'fetch_all'));
 224  
 225          $grade_items = grade_item::fetch_all(array('courseid'=>$this->courseid));
 226          $this->assertEquals(count($this->grade_items), count($grade_items)-1); // -1 to account for the course grade item.
 227      }
 228  
 229      // Retrieve all final scores for a given grade_item.
 230      protected function sub_test_grade_item_get_all_finals() {
 231          $grade_item = new grade_item($this->grade_items[0], false);
 232          $this->assertTrue(method_exists($grade_item, 'get_final'));
 233  
 234          $final_grades = $grade_item->get_final();
 235          $this->assertEquals(3, count($final_grades));
 236      }
 237  
 238  
 239      // Retrieve all final scores for a specific userid.
 240      protected function sub_test_grade_item_get_final() {
 241          $grade_item = new grade_item($this->grade_items[0], false);
 242          $this->assertTrue(method_exists($grade_item, 'get_final'));
 243          $final_grade = $grade_item->get_final($this->user[1]->id);
 244          $this->assertEquals($this->grade_grades[0]->finalgrade, $final_grade->finalgrade);
 245      }
 246  
 247      protected function sub_test_grade_item_get_sortorder() {
 248          $grade_item = new grade_item($this->grade_items[0], false);
 249          $this->assertTrue(method_exists($grade_item, 'get_sortorder'));
 250          $sortorder = $grade_item->get_sortorder();
 251          $this->assertEquals($this->grade_items[0]->sortorder, $sortorder);
 252      }
 253  
 254      protected function sub_test_grade_item_set_sortorder() {
 255          $grade_item = new grade_item($this->grade_items[0], false);
 256          $this->assertTrue(method_exists($grade_item, 'set_sortorder'));
 257          $grade_item->set_sortorder(999);
 258          $this->assertEquals($grade_item->sortorder, 999);
 259      }
 260  
 261      protected function sub_test_grade_item_move_after_sortorder() {
 262          $grade_item = new grade_item($this->grade_items[0], false);
 263          $this->assertTrue(method_exists($grade_item, 'move_after_sortorder'));
 264          $grade_item->move_after_sortorder(5);
 265          $this->assertEquals($grade_item->sortorder, 6);
 266  
 267          $grade_item = grade_item::fetch(array('id'=>$this->grade_items[0]->id));
 268          $this->assertEquals($grade_item->sortorder, 6);
 269  
 270          $after = grade_item::fetch(array('id'=>$this->grade_items[6]->id));
 271          $this->assertEquals($after->sortorder, 8);
 272      }
 273  
 274      protected function sub_test_grade_item_get_name() {
 275          $grade_item = new grade_item($this->grade_items[0], false);
 276          $this->assertTrue(method_exists($grade_item, 'get_name'));
 277  
 278          $name = $grade_item->get_name();
 279          $this->assertEquals($this->grade_items[0]->itemname, $name);
 280      }
 281  
 282      protected function sub_test_grade_item_set_parent() {
 283          $grade_item = new grade_item($this->grade_items[0], false);
 284          $this->assertTrue(method_exists($grade_item, 'set_parent'));
 285  
 286          $old = $grade_item->get_parent_category();
 287          $new = new grade_category($this->grade_categories[3], false);
 288          $new_item = $new->get_grade_item();
 289  
 290          $this->assertTrue($grade_item->set_parent($new->id));
 291  
 292          $new_item->update_from_db();
 293          $grade_item->update_from_db();
 294  
 295          $this->assertEquals($grade_item->categoryid, $new->id);
 296      }
 297  
 298      protected function sub_test_grade_item_get_parent_category() {
 299          $grade_item = new grade_item($this->grade_items[0], false);
 300          $this->assertTrue(method_exists($grade_item, 'get_parent_category'));
 301  
 302          $category = $grade_item->get_parent_category();
 303          $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
 304      }
 305  
 306      protected function sub_test_grade_item_load_parent_category() {
 307          $grade_item = new grade_item($this->grade_items[0], false);
 308          $this->assertTrue(method_exists($grade_item, 'load_parent_category'));
 309  
 310          $category = $grade_item->load_parent_category();
 311          $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
 312          $this->assertEquals($this->grade_categories[1]->fullname, $grade_item->parent_category->fullname);
 313      }
 314  
 315      protected function sub_test_grade_item_get_item_category() {
 316          $grade_item = new grade_item($this->grade_items[3], false);
 317          $this->assertTrue(method_exists($grade_item, 'get_item_category'));
 318  
 319          $category = $grade_item->get_item_category();
 320          $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
 321      }
 322  
 323      protected function sub_test_grade_item_load_item_category() {
 324          $grade_item = new grade_item($this->grade_items[3], false);
 325          $this->assertTrue(method_exists($grade_item, 'load_item_category'));
 326  
 327          $category = $grade_item->load_item_category();
 328          $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
 329          $this->assertEquals($this->grade_categories[0]->fullname, $grade_item->item_category->fullname);
 330      }
 331  
 332      protected function sub_test_grade_item_regrade_final_grades() {
 333          $grade_item = new grade_item($this->grade_items[0], false);
 334          $this->assertTrue(method_exists($grade_item, 'regrade_final_grades'));
 335          $this->assertEquals(true, $grade_item->regrade_final_grades());
 336          // TODO: add more tests.
 337      }
 338  
 339      protected function sub_test_grade_item_adjust_raw_grade() {
 340          $grade_item = new grade_item($this->grade_items[2], false); // Anything but assignment module!
 341          $this->assertTrue(method_exists($grade_item, 'adjust_raw_grade'));
 342  
 343          $grade_raw = new stdClass();
 344          $grade_raw->rawgrade = 40;
 345          $grade_raw->grademax = 100;
 346          $grade_raw->grademin = 0;
 347  
 348          $grade_item->gradetype = GRADE_TYPE_VALUE;
 349          $grade_item->multfactor = 1;
 350          $grade_item->plusfactor = 0;
 351          $grade_item->grademax = 50;
 352          $grade_item->grademin = 0;
 353  
 354          $original_grade_raw  = clone($grade_raw);
 355          $original_grade_item = clone($grade_item);
 356  
 357          $this->assertEquals(20, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 358  
 359          // Try a larger maximum grade.
 360          $grade_item->grademax = 150;
 361          $grade_item->grademin = 0;
 362          $this->assertEquals(60, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 363  
 364          // Try larger minimum grade.
 365          $grade_item->grademin = 50;
 366  
 367          $this->assertEquals(90, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 368  
 369          // Rescaling from a small scale (0-50) to a larger scale (0-100).
 370          $grade_raw->grademax = 50;
 371          $grade_raw->grademin = 0;
 372          $grade_item->grademax = 100;
 373          $grade_item->grademin = 0;
 374  
 375          $this->assertEquals(80, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 376  
 377          // Rescaling from a small scale (0-50) to a larger scale with offset (40-100).
 378          $grade_item->grademax = 100;
 379          $grade_item->grademin = 40;
 380  
 381          $this->assertEquals(88, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 382  
 383          // Try multfactor and plusfactor.
 384          $grade_raw = clone($original_grade_raw);
 385          $grade_item = clone($original_grade_item);
 386          $grade_item->multfactor = 1.23;
 387          $grade_item->plusfactor = 3;
 388  
 389          $this->assertEquals(27.6, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 390  
 391          // Try multfactor below 0 and a negative plusfactor.
 392          $grade_raw = clone($original_grade_raw);
 393          $grade_item = clone($original_grade_item);
 394          $grade_item->multfactor = 0.23;
 395          $grade_item->plusfactor = -3;
 396  
 397          $this->assertEquals(round(1.6), round($grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax)));
 398      }
 399  
 400      protected function sub_test_grade_item_rescale_grades_keep_percentage() {
 401          global $DB;
 402          $gradeitem = new grade_item($this->grade_items[10], false); // 10 is the manual grade item.
 403  
 404          // Create some grades to go with the grade item.
 405          $gradeids = array();
 406          $grade = new stdClass();
 407          $grade->itemid = $gradeitem->id;
 408          $grade->userid = $this->user[2]->id;
 409          $grade->finalgrade = 10;
 410          $grade->rawgrademax = $gradeitem->grademax;
 411          $grade->rawgrademin = $gradeitem->grademin;
 412          $grade->timecreated = time();
 413          $grade->timemodified = time();
 414          $gradeids[] = $DB->insert_record('grade_grades', $grade);
 415  
 416          $grade->userid = $this->user[3]->id;
 417          $grade->finalgrade = 50;
 418          $grade->rawgrademax = $gradeitem->grademax;
 419          $grade->rawgrademin = $gradeitem->grademin;
 420          $gradeids[] = $DB->insert_record('grade_grades', $grade);
 421  
 422          // Run the function.
 423          $gradeitem->grademax = 33;
 424          $gradeitem->grademin = 3;
 425          $gradeitem->update();
 426          $gradeitem->rescale_grades_keep_percentage(0, 100, 3, 33, 'test');
 427  
 428          // Check that the grades were updated to match the grade item.
 429          $grade = $DB->get_record('grade_grades', array('id' => $gradeids[0]));
 430          $this->assertEquals($gradeitem->grademax, $grade->rawgrademax, 'Max grade mismatch', 0.0001);
 431          $this->assertEquals($gradeitem->grademin, $grade->rawgrademin, 'Min grade mismatch', 0.0001);
 432          $this->assertEquals(6, $grade->finalgrade, 'Min grade mismatch', 0.0001);
 433  
 434          $grade = $DB->get_record('grade_grades', array('id' => $gradeids[1]));
 435          $this->assertEquals($gradeitem->grademax, $grade->rawgrademax, 'Max grade mismatch', 0.0001);
 436          $this->assertEquals($gradeitem->grademin, $grade->rawgrademin, 'Min grade mismatch', 0.0001);
 437          $this->assertEquals(18, $grade->finalgrade, 'Min grade mismatch', 0.0001);
 438      }
 439  
 440      protected function sub_test_grade_item_set_locked() {
 441          // Getting a grade_item from the DB as set_locked() will fail if the grade items needs to be updated
 442          // also needs to have at least one grade_grade or $grade_item->get_final(1) returns null.
 443          // $grade_item = new grade_item($this->grade_items[8]);
 444          $grade_item = grade_item::fetch(array('id'=>$this->grade_items[8]->id));
 445  
 446          $this->assertTrue(method_exists($grade_item, 'set_locked'));
 447  
 448          $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 449          $this->assertTrue(empty($grade_item->locked));// Not locked.
 450          $this->assertTrue(empty($grade_grade->locked));// Not locked.
 451  
 452          $this->assertTrue($grade_item->set_locked(true, true, false));
 453          $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 454  
 455          $this->assertFalse(empty($grade_item->locked));// Locked.
 456          $this->assertFalse(empty($grade_grade->locked)); // Individual grades should be locked too.
 457  
 458          $this->assertTrue($grade_item->set_locked(false, true, false));
 459          $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 460  
 461          $this->assertTrue(empty($grade_item->locked));
 462          $this->assertTrue(empty($grade->locked)); // Individual grades should be unlocked too.
 463      }
 464  
 465      protected function sub_test_grade_item_is_locked() {
 466          $grade_item = new grade_item($this->grade_items[10], false);
 467          $this->assertTrue(method_exists($grade_item, 'is_locked'));
 468  
 469          $this->assertFalse($grade_item->is_locked());
 470          $this->assertFalse($grade_item->is_locked($this->user[1]->id));
 471          $this->assertTrue($grade_item->set_locked(true, true, false));
 472          $this->assertTrue($grade_item->is_locked());
 473          $this->assertTrue($grade_item->is_locked($this->user[1]->id));
 474      }
 475  
 476      protected function sub_test_grade_item_set_hidden() {
 477          $grade_item = new grade_item($this->grade_items[0], false);
 478          $this->assertTrue(method_exists($grade_item, 'set_hidden'));
 479  
 480          $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 481          $this->assertEquals(0, $grade_item->hidden);
 482          $this->assertEquals(0, $grade->hidden);
 483  
 484          $grade_item->set_hidden(666, true);
 485          $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 486  
 487          $this->assertEquals(666, $grade_item->hidden);
 488          $this->assertEquals(666, $grade->hidden);
 489      }
 490  
 491      protected function sub_test_grade_item_is_hidden() {
 492          $grade_item = new grade_item($this->grade_items[0], false);
 493          $this->assertTrue(method_exists($grade_item, 'is_hidden'));
 494  
 495          $this->assertFalse($grade_item->is_hidden());
 496          $this->assertFalse($grade_item->is_hidden(1));
 497  
 498          $grade_item->set_hidden(1);
 499          $this->assertTrue($grade_item->is_hidden());
 500          $this->assertTrue($grade_item->is_hidden(1));
 501  
 502          $grade_item->set_hidden(666);
 503          $this->assertFalse($grade_item->is_hidden());
 504          $this->assertFalse($grade_item->is_hidden(1));
 505  
 506          $grade_item->set_hidden(time()+666);
 507          $this->assertTrue($grade_item->is_hidden());
 508          $this->assertTrue($grade_item->is_hidden(1));
 509      }
 510  
 511      protected function sub_test_grade_item_is_category_item() {
 512          $grade_item = new grade_item($this->grade_items[3], false);
 513          $this->assertTrue(method_exists($grade_item, 'is_category_item'));
 514          $this->assertTrue($grade_item->is_category_item());
 515      }
 516  
 517      protected function sub_test_grade_item_is_course_item() {
 518          $grade_item = grade_item::fetch_course_item($this->courseid);
 519          $this->assertTrue(method_exists($grade_item, 'is_course_item'));
 520          $this->assertTrue($grade_item->is_course_item());
 521      }
 522  
 523      protected function sub_test_grade_item_fetch_course_item() {
 524          $grade_item = grade_item::fetch_course_item($this->courseid);
 525          $this->assertTrue(method_exists($grade_item, 'fetch_course_item'));
 526          $this->assertEquals($grade_item->itemtype, 'course');
 527      }
 528  
 529      protected function sub_test_grade_item_depends_on() {
 530          global $CFG;
 531  
 532          $origenableoutcomes = $CFG->enableoutcomes;
 533          $CFG->enableoutcomes = 0;
 534          $grade_item = new grade_item($this->grade_items[1], false);
 535  
 536          // Calculated grade dependency.
 537          $deps = $grade_item->depends_on();
 538          sort($deps, SORT_NUMERIC); // For comparison.
 539          $this->assertEquals(array($this->grade_items[0]->id), $deps);
 540  
 541          // Simulate depends on returns none when locked.
 542          $grade_item->locked = time();
 543          $grade_item->update();
 544          $deps = $grade_item->depends_on();
 545          sort($deps, SORT_NUMERIC); // For comparison.
 546          $this->assertEquals(array(), $deps);
 547  
 548          // Category dependency.
 549          $grade_item = new grade_item($this->grade_items[3], false);
 550          $deps = $grade_item->depends_on();
 551          sort($deps, SORT_NUMERIC); // For comparison.
 552          $res = array($this->grade_items[4]->id, $this->grade_items[5]->id);
 553          $this->assertEquals($res, $deps);
 554      }
 555  
 556      protected function scales_outcomes_test_grade_item_depends_on() {
 557          $CFG->enableoutcomes = 1;
 558          $origgradeincludescalesinaggregation = $CFG->grade_includescalesinaggregation;
 559          $CFG->grade_includescalesinaggregation = 1;
 560  
 561          // Scale item in category with $CFG->grade_includescalesinaggregation = 1.
 562          $grade_item = new grade_item($this->grade_items[14], false);
 563          $deps = $grade_item->depends_on();
 564          sort($deps, SORT_NUMERIC);
 565          $res = array($this->grade_items[16]->id);
 566          $this->assertEquals($res, $deps);
 567  
 568          // Scale item in category with $CFG->grade_includescalesinaggregation = 0.
 569          $CFG->grade_includescalesinaggregation = 0;
 570          $grade_item = new grade_item($this->grade_items[14], false);
 571          $deps = $grade_item->depends_on();
 572          $res = array();
 573          $this->assertEquals($res, $deps);
 574          $CFG->grade_includescalesinaggregation = 1;
 575  
 576          // Outcome item in category with outcomes disabled.
 577          $CFG->enableoutcomes = 0;
 578          $grade_item = new grade_item($this->grade_items[14], false);
 579          $deps = $grade_item->depends_on();
 580          sort($deps, SORT_NUMERIC);
 581          $res = array($this->grade_items[16]->id, $this->grade_items[17]->id);
 582          $this->assertEquals($res, $deps);
 583  
 584          $CFG->enableoutcomes = $origenableoutcomes;
 585          $CFG->grade_includescalesinaggregation = $origgradeincludescalesinaggregation;
 586      }
 587  
 588      protected function sub_test_refresh_grades() {
 589          // Testing with the grade item for a mod_assignment instance.
 590          $grade_item = new grade_item($this->grade_items[0], false);
 591          $this->assertTrue(method_exists($grade_item, 'refresh_grades'));
 592          $this->assertTrue($grade_item->refresh_grades());
 593  
 594          // Break the grade item and check error handling.
 595          $grade_item->iteminstance = 123456789;
 596          $this->assertFalse($grade_item->refresh_grades());
 597          $this->assertDebuggingCalled();
 598      }
 599  
 600      protected function sub_test_grade_item_is_calculated() {
 601          $grade_item = new grade_item($this->grade_items[1], false);
 602          $this->assertTrue(method_exists($grade_item, 'is_calculated'));
 603          $this->assertTrue($grade_item->is_calculated());
 604  
 605          $grade_item = new grade_item($this->grade_items[0], false);
 606          $this->assertFalse($grade_item->is_calculated());
 607      }
 608  
 609      protected function sub_test_grade_item_set_calculation() {
 610          $grade_item = new grade_item($this->grade_items[1], false);
 611          $this->assertTrue(method_exists($grade_item, 'set_calculation'));
 612          $grade_itemsource = new grade_item($this->grade_items[0], false);
 613  
 614          $grade_item->set_calculation('=[['.$grade_itemsource->idnumber.']]');
 615  
 616          $this->assertTrue(!empty($grade_item->needsupdate));
 617          $this->assertEquals('=##gi'.$grade_itemsource->id.'##', $grade_item->calculation);
 618      }
 619  
 620      protected function sub_test_grade_item_get_calculation() {
 621          $grade_item = new grade_item($this->grade_items[1], false);
 622          $this->assertTrue(method_exists($grade_item, 'get_calculation'));
 623          $grade_itemsource = new grade_item($this->grade_items[0], false);
 624  
 625          $denormalizedformula = str_replace('##gi'.$grade_itemsource->id.'##', '[['.$grade_itemsource->idnumber.']]', $this->grade_items[1]->calculation);
 626  
 627          $formula = $grade_item->get_calculation();
 628          $this->assertTrue(!empty($grade_item->needsupdate));
 629          $this->assertEquals($denormalizedformula, $formula);
 630      }
 631  
 632      public function sub_test_grade_item_compute() {
 633          $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
 634          $this->assertTrue(method_exists($grade_item, 'compute'));
 635  
 636          // Check the grade_grades in the array match those in the DB then delete $this->grade_items[1]'s grade_grades.
 637          $this->grade_grades[3] = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
 638          $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
 639          $grade_grade->delete();
 640  
 641          $this->grade_grades[4] = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
 642          $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
 643          $grade_grade->delete();
 644  
 645          $this->grade_grades[5] = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
 646          $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
 647          $grade_grade->delete();
 648  
 649          // Recalculate the grades (its a calculation so pulls values from other grade_items) and reinsert them.
 650          $grade_item->compute();
 651  
 652          $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[3]->userid, 'itemid'=>$this->grade_grades[3]->itemid));
 653          $this->assertEquals($this->grade_grades[3]->finalgrade, $grade_grade->finalgrade);
 654  
 655          $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[4]->userid, 'itemid'=>$this->grade_grades[4]->itemid));
 656          $this->assertEquals($this->grade_grades[4]->finalgrade, $grade_grade->finalgrade);
 657  
 658          $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[5]->userid, 'itemid'=>$this->grade_grades[5]->itemid));
 659          $this->assertEquals($this->grade_grades[5]->finalgrade, $grade_grade->finalgrade);
 660      }
 661  
 662      protected function sub_test_update_final_grade() {
 663  
 664          // MDL-31713 Check that min and max are set on the grade_grade instance
 665          // if the grade is overridden before the activity has supplied a grade.
 666          $min = 2;
 667          $max = 8;
 668  
 669          // Create a brand new grade item.
 670          $grade_item = new grade_item();
 671          $this->assertTrue(method_exists($grade_item, 'insert'));
 672  
 673          $grade_item->courseid = $this->courseid;
 674          $grade_item->categoryid = $this->grade_categories[1]->id;
 675          $grade_item->itemname = 'brand new unit test grade item';
 676          $grade_item->itemtype = 'mod';
 677          $grade_item->itemmodule = 'quiz';
 678          $grade_item->iteminfo = 'Grade item used for unit testing';
 679          $grade_item->iteminstance = $this->activities[7]->id;
 680          $grade_item->grademin = $min;
 681          $grade_item->grademax = $max;
 682          $grade_item->insert();
 683  
 684          // Override the student grade.
 685          $grade_item->update_final_grade($this->user[1]->id, 7, 'gradebook', '', FORMAT_MOODLE);
 686  
 687          // Check the student's grade has the correct min and max grade.
 688          $grade_grade = grade_grade::fetch(array('userid'=>$this->user[1]->id, 'itemid'=>$grade_item->id));
 689          $this->assertEquals($min, $grade_grade->rawgrademin);
 690          $this->assertEquals($max, $grade_grade->rawgrademax);
 691      }
 692  
 693      protected function sub_test_grade_item_can_control_visibility() {
 694          // Grade item 0 == Course module 0 == Assignment.
 695          $grade_item = new grade_item($this->grade_items[0], false);
 696          $this->assertTrue($grade_item->can_control_visibility());
 697  
 698          // Grade item  == Course module 7 == Quiz.
 699          $grade_item = new grade_item($this->grade_items[11], false);
 700          $this->assertFalse($grade_item->can_control_visibility());
 701      }
 702  
 703      /**
 704       * Test the {@link grade_item::fix_duplicate_sortorder() function with
 705       * faked duplicate sortorder data.
 706       */
 707      public function sub_test_grade_item_fix_sortorder() {
 708          global $DB;
 709  
 710          $this->resetAfterTest(true);
 711  
 712          // Each set is used for filling the db with fake data and will be representing the result of query:
 713          // "SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id".
 714          $testsets = array(
 715              // Items that need no action.
 716              array(1,2,3),
 717              array(5,6,7),
 718              array(7,6,1,3,2,5),
 719              // Items with sortorder duplicates
 720              array(1,2,2,3,3,4,5),
 721              // Only one sortorder duplicate.
 722              array(1,1),
 723              array(3,3),
 724              // Non-sequential sortorders with one or multiple duplicates.
 725              array(3,3,7,5,6,6,9,10,8,3),
 726              array(7,7,3),
 727              array(3,4,5,3,5,4,7,1)
 728          );
 729          $origsequence = array();
 730  
 731          // Generate the data and remember the initial sequence or items.
 732          foreach ($testsets as $testset) {
 733              $course = $this->getDataGenerator()->create_course();
 734              foreach ($testset as $sortorder) {
 735                  $this->insert_fake_grade_item_sortorder($course->id, $sortorder);
 736              }
 737              $DB->get_records('grade_items');
 738              $origsequence[$course->id] = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
 739                  "WHERE courseid = ? ORDER BY sortorder, id", array($course->id));
 740          }
 741  
 742          $duplicatedetectionsql = "SELECT courseid, sortorder
 743                                      FROM {grade_items}
 744                                  WHERE courseid = :courseid
 745                                  GROUP BY courseid, sortorder
 746                                    HAVING COUNT(id) > 1";
 747  
 748          // Do the work.
 749          foreach ($origsequence as $courseid => $ignore) {
 750              grade_item::fix_duplicate_sortorder($courseid);
 751              // Verify that no duplicates are left in the database.
 752              $dupes = $DB->record_exists_sql($duplicatedetectionsql, array('courseid' => $courseid));
 753              $this->assertFalse($dupes);
 754          }
 755  
 756          // Verify that sequences are exactly the same as they were before upgrade script.
 757          $idx = 0;
 758          foreach ($origsequence as $courseid => $sequence) {
 759              if (count(($testsets[$idx])) == count(array_unique($testsets[$idx]))) {
 760                  // If there were no duplicates for this course verify that sortorders are not modified.
 761                  $newsortorders = $DB->get_fieldset_sql("SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id", array($courseid));
 762                  $this->assertEquals($testsets[$idx], $newsortorders);
 763              }
 764              $newsequence = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
 765                  "WHERE courseid = ? ORDER BY sortorder, id", array($courseid));
 766              $this->assertEquals($sequence, $newsequence,
 767                      "Sequences do not match for test set $idx : ".join(',', $testsets[$idx]));
 768              $idx++;
 769          }
 770      }
 771  
 772      /**
 773       * Populate some fake grade items into the database with specified
 774       * sortorder and course id.
 775       *
 776       * NOTE: This function doesn't make much attempt to respect the
 777       * gradebook internals, its simply used to fake some data for
 778       * testing the upgradelib function. Please don't use it for other
 779       * purposes.
 780       *
 781       * @param int $courseid id of course
 782       * @param int $sortorder numeric sorting order of item
 783       * @return stdClass grade item object from the database.
 784       */
 785      private function insert_fake_grade_item_sortorder($courseid, $sortorder) {
 786          global $DB, $CFG;
 787          require_once($CFG->libdir.'/gradelib.php');
 788  
 789          $item = new stdClass();
 790          $item->courseid = $courseid;
 791          $item->sortorder = $sortorder;
 792          $item->gradetype = GRADE_TYPE_VALUE;
 793          $item->grademin = 30;
 794          $item->grademax = 110;
 795          $item->itemnumber = 1;
 796          $item->iteminfo = '';
 797          $item->timecreated = time();
 798          $item->timemodified = time();
 799  
 800          $item->id = $DB->insert_record('grade_items', $item);
 801  
 802          return $DB->get_record('grade_items', array('id' => $item->id));
 803      }
 804  
 805      public function test_set_aggregation_fields_for_aggregation() {
 806          $course = $this->getDataGenerator()->create_course();
 807          $gi = new grade_item(array('courseid' => $course->id, 'itemtype' => 'manual'), false);
 808  
 809          $methods = array(GRADE_AGGREGATE_MEAN, GRADE_AGGREGATE_MEDIAN, GRADE_AGGREGATE_MIN, GRADE_AGGREGATE_MAX,
 810              GRADE_AGGREGATE_MODE, GRADE_AGGREGATE_WEIGHTED_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN2,
 811              GRADE_AGGREGATE_EXTRACREDIT_MEAN, GRADE_AGGREGATE_SUM);
 812  
 813          // Switching from and to the same aggregation using the defaults.
 814          foreach ($methods as $method) {
 815              $defaults = grade_category::get_default_aggregation_coefficient_values($method);
 816              $gi->aggregationcoef = $defaults['aggregationcoef'];
 817              $gi->aggregationcoef2 = $defaults['aggregationcoef2'];
 818              $gi->weightoverride = $defaults['weightoverride'];
 819              $this->assertFalse($gi->set_aggregation_fields_for_aggregation($method, $method));
 820              $this->assertEquals($defaults['aggregationcoef'], $gi->aggregationcoef);
 821              $this->assertEquals($defaults['aggregationcoef2'], $gi->aggregationcoef2);
 822              $this->assertEquals($defaults['weightoverride'], $gi->weightoverride);
 823          }
 824  
 825          // Extra credit is kept across aggregation methods that support it.
 826          foreach ($methods as $from) {
 827              $fromsupportsec = grade_category::aggregation_uses_extracredit($from);
 828              $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 829  
 830              foreach ($methods as $to) {
 831                  $tosupportsec = grade_category::aggregation_uses_extracredit($to);
 832                  $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 833  
 834                  // Set the item to be extra credit, if supported.
 835                  if ($fromsupportsec) {
 836                      $gi->aggregationcoef = 1;
 837                  } else {
 838                      $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 839                  }
 840  
 841                  // We ignore those fields, we know it is never used for extra credit.
 842                  $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
 843                  $gi->weightoverride = $todefaults['weightoverride'];
 844  
 845                  if ($fromsupportsec && $tosupportsec) {
 846                      $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 847                      $this->assertEquals(1, $gi->aggregationcoef);
 848  
 849                  } else if ($fromsupportsec && !$tosupportsec) {
 850                      if ($to == GRADE_AGGREGATE_WEIGHTED_MEAN) {
 851                          // Special case, aggregationcoef is used but for weights.
 852                          $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 853                          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 854                      } else {
 855                          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 856                          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 857                      }
 858                  } else {
 859                      // The source does not support extra credit, everything will be reset.
 860                      if (($from == GRADE_AGGREGATE_WEIGHTED_MEAN || $to == GRADE_AGGREGATE_WEIGHTED_MEAN) && $from != $to) {
 861                          // Special case, aggregationcoef is used but for weights.
 862                          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 863                          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 864                      } else {
 865                          $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 866                          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 867                      }
 868                  }
 869              }
 870          }
 871  
 872          // Extra credit can be higher than one for GRADE_AGGREGATE_EXTRACREDIT_MEAN, but will be normalised for others.
 873          $from = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
 874          $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 875  
 876          foreach ($methods as $to) {
 877              if (!grade_category::aggregation_uses_extracredit($to)) {
 878                  continue;
 879              }
 880  
 881              $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 882              $gi->aggregationcoef = 8;
 883  
 884              // Ignore those fields, they are not used for extra credit.
 885              $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
 886              $gi->weightoverride = $todefaults['weightoverride'];
 887  
 888              if ($to == $from) {
 889                  $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 890                  $this->assertEquals(8, $gi->aggregationcoef);
 891              } else {
 892                  $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 893                  $this->assertEquals(1, $gi->aggregationcoef);
 894              }
 895          }
 896  
 897          // Weights are reset.
 898          $from = GRADE_AGGREGATE_SUM;
 899          $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 900  
 901          $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 902          $gi->aggregationcoef2 = 0.321;
 903          $gi->weightoverride = $fromdefaults['weightoverride'];
 904  
 905          $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
 906          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 907  
 908          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 909          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 910          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 911          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 912  
 913          $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 914          $gi->aggregationcoef2 = 0.321;
 915          $gi->weightoverride = $fromdefaults['weightoverride'];
 916  
 917          $to = GRADE_AGGREGATE_SUM;
 918          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 919  
 920          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 921          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 922          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 923          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 924  
 925          // Weight is kept when using SUM with weight override.
 926          $from = GRADE_AGGREGATE_SUM;
 927          $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 928  
 929          $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 930          $gi->aggregationcoef2 = 0.321;
 931          $gi->weightoverride = 1;
 932  
 933          $to = GRADE_AGGREGATE_SUM;
 934          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 935  
 936          $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 937          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 938          $this->assertEquals(0.321, $gi->aggregationcoef2);
 939          $this->assertEquals(1, $gi->weightoverride);
 940  
 941          $gi->aggregationcoef2 = 0.321;
 942          $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 943          $gi->weightoverride = 1;
 944  
 945          $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
 946          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 947  
 948          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 949          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 950          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 951          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 952  
 953          // Weight is kept when staying in weighted mean.
 954          $from = GRADE_AGGREGATE_WEIGHTED_MEAN;
 955          $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 956  
 957          $gi->aggregationcoef = 18;
 958          $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
 959          $gi->weightoverride = $fromdefaults['weightoverride'];
 960  
 961          $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
 962          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 963  
 964          $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 965          $this->assertEquals(18, $gi->aggregationcoef);
 966          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 967          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 968  
 969          $gi->aggregationcoef = 18;
 970          $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
 971          $gi->weightoverride = $fromdefaults['weightoverride'];
 972  
 973          $to = GRADE_AGGREGATE_SUM;
 974          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 975  
 976          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 977          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 978          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 979          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 980      }
 981  
 982      /**
 983       * Test that grade item event triggered when a grade item is created.
 984       */
 985      protected function sub_test_grade_item_created_event() {
 986          $sink = $this->redirectEvents();
 987  
 988          $gradeitem = new grade_item();
 989  
 990          $gradeitem->courseid = $this->courseid;
 991          $gradeitem->categoryid = $this->grade_categories[1]->id;
 992          $gradeitem->itemname = 'unittestgradeitem4';
 993          $gradeitem->itemtype = 'mod';
 994          $gradeitem->itemmodule = 'quiz';
 995          $gradeitem->iteminfo = 'Grade item used for unit testing';
 996  
 997          $gradeitem->insert();
 998  
 999          $result = $sink->get_events();
1000          $sink->close();
1001  
1002          $this->assertCount(1, $result);
1003  
1004          $event = reset($result);
1005          $this->assertEventContextNotUsed($event);
1006          $this->assertInstanceOf('core\event\grade_item_created', $event);
1007  
1008          $eventgradeitem = $event->get_grade_item();
1009  
1010          $this->assertInstanceOf('grade_item', $eventgradeitem);
1011          $this->assertEquals($gradeitem->id, $eventgradeitem->id);
1012          $this->assertEquals($gradeitem->itemname, $event->other['itemname']);
1013          $this->assertEquals($gradeitem->itemtype, $event->other['itemtype']);
1014          $this->assertEquals($gradeitem->itemmodule, $event->other['itemmodule']);
1015      }
1016  
1017      /**
1018       * Test that grade item event triggered when a grade item is updated.
1019       */
1020      protected function sub_test_grade_item_updated_event() {
1021          $gradeitem = new grade_item();
1022  
1023          $gradeitem->courseid = $this->courseid;
1024          $gradeitem->categoryid = $this->grade_categories[1]->id;
1025          $gradeitem->itemname = 'unittestgradeitem4';
1026          $gradeitem->itemtype = 'mod';
1027          $gradeitem->itemmodule = 'quiz';
1028          $gradeitem->iteminfo = 'Grade item used for unit testing';
1029          $gradeitem->insert();
1030  
1031          $sink = $this->redirectEvents();
1032  
1033          $gradeitem->itemname = 'updatedname';
1034          $gradeitem->update();
1035  
1036          $result = $sink->get_events();
1037          $sink->close();
1038  
1039          $this->assertCount(1, $result);
1040  
1041          $event = reset($result);
1042          $this->assertInstanceOf('core\event\grade_item_updated', $event);
1043          $this->assertEventContextNotUsed($event);
1044  
1045          $eventgradeitem = $event->get_grade_item();
1046  
1047          $this->assertInstanceOf('grade_item', $eventgradeitem);
1048          $this->assertEquals($gradeitem->id, $eventgradeitem->id);
1049          $this->assertEquals($gradeitem->itemtype, $event->other['itemtype']);
1050          $this->assertEquals($gradeitem->itemmodule, $event->other['itemmodule']);
1051          $this->assertEquals('updatedname', $event->other['itemname']);
1052      }
1053  
1054  
1055      /**
1056       * Test grade item duplication expecting success.
1057       */
1058      public function test_grade_duplicate_grade_item_success() {
1059          $cat = new grade_category();
1060          $cat->courseid = $this->courseid;
1061          $cat->fullname = 'Grade category';
1062          $cat->insert();
1063  
1064          // Method exists.
1065          $gi = new grade_item();
1066          $this->assertTrue(method_exists($gi, 'duplicate'));
1067  
1068          // Grade item is inserted and valid for duplication.
1069          $gi->courseid = $this->courseid;
1070          $gi->categoryid = $cat->id;
1071          $gi->itemtype = 'manual';
1072          $gi->itemname = 'Grade Item 1';
1073          $gi->idnumber = '1000';
1074          $gi->insert();
1075          $gi2 = $gi->duplicate();
1076  
1077          $this->assertEquals($gi->courseid, $gi2->courseid);
1078          $this->assertEquals($gi->categoryid, $gi2->categoryid);
1079          $this->assertEquals($gi->itemtype, $gi2->itemtype);
1080          $this->assertEquals($gi->gradetype, $gi2->gradetype);
1081          $this->assertEquals($gi->grademax, $gi2->grademax);
1082          $this->assertEquals($gi->grademin, $gi2->grademin);
1083          $this->assertEquals($gi->gradepass, $gi2->gradepass);
1084          $this->assertEquals($gi->display, $gi2->display);
1085          $this->assertEquals($gi->decimals, $gi2->decimals);
1086          $this->assertEquals($gi->hidden, $gi2->hidden);
1087          $this->assertEquals($gi->weightoverride, $gi2->weightoverride);
1088  
1089          $this->assertNotEquals($gi->id, $gi2->id);
1090          $this->assertNotEquals($gi->idnumber, $gi2->idnumber);
1091          $this->assertNotEquals($gi->sortorder, $gi2->sortorder);
1092          $this->assertNotEquals($gi->itemname, $gi2->itemname);
1093      }
1094  
1095      /**
1096       * Test grade item duplication exception expected with incomplete grade item.
1097       */
1098      public function test_grade_duplicate_grade_item_incomplete() {
1099          // Grade item is not valid because it is empty.
1100          $gi = new grade_item();
1101          $gi->courseid = $this->courseid;
1102          $this->expectException("moodle_exception");
1103          $gi2 = $gi->duplicate();
1104      }
1105  
1106      /**
1107       * Test grade item duplication exception expected because item must be in db.
1108       */
1109      public function test_grade_duplicate_grade_item_not_in_db() {
1110          $cat = new grade_category();
1111          $cat->courseid = $this->courseid;
1112          $cat->fullname = 'Grade category';
1113          $cat->insert();
1114  
1115          // Grade item is valid for insertion but is not inserted into db.
1116          // Duplicate method throws an exception.
1117          $gi = new grade_item();
1118          $gi->courseid = $this->courseid;
1119          $gi->categoryid = $cat->id;
1120          $gi->itemtype = 'manual';
1121          $gi->itemname = 'Grade Item 1';
1122          $gi->idnumber = '1000';
1123  
1124          $this->expectException("moodle_exception");
1125          $gi2 = $gi->duplicate();
1126      }
1127  }