Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core_grades;
  18  
  19  use grade_plugin_return;
  20  use grade_report_grader;
  21  use mod_quiz\quiz_settings;
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  global $CFG;
  26  require_once($CFG->dirroot.'/grade/lib.php');
  27  require_once($CFG->dirroot.'/grade/report/grader/lib.php');
  28  
  29  /**
  30   * Tests grade_report_grader (the grader report)
  31   *
  32   * @package  core_grades
  33   * @covers   \grade_report_grader
  34   * @category test
  35   * @copyright 2012 Andrew Davis
  36   * @license  http://www.gnu.org/copyleft/gpl.html GNU Public License
  37   */
  38  class report_graderlib_test extends \advanced_testcase {
  39  
  40      /**
  41       * Tests grade_report_grader::process_data()
  42       *
  43       * process_data() processes submitted grade and feedback data
  44       */
  45      public function test_process_data() {
  46          global $DB, $CFG;
  47  
  48          $this->resetAfterTest(true);
  49  
  50          $course = $this->getDataGenerator()->create_course();
  51  
  52          // Create and enrol a student.
  53          $student = $this->getDataGenerator()->create_user(array('username' => 'student_sam'));
  54          $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
  55          $this->getDataGenerator()->enrol_user($student->id, $course->id, $role->id);
  56  
  57          // Test with limited grades.
  58          $CFG->unlimitedgrades = 0;
  59  
  60          $forummax = 80;
  61          $forum1 = $this->getDataGenerator()->create_module('forum', array('assessed' => 1, 'scale' => $forummax, 'course' => $course->id));
  62          // Switch the stdClass instance for a grade item instance.
  63          $forum1 = \grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'forum', 'iteminstance' => $forum1->id, 'courseid' => $course->id));
  64  
  65          $report = $this->create_report($course);
  66          $testgrade = 60.00;
  67  
  68          $data = new \stdClass();
  69          $data->id = $course->id;
  70          $data->report = 'grader';
  71          $data->timepageload = time();
  72  
  73          $data->grade = array();
  74          $data->grade[$student->id] = array();
  75          $data->grade[$student->id][$forum1->id] = $testgrade;
  76  
  77          $warnings = $report->process_data($data);
  78          $this->assertEquals(count($warnings), 0);
  79  
  80          $studentgrade = \grade_grade::fetch(array('itemid' => $forum1->id, '' => $student->id));
  81          $this->assertEquals($studentgrade->finalgrade, $testgrade);
  82  
  83          // Grade above max. Should be pulled down to max.
  84          $toobig = 200.00;
  85          $data->grade[$student->id][$forum1->id] = $toobig;
  86          $data->timepageload = time();
  87          $warnings = $report->process_data($data);
  88          $this->assertEquals(count($warnings), 1);
  89  
  90          $studentgrade = \grade_grade::fetch(array('itemid' => $forum1->id, '' => $student->id));
  91          $this->assertEquals($studentgrade->finalgrade, $forummax);
  92  
  93          // Grade below min. Should be pulled up to min.
  94          $toosmall = -10.00;
  95          $data->grade[$student->id][$forum1->id] = $toosmall;
  96          $data->timepageload = time();
  97          $warnings = $report->process_data($data);
  98          $this->assertEquals(count($warnings), 1);
  99  
 100          $studentgrade = \grade_grade::fetch(array('itemid' => $forum1->id, '' => $student->id));
 101          $this->assertEquals($studentgrade->finalgrade, 0);
 102  
 103          // Test unlimited grades so we can give a student a grade about max.
 104          $CFG->unlimitedgrades = 1;
 105  
 106          $data->grade[$student->id][$forum1->id] = $toobig;
 107          $data->timepageload = time();
 108          $warnings = $report->process_data($data);
 109          $this->assertEquals(count($warnings), 0);
 110  
 111          $studentgrade = \grade_grade::fetch(array('itemid' => $forum1->id, '' => $student->id));
 112          $this->assertEquals($studentgrade->finalgrade, $toobig);
 113      }
 114  
 115      public function test_collapsed_preferences() {
 116          $this->resetAfterTest(true);
 117  
 118          $emptypreferences = array('aggregatesonly' => array(), 'gradesonly' => array());
 119  
 120          $user1 = $this->getDataGenerator()->create_user();
 121          $course1 = $this->getDataGenerator()->create_course();
 122          $course2 = $this->getDataGenerator()->create_course();
 123          $course3 = $this->getDataGenerator()->create_course();
 124  
 125          $this->setUser($user1);
 126  
 127          $report = $this->create_report($course1);
 128          $this->assertEquals($emptypreferences, $report->collapsed);
 129  
 130          // Validating preferences set/get for one course.
 131          $report->process_action('cg13', 'switch_minus');
 132          $report = $this->create_report($course1);
 133          $this->assertEquals(array(13), $report->collapsed['aggregatesonly']);
 134          $this->assertEmpty($report->collapsed['gradesonly']);
 135  
 136          $report->process_action('cg13', 'switch_plus');
 137          $report = $this->create_report($course1);
 138          $this->assertEmpty($report->collapsed['aggregatesonly']);
 139          $this->assertEquals(array(13), $report->collapsed['gradesonly']);
 140  
 141          $report->process_action('cg13', 'switch_whole');
 142          $report = $this->create_report($course1);
 143          $this->assertEquals($emptypreferences, $report->collapsed);
 144  
 145          // Validating preferences set/get for several courses.
 146  
 147          $course1cats = $course2cats = $course3cats = array();
 148          for ($i=0;$i<10;$i++) {
 149              $course1cats[] = $this->create_grade_category($course1)->id;
 150              $course2cats[] = $this->create_grade_category($course2)->id;
 151              $course3cats[] = $this->create_grade_category($course3)->id;
 152          }
 153  
 154          $report1 = $this->create_report($course1);
 155          foreach ($course1cats as $catid) {
 156              $report1->process_action('cg'.$catid, 'switch_minus');
 157          }
 158          $report2 = $this->create_report($course2);
 159          foreach ($course2cats as $catid) {
 160              $report2->process_action('cg'.$catid, 'switch_minus');
 161              $report2->process_action('cg'.$catid, 'switch_plus');
 162          }
 163          $report3 = $this->create_report($course3);
 164          foreach ($course3cats as $catid) {
 165              $report3->process_action('cg'.$catid, 'switch_minus');
 166              if (($i++)%2) {
 167                  $report3->process_action('cg'.$catid, 'switch_plus');
 168              }
 169          }
 170  
 171          $report1 = $this->create_report($course1);
 172          $this->assertEquals(10, count($report1->collapsed['aggregatesonly']));
 173          $this->assertEquals(0, count($report1->collapsed['gradesonly']));
 174          $report2 = $this->create_report($course2);
 175          $this->assertEquals(0, count($report2->collapsed['aggregatesonly']));
 176          $this->assertEquals(10, count($report2->collapsed['gradesonly']));
 177          $report3 = $this->create_report($course3);
 178          $this->assertEquals(5, count($report3->collapsed['aggregatesonly']));
 179          $this->assertEquals(5, count($report3->collapsed['gradesonly']));
 180  
 181          // Test upgrade script.
 182          // Combine data generated for user1 and set it in the old format for user2, Try to retrieve it and make sure it is converted.
 183  
 184          $user2 = $this->getDataGenerator()->create_user();
 185          $alldata = array(
 186              'aggregatesonly' => array_merge($report1->collapsed['aggregatesonly'], $report2->collapsed['aggregatesonly'], $report3->collapsed['aggregatesonly']),
 187              'gradesonly' => array_merge($report1->collapsed['gradesonly'], $report2->collapsed['gradesonly'], $report3->collapsed['gradesonly']),
 188          );
 189          set_user_preference('grade_report_grader_collapsed_categories', serialize($alldata), $user2);
 190  
 191          $this->setUser($user2);
 192          $convertedreport1 = $this->create_report($course1);
 193          $this->assertEquals($report1->collapsed, $convertedreport1->collapsed);
 194          $convertedreport2 = $this->create_report($course2);
 195          $this->assertEquals($report2->collapsed, $convertedreport2->collapsed);
 196          $convertedreport3 = $this->create_report($course3);
 197          $this->assertEquals($report3->collapsed, $convertedreport3->collapsed);
 198          // Make sure the old style user preference is removed now.
 199          $this->assertEmpty(get_user_preferences('grade_report_grader_collapsed_categories'));
 200  
 201          // Test overflowing the setting with non-existing categories (only validated if new setting size exceeds 1333 chars).
 202  
 203          $toobigvalue = $expectedvalue = $report1->collapsed;
 204          for ($i = 0; strlen(json_encode($toobigvalue)) < 1333; $i++) {
 205              $toobigvalue[($i < 7) ? 'gradesonly' : 'aggregatesonly'][] = $course1cats[9] + 1 + $i;
 206          }
 207          $lastvalue = array_pop($toobigvalue['gradesonly']);
 208          set_user_preference('grade_report_grader_collapsed_categories'.$course1->id, json_encode($toobigvalue));
 209  
 210          $report1 = $this->create_report($course1);
 211          $report1->process_action('cg'.$lastvalue, 'switch_minus');
 212  
 213          $report1 = $this->create_report($course1);
 214          $this->assertEquals($expectedvalue, $report1->collapsed);
 215  
 216          // Test overflowing the setting with existing categories.
 217  
 218          $toobigvalue = $report1->collapsed;
 219          for ($i = 0; strlen(json_encode($toobigvalue)) < 1333; $i++) {
 220              $catid = $this->create_grade_category($course1)->id;
 221              $toobigvalue[($i < 7) ? 'gradesonly' : 'aggregatesonly'][] = $catid;
 222          }
 223          $lastcatid = array_pop($toobigvalue['gradesonly']);
 224          set_user_preference('grade_report_grader_collapsed_categories'.$course1->id, json_encode($toobigvalue));
 225          $toobigvalue['aggregatesonly'][] = $lastcatid;
 226  
 227          $report1 = $this->create_report($course1);
 228          $report1->process_action('cg'.$lastcatid, 'switch_minus');
 229  
 230          // One last value should be removed from both arrays.
 231          $report1 = $this->create_report($course1);
 232          $this->assertEquals(count($toobigvalue['aggregatesonly']) - 1, count($report1->collapsed['aggregatesonly']));
 233          $this->assertEquals(count($toobigvalue['gradesonly']) - 1, count($report1->collapsed['gradesonly']));
 234      }
 235  
 236      /**
 237       * Test some special cases of the conversion from old preferences to new ones
 238       *
 239       * @covers \grade_report_grader::get_collapsed_preferences
 240       * @covers \grade_report_grader::filter_collapsed_categories
 241       */
 242      public function test_old_collapsed_preferences() {
 243          $this->resetAfterTest(true);
 244  
 245          $user1 = $this->getDataGenerator()->create_user();
 246          $course1 = $this->getDataGenerator()->create_course();
 247          $course2 = $this->getDataGenerator()->create_course();
 248          $course3 = $this->getDataGenerator()->create_course();
 249  
 250          $course1cats = $course2cats = $course3cats = [];
 251          for ($i = 0; $i < 10; $i++) {
 252              $course1cats[] = $this->create_grade_category($course1)->id;
 253              $course2cats[] = $this->create_grade_category($course2)->id;
 254              $course3cats[] = $this->create_grade_category($course3)->id;
 255          }
 256  
 257          $report1 = $this->create_report($course1);
 258          // Collapse all the cats in course1.
 259          foreach ($course1cats as $catid) {
 260              $report1->process_action('cg'. $catid, 'switch_minus');
 261          }
 262  
 263          // Expand all the cats in course2.
 264          $report2 = $this->create_report($course2);
 265          foreach ($course2cats as $catid) {
 266              $report2->process_action('cg'.$catid, 'switch_minus');
 267              $report2->process_action('cg'.$catid, 'switch_plus');
 268          }
 269  
 270          // Collapse odd cats and expand even cats in course3.
 271          $report3 = $this->create_report($course3);
 272          foreach ($course3cats as $catid) {
 273              $report3->process_action('cg'.$catid, 'switch_minus');
 274              if (($i++) % 2) {
 275                  $report3->process_action('cg'.$catid, 'switch_plus');
 276              }
 277          }
 278  
 279          $report1 = $this->create_report($course1);
 280          $this->assertEquals(10, count($report1->collapsed['aggregatesonly']));
 281          $this->assertEquals(0, count($report1->collapsed['gradesonly']));
 282          $report2 = $this->create_report($course2);
 283          $this->assertEquals(0, count($report2->collapsed['aggregatesonly']));
 284          $this->assertEquals(10, count($report2->collapsed['gradesonly']));
 285          $report3 = $this->create_report($course3);
 286          $this->assertEquals(5, count($report3->collapsed['aggregatesonly']));
 287          $this->assertEquals(5, count($report3->collapsed['gradesonly']));
 288  
 289          // Use the preferences generated for user1 and set it in the old format for other users.
 290  
 291          // User2: both gradesonly and aggregatesonly.
 292          $user2 = $this->getDataGenerator()->create_user();
 293          $alldata = [
 294              'gradesonly' => array_merge(
 295                  $report1->collapsed['gradesonly'],
 296                  $report2->collapsed['gradesonly'],
 297                  $report3->collapsed['gradesonly']),
 298              'aggregatesonly' => array_merge(
 299                  $report1->collapsed['aggregatesonly'],
 300                  $report2->collapsed['aggregatesonly'],
 301                  $report3->collapsed['aggregatesonly']),
 302          ];
 303          set_user_preference('grade_report_grader_collapsed_categories', serialize($alldata), $user2);
 304  
 305          $this->setUser($user2);
 306          $convertedreport1 = $this->create_report($course1);
 307          $this->assertEquals($report1->collapsed['gradesonly'], $convertedreport1->collapsed['gradesonly']);
 308          $this->assertEquals($report1->collapsed['aggregatesonly'], $convertedreport1->collapsed['aggregatesonly']);
 309          $newprefs1 = get_user_preferences('grade_report_grader_collapsed_categories' . $course1->id); // Also verify new prefs.
 310          $this->assertEquals($report1->collapsed['gradesonly'], json_decode($newprefs1, true)['gradesonly']);
 311          $this->assertEquals($report1->collapsed['aggregatesonly'], json_decode($newprefs1, true)['aggregatesonly']);
 312  
 313          $convertedreport2 = $this->create_report($course2);
 314          $this->assertEquals($report2->collapsed['gradesonly'], $convertedreport2->collapsed['gradesonly']);
 315          $this->assertEquals($report2->collapsed['aggregatesonly'], $convertedreport2->collapsed['aggregatesonly']);
 316          $newprefs2 = get_user_preferences('grade_report_grader_collapsed_categories' . $course2->id); // Also verify new prefs.
 317          $this->assertEquals($report2->collapsed['gradesonly'], json_decode($newprefs2, true)['gradesonly']);
 318          $this->assertEquals($report2->collapsed['aggregatesonly'], json_decode($newprefs2, true)['aggregatesonly']);
 319  
 320          $convertedreport3 = $this->create_report($course3);
 321          $this->assertEquals($report3->collapsed['gradesonly'], $convertedreport3->collapsed['gradesonly']);
 322          $this->assertEquals($report3->collapsed['aggregatesonly'], $convertedreport3->collapsed['aggregatesonly']);
 323          $newprefs3 = get_user_preferences('grade_report_grader_collapsed_categories' . $course3->id); // Also verify new prefs.
 324          $this->assertEquals($report3->collapsed['gradesonly'], json_decode($newprefs3, true)['gradesonly']);
 325          $this->assertEquals($report3->collapsed['aggregatesonly'], json_decode($newprefs3, true)['aggregatesonly']);
 326  
 327          // Make sure the old style user preference is removed now.
 328          $this->assertEmpty(get_user_preferences('grade_report_grader_collapsed_categories'));
 329  
 330          // User3: only gradesonly (missing aggregatesonly).
 331          $user3 = $this->getDataGenerator()->create_user();
 332          $alldata = [
 333              'gradesonly' => array_merge(
 334                  $report1->collapsed['gradesonly'],
 335                  $report2->collapsed['gradesonly'],
 336                  $report3->collapsed['gradesonly']),
 337          ];
 338          set_user_preference('grade_report_grader_collapsed_categories', serialize($alldata), $user3);
 339  
 340          $this->setUser($user3);
 341          $convertedreport1 = $this->create_report($course1);
 342          $this->assertEquals($report1->collapsed['gradesonly'], $convertedreport1->collapsed['gradesonly']);
 343          $this->assertEquals([], $convertedreport1->collapsed['aggregatesonly']);
 344          $newprefs1 = get_user_preferences('grade_report_grader_collapsed_categories' . $course1->id); // Also verify new prefs.
 345          $this->assertNull($newprefs1);
 346  
 347          $convertedreport2 = $this->create_report($course2);
 348          $this->assertEquals($report2->collapsed['gradesonly'], $convertedreport2->collapsed['gradesonly']);
 349          $this->assertEquals([], $convertedreport2->collapsed['aggregatesonly']);
 350          $newprefs2 = get_user_preferences('grade_report_grader_collapsed_categories' . $course2->id); // Also verify new prefs.
 351          $this->assertEquals($report2->collapsed['gradesonly'], json_decode($newprefs2, true)['gradesonly']);
 352          $this->assertEquals([], json_decode($newprefs2, true)['aggregatesonly']);
 353  
 354          $convertedreport3 = $this->create_report($course3);
 355          $this->assertEquals($report3->collapsed['gradesonly'], $convertedreport3->collapsed['gradesonly']);
 356          $this->assertEquals([], $convertedreport3->collapsed['aggregatesonly']);
 357          $newprefs3 = get_user_preferences('grade_report_grader_collapsed_categories' . $course3->id); // Also verify new prefs.
 358          $this->assertEquals($report3->collapsed['gradesonly'], json_decode($newprefs3, true)['gradesonly']);
 359          $this->assertEquals([], json_decode($newprefs3, true)['aggregatesonly']);
 360  
 361          // Make sure the old style user preference is removed now.
 362          $this->assertEmpty(get_user_preferences('grade_report_grader_collapsed_categories'));
 363  
 364          // User4: only aggregatesonly (missing gradesonly).
 365          $user4 = $this->getDataGenerator()->create_user();
 366          $alldata = [
 367              'aggregatesonly' => array_merge(
 368                  $report1->collapsed['aggregatesonly'],
 369                  $report2->collapsed['aggregatesonly'],
 370                  $report3->collapsed['aggregatesonly']),
 371          ];
 372          set_user_preference('grade_report_grader_collapsed_categories', serialize($alldata), $user4);
 373  
 374          $this->setUser($user4);
 375          $convertedreport1 = $this->create_report($course1);
 376          $this->assertEquals([], $convertedreport1->collapsed['gradesonly']);
 377          $this->assertEquals($report1->collapsed['aggregatesonly'], $convertedreport1->collapsed['aggregatesonly']);
 378          $newprefs1 = get_user_preferences('grade_report_grader_collapsed_categories' . $course1->id); // Also verify new prefs.
 379          $this->assertEquals([], json_decode($newprefs1, true)['gradesonly']);
 380          $this->assertEquals($report1->collapsed['aggregatesonly'], json_decode($newprefs1, true)['aggregatesonly']);
 381  
 382          $convertedreport2 = $this->create_report($course2);
 383          $this->assertEquals([], $convertedreport2->collapsed['gradesonly']);
 384          $this->assertEquals($report2->collapsed['aggregatesonly'], $convertedreport2->collapsed['aggregatesonly']);
 385          $newprefs2 = get_user_preferences('grade_report_grader_collapsed_categories' . $course2->id); // Also verify new prefs.
 386          $this->assertNull($newprefs2);
 387  
 388          $convertedreport3 = $this->create_report($course3);
 389          $this->assertEquals([], $convertedreport3->collapsed['gradesonly']);
 390          $this->assertEquals($report3->collapsed['aggregatesonly'], $convertedreport3->collapsed['aggregatesonly']);
 391          $newprefs3 = get_user_preferences('grade_report_grader_collapsed_categories' . $course3->id); // Also verify new prefs.
 392          $this->assertEquals([], json_decode($newprefs3, true)['gradesonly']);
 393          $this->assertEquals($report3->collapsed['aggregatesonly'], json_decode($newprefs3, true)['aggregatesonly']);
 394  
 395          // Make sure the old style user preference is removed now.
 396          $this->assertEmpty(get_user_preferences('grade_report_grader_collapsed_categories'));
 397  
 398          // User5: both missing gradesonly and aggregatesonly.
 399          $user5 = $this->getDataGenerator()->create_user();
 400          $alldata = [];
 401          set_user_preference('grade_report_grader_collapsed_categories', serialize($alldata), $user5);
 402  
 403          $this->setUser($user5);
 404          $convertedreport1 = $this->create_report($course1);
 405          $this->assertEquals([], $convertedreport1->collapsed['gradesonly']);
 406          $this->assertEquals([], $convertedreport1->collapsed['aggregatesonly']);
 407          $newprefs1 = get_user_preferences('grade_report_grader_collapsed_categories' . $course1->id); // Also verify new prefs.
 408          $this->assertNull($newprefs1);
 409  
 410          $convertedreport2 = $this->create_report($course2);
 411          $this->assertEquals([], $convertedreport2->collapsed['gradesonly']);
 412          $this->assertEquals([], $convertedreport2->collapsed['aggregatesonly']);
 413          $newprefs2 = get_user_preferences('grade_report_grader_collapsed_categories' . $course2->id); // Also verify new prefs.
 414          $this->assertNull($newprefs2);
 415  
 416          $convertedreport3 = $this->create_report($course3);
 417          $this->assertEquals([], $convertedreport3->collapsed['gradesonly']);
 418          $this->assertEquals([], $convertedreport3->collapsed['aggregatesonly']);
 419          $newprefs3 = get_user_preferences('grade_report_grader_collapsed_categories' . $course3->id); // Also verify new prefs.
 420          $this->assertNull($newprefs3);
 421  
 422          // Make sure the old style user preference is removed now.
 423          $this->assertEmpty(get_user_preferences('grade_report_grader_collapsed_categories'));
 424  
 425          // User6: both empty gradesonly and aggregatesonly.
 426          $user6 = $this->getDataGenerator()->create_user();
 427          $alldata = [
 428              'gradesonly' => [],
 429              'aggregatesonly' => []
 430          ];
 431          set_user_preference('grade_report_grader_collapsed_categories', serialize($alldata), $user6);
 432  
 433          $this->setUser($user6);
 434          $convertedreport1 = $this->create_report($course1);
 435          $this->assertEquals([], $convertedreport1->collapsed['gradesonly']);
 436          $this->assertEquals([], $convertedreport1->collapsed['aggregatesonly']);
 437          $newprefs1 = get_user_preferences('grade_report_grader_collapsed_categories' . $course1->id); // Also verify new prefs.
 438          $this->assertNull($newprefs1);
 439  
 440          $convertedreport2 = $this->create_report($course2);
 441          $this->assertEquals([], $convertedreport2->collapsed['gradesonly']);
 442          $this->assertEquals([], $convertedreport2->collapsed['aggregatesonly']);
 443          $newprefs2 = get_user_preferences('grade_report_grader_collapsed_categories' . $course2->id); // Also verify new prefs.
 444          $this->assertNull($newprefs2);
 445  
 446          $convertedreport3 = $this->create_report($course3);
 447          $this->assertEquals([], $convertedreport3->collapsed['gradesonly']);
 448          $this->assertEquals([], $convertedreport3->collapsed['aggregatesonly']);
 449          $newprefs3 = get_user_preferences('grade_report_grader_collapsed_categories' . $course3->id); // Also verify new prefs.
 450          $this->assertNull($newprefs3);
 451  
 452          // Make sure the old style user preference is removed now.
 453          $this->assertEmpty(get_user_preferences('grade_report_grader_collapsed_categories'));
 454      }
 455  
 456      /**
 457       * Tests the get_right_rows function with one 'normal' and one 'ungraded' quiz.
 458       *
 459       * Previously, with an ungraded quiz (which results in a grade item with type GRADETYPE_NONE)
 460       * there was a bug in get_right_rows in some situations.
 461       */
 462      public function test_get_right_rows() {
 463          global $USER, $DB;
 464          $this->resetAfterTest(true);
 465  
 466          // Create manager and student on a course.
 467          $generator = $this->getDataGenerator();
 468          $manager = $generator->create_user();
 469          $student = $generator->create_user();
 470          $course = $generator->create_course();
 471          $generator->enrol_user($manager->id, $course->id, 'manager');
 472          $generator->enrol_user($student->id, $course->id, 'student');
 473  
 474          // Create a couple of quizzes on the course.
 475          $normalquiz = $generator->create_module('quiz', array('course' => $course->id,
 476                  'name' => 'NormalQuiz'));
 477          $ungradedquiz = $generator->create_module('quiz', array('course' => $course->id,
 478                  'name' => 'UngradedQuiz'));
 479  
 480          // Set the grade for the second one to 0 (note, you have to do this after creating it,
 481          // otherwise it doesn't create an ungraded grade item).
 482          quiz_settings::create($ungradedquiz->id)->get_grade_calculator()->update_quiz_maximum_grade(0);
 483  
 484          // Set current user.
 485          $this->setUser($manager);
 486          $USER->editing = false;
 487  
 488          // Get the report.
 489          $report = $this->create_report($course);
 490          $report->load_users();
 491          $report->load_final_grades();
 492          $result = $report->get_right_rows(false);
 493  
 494          // There should be 3 rows (2 header rows, plus the grades for the first user).
 495          $this->assertCount(3, $result);
 496  
 497          // The second row should contain 2 cells - one for the graded quiz and course total.
 498          $this->assertCount(2, $result[1]->cells);
 499          $this->assertStringContainsString('NormalQuiz', $result[1]->cells[0]->text);
 500          $this->assertStringContainsString('Course total', $result[1]->cells[1]->text);
 501  
 502          // User row should contain grade values '-'.
 503          $this->assertCount(2, $result[2]->cells);
 504          $this->assertStringContainsString('>-<', $result[2]->cells[0]->text);
 505          $this->assertStringContainsString('>-<', $result[2]->cells[1]->text);
 506  
 507          // Supposing the user cannot view hidden grades, this shouldn't make any difference (due
 508          // to a bug, it previously did).
 509          $context = \context_course::instance($course->id);
 510          $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
 511          assign_capability('moodle/grade:viewhidden', CAP_PROHIBIT, $managerroleid, $context->id, true);
 512          $this->assertFalse(has_capability('moodle/grade:viewhidden', $context));
 513  
 514          // Recreate the report. Confirm it returns successfully still.
 515          $report = $this->create_report($course);
 516          $report->load_users();
 517          $report->load_final_grades();
 518          $result = $report->get_right_rows(false);
 519          $this->assertCount(3, $result);
 520      }
 521  
 522      /**
 523       * Test loading report users when per page preferences are set
 524       */
 525      public function test_load_users_paging_preference(): void {
 526          $this->resetAfterTest();
 527  
 528          $course = $this->getDataGenerator()->create_course();
 529  
 530          // The report users will default to sorting by their lastname.
 531          $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student', ['lastname' => 'Apple']);
 532          $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student', ['lastname' => 'Banana']);
 533          $user3 = $this->getDataGenerator()->create_and_enrol($course, 'student', ['lastname' => 'Carrot']);
 534  
 535          // Set to empty string.
 536          $report = $this->create_report($course);
 537          $report->set_pref('studentsperpage', '');
 538          $users = $report->load_users();
 539          $this->assertEquals([$user1->id, $user2->id, $user3->id], array_column($users, 'id'));
 540  
 541          // Set to valid value.
 542          $report = $this->create_report($course);
 543          $report->set_pref('studentsperpage', 2);
 544          $users = $report->load_users();
 545          $this->assertEquals([$user1->id, $user2->id], array_column($users, 'id'));
 546      }
 547  
 548      /**
 549       * Test getting students per page report preference
 550       */
 551      public function test_get_students_per_page(): void {
 552          $this->resetAfterTest();
 553  
 554          $course = $this->getDataGenerator()->create_course();
 555  
 556          $report = $this->create_report($course);
 557          $report->set_pref('studentsperpage', 10);
 558  
 559          $perpage = $report->get_students_per_page();
 560          $this->assertSame(10, $perpage);
 561      }
 562  
 563      private function create_grade_category($course) {
 564          static $cnt = 0;
 565          $cnt++;
 566          $gradecat = new \grade_category(array('courseid' => $course->id, 'fullname' => 'Cat '.$cnt), false);
 567          $gradecat->apply_default_settings();
 568          $gradecat->apply_forced_settings();
 569          $gradecat->insert();
 570          return $gradecat;
 571      }
 572  
 573      private function create_report($course) {
 574  
 575          $coursecontext = \context_course::instance($course->id);
 576          $gpr = new grade_plugin_return(array('type' => 'report', 'plugin'=>'grader', 'courseid' => $course->id));
 577          $report = new grade_report_grader($course->id, $gpr, $coursecontext);
 578  
 579          return $report;
 580      }
 581  }