Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

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