Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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