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]

   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 gradereport_history;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  /**
  22   * Grade history report test class.
  23   *
  24   * @package    gradereport_history
  25   * @copyright  2014 Frédéric Massart - FMCorz.net
  26   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  class report_test extends \advanced_testcase {
  29  
  30      /**
  31       * Create some grades.
  32       */
  33      public function test_query_db() {
  34          $this->resetAfterTest();
  35  
  36          // Making the setup.
  37          $c1 = $this->getDataGenerator()->create_course();
  38          $c2 = $this->getDataGenerator()->create_course();
  39          $c1ctx = \context_course::instance($c1->id);
  40          $c2ctx = \context_course::instance($c2->id);
  41  
  42          // Users.
  43          $u1 = $this->getDataGenerator()->create_user();
  44          $u2 = $this->getDataGenerator()->create_user();
  45          $u3 = $this->getDataGenerator()->create_user();
  46          $u4 = $this->getDataGenerator()->create_user();
  47          $u5 = $this->getDataGenerator()->create_user();
  48          $grader1 = $this->getDataGenerator()->create_user();
  49          $grader2 = $this->getDataGenerator()->create_user();
  50          self::getDataGenerator()->enrol_user($grader1->id, $c1->id, 'teacher');
  51          self::getDataGenerator()->enrol_user($grader2->id, $c1->id, 'teacher');
  52          self::getDataGenerator()->enrol_user($u2->id, $c1->id, 'student');
  53          self::getDataGenerator()->enrol_user($u3->id, $c1->id, 'student');
  54          self::getDataGenerator()->enrol_user($u4->id, $c1->id, 'student');
  55          self::getDataGenerator()->enrol_user($u5->id, $c1->id, 'student');
  56  
  57          self::getDataGenerator()->enrol_user($grader1->id, $c2->id, 'teacher');
  58          self::getDataGenerator()->enrol_user($u5->id, $c2->id, 'student');
  59  
  60          // Modules.
  61          $c1m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
  62          $c1m2 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
  63          $c1m3 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
  64          $c2m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c2));
  65          $c2m2 = $this->getDataGenerator()->create_module('assign', array('course' => $c2));
  66  
  67          // Creating fake history data.
  68          $giparams = array('itemtype' => 'mod', 'itemmodule' => 'assign');
  69          $grades = array();
  70  
  71          $this->setUser($grader1);
  72  
  73          $gi = \grade_item::fetch($giparams + array('iteminstance' => $c1m1->id));
  74          $grades['c1m1u1'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
  75                  'timemodified' => time() - 3600));
  76          $grades['c1m1u2'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u2->id,
  77                  'timemodified' => time() + 3600));
  78          $grades['c1m1u3'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u3->id));
  79          $grades['c1m1u4'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u4->id));
  80          $grades['c1m1u5'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u5->id));
  81  
  82          $gi = \grade_item::fetch($giparams + array('iteminstance' => $c1m2->id));
  83          $grades['c1m2u1'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id));
  84          $grades['c1m2u2'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u2->id));
  85  
  86          $gi = \grade_item::fetch($giparams + array('iteminstance' => $c1m3->id));
  87          $grades['c1m3u1'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id));
  88  
  89          $gi = \grade_item::fetch($giparams + array('iteminstance' => $c2m1->id));
  90          $grades['c2m1u1'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
  91              'usermodified' => $grader1->id));
  92          $grades['c2m1u2'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u2->id,
  93              'usermodified' => $grader1->id));
  94          $grades['c2m1u3'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u3->id,
  95              'usermodified' => $grader1->id));
  96          $grades['c2m1u4'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u4->id,
  97              'usermodified' => $grader2->id));
  98  
  99          // Histories where grades have not been revised..
 100          $grades['c2m1u5a'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u5->id,
 101              'timemodified' => time() - 60));
 102          $grades['c2m1u5b'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u5->id,
 103              'timemodified' => time()));
 104          $grades['c2m1u5c'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u5->id,
 105              'timemodified' => time() + 60));
 106  
 107          // Histories where grades have been revised and not revised.
 108          $now = time();
 109          $gi = \grade_item::fetch($giparams + array('iteminstance' => $c2m2->id));
 110          $grades['c2m2u1a'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
 111              'timemodified' => $now - 60, 'finalgrade' => 50));
 112          $grades['c2m2u1b'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
 113              'timemodified' => $now - 50, 'finalgrade' => 50));      // Not revised.
 114          $grades['c2m2u1c'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
 115              'timemodified' => $now, 'finalgrade' => 75));
 116          $grades['c2m2u1d'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
 117              'timemodified' => $now + 10, 'finalgrade' => 75));      // Not revised.
 118          $grades['c2m2u1e'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
 119              'timemodified' => $now + 60, 'finalgrade' => 25));
 120          $grades['c2m2u1f'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
 121              'timemodified' => $now + 70, 'finalgrade' => 25));      // Not revised.
 122  
 123          // TODO MDL-46736 Handle deleted/non-existing grade items.
 124          // Histories with missing grade items, considered as deleted.
 125          // $grades['c2x1u5'] = $this->create_grade_history($giparams + array('itemid' => -1, 'userid' => $u5->id, 'courseid' => $c1->id));
 126          // $grades['c2x2u5'] = $this->create_grade_history($giparams + array('itemid' => 999999, 'userid' => $u5->id, 'courseid' => $c1->id));
 127  
 128          // Basic filtering based on course id.
 129          $this->assertEquals(8, $this->get_tablelog_results($c1ctx, array(), true));
 130          $this->assertEquals(13, $this->get_tablelog_results($c2ctx, array(), true));
 131  
 132          // Filtering on 1 user the current user cannot access should return all records.
 133          $this->assertEquals(8, $this->get_tablelog_results($c1ctx, array('userids' => $u1->id), true));
 134  
 135          // Filtering on 2 users, only one of whom the current user can access.
 136          $this->assertEquals(1, $this->get_tablelog_results($c1ctx, ['userids' => "$u1->id,$u3->id"], true));
 137          $results = $this->get_tablelog_results($c1ctx, ['userids' => "$u1->id,$u3->id"]);
 138          $this->assertGradeHistoryIds([$grades['c1m1u3']->id], $results);
 139  
 140          // Filtering on 2 users, both of whom the current user can access.
 141          $this->assertEquals(3, $this->get_tablelog_results($c1ctx, ['userids' => "$u2->id,$u3->id"], true));
 142          $results = $this->get_tablelog_results($c1ctx, ['userids' => "$u2->id,$u3->id"]);
 143          $this->assertGradeHistoryIds([$grades['c1m1u2']->id, $grades['c1m1u3']->id, $grades['c1m2u2']->id], $results);
 144  
 145          // Filtering based on one grade item.
 146          $gi = \grade_item::fetch($giparams + array('iteminstance' => $c1m1->id));
 147          $this->assertEquals(5, $this->get_tablelog_results($c1ctx, array('itemid' => $gi->id), true));
 148          $gi = \grade_item::fetch($giparams + array('iteminstance' => $c1m3->id));
 149          $this->assertEquals(1, $this->get_tablelog_results($c1ctx, array('itemid' => $gi->id), true));
 150  
 151          // Filtering based on the grader.
 152          $this->assertEquals(3, $this->get_tablelog_results($c2ctx, array('grader' => $grader1->id), true));
 153          $this->assertEquals(1, $this->get_tablelog_results($c2ctx, array('grader' => $grader2->id), true));
 154  
 155          // Filtering based on date.
 156          $results = $this->get_tablelog_results($c1ctx, array('datefrom' => time() + 1800));
 157          $this->assertGradeHistoryIds(array($grades['c1m1u2']->id), $results);
 158          $results = $this->get_tablelog_results($c1ctx, array('datetill' => time() - 1800));
 159          $this->assertGradeHistoryIds(array($grades['c1m1u1']->id), $results);
 160          $results = $this->get_tablelog_results($c1ctx, array('datefrom' => time() - 1800, 'datetill' => time() + 1800));
 161          $this->assertGradeHistoryIds(array($grades['c1m1u3']->id, $grades['c1m1u4']->id, $grades['c1m1u5']->id,
 162              $grades['c1m2u1']->id, $grades['c1m2u2']->id, $grades['c1m3u1']->id), $results);
 163  
 164          // Filtering based on revised only.
 165          $this->assertEquals(3, $this->get_tablelog_results($c2ctx, array('userids' => $u5->id), true));
 166          $this->assertEquals(1, $this->get_tablelog_results($c2ctx, array('userids' => $u5->id, 'revisedonly' => true), true));
 167  
 168          // More filtering based on revised only.
 169          $gi = \grade_item::fetch($giparams + array('iteminstance' => $c2m2->id));
 170          $this->assertEquals(6, $this->get_tablelog_results($c2ctx, array('userids' => $u1->id, 'itemid' => $gi->id), true));
 171          $results = $this->get_tablelog_results($c2ctx, array('userids' => $u1->id, 'itemid' => $gi->id, 'revisedonly' => true));
 172          $this->assertGradeHistoryIds(array($grades['c2m2u1a']->id, $grades['c2m2u1c']->id, $grades['c2m2u1e']->id), $results);
 173  
 174          // Checking the value of the previous grade.
 175          $this->assertEquals(null, $results[$grades['c2m2u1a']->id]->prevgrade);
 176          $this->assertEquals($grades['c2m2u1a']->finalgrade, $results[$grades['c2m2u1c']->id]->prevgrade);
 177          $this->assertEquals($grades['c2m2u1c']->finalgrade, $results[$grades['c2m2u1e']->id]->prevgrade);
 178  
 179          // Put course in separate groups mode, add grader1 and two students to the same group.
 180          $c1->groupmode = SEPARATEGROUPS;
 181          update_course($c1);
 182          $this->assertFalse(has_capability('moodle/site:accessallgroups', \context_course::instance($c1->id)));
 183          $g1 = self::getDataGenerator()->create_group(['courseid' => $c1->id, 'name' => 'g1']);
 184          self::getDataGenerator()->create_group_member(['groupid' => $g1->id, 'userid' => $grader1->id]);
 185          self::getDataGenerator()->create_group_member(['groupid' => $g1->id, 'userid' => $u1->id]);
 186          self::getDataGenerator()->create_group_member(['groupid' => $g1->id, 'userid' => $u2->id]);
 187          $this->assertEquals(2, $this->get_tablelog_results($c1ctx, array(), true));
 188  
 189          // Grader2 is not in any groups.
 190          $this->setUser($grader2);
 191          $this->assertEquals(0, $this->get_tablelog_results($c1ctx, array(), true));
 192      }
 193  
 194      /**
 195       * Test the get users helper method.
 196       */
 197      public function test_get_users() {
 198          $this->resetAfterTest();
 199  
 200          // Making the setup.
 201          $c1 = $this->getDataGenerator()->create_course();
 202          $c2 = $this->getDataGenerator()->create_course();
 203          $c1ctx = \context_course::instance($c1->id);
 204          $c2ctx = \context_course::instance($c2->id);
 205  
 206          $c1m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
 207          $c2m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c2));
 208  
 209          // Users.
 210          $u1 = $this->getDataGenerator()->create_user(array('firstname' => 'Eric', 'lastname' => 'Cartman'));
 211          $u2 = $this->getDataGenerator()->create_user(array('firstname' => 'Stan', 'lastname' => 'Marsh'));
 212          $u3 = $this->getDataGenerator()->create_user(array('firstname' => 'Kyle', 'lastname' => 'Broflovski'));
 213          $u4 = $this->getDataGenerator()->create_user(array('firstname' => 'Kenny', 'lastname' => 'McCormick'));
 214  
 215          // Creating grade history for some users.
 216          $gi = \grade_item::fetch(array('iteminstance' => $c1m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
 217          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id));
 218          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u2->id));
 219          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u3->id));
 220  
 221          $gi = \grade_item::fetch(array('iteminstance' => $c2m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
 222          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u4->id));
 223  
 224          // Checking fetching some users.
 225          $users = \gradereport_history\helper::get_users($c1ctx);
 226          $this->assertCount(3, $users);
 227          $this->assertArrayHasKey($u3->id, $users);
 228          $users = \gradereport_history\helper::get_users($c2ctx);
 229          $this->assertCount(1, $users);
 230          $this->assertArrayHasKey($u4->id, $users);
 231          $users = \gradereport_history\helper::get_users($c1ctx, 'c');
 232          $this->assertCount(1, $users);
 233          $this->assertArrayHasKey($u1->id, $users);
 234          $users = \gradereport_history\helper::get_users($c1ctx, '', 0, 2);
 235          $this->assertCount(2, $users);
 236          $this->assertArrayHasKey($u3->id, $users);
 237          $this->assertArrayHasKey($u1->id, $users);
 238          $users = \gradereport_history\helper::get_users($c1ctx, '', 1, 2);
 239          $this->assertCount(1, $users);
 240          $this->assertArrayHasKey($u2->id, $users);
 241  
 242          // Checking the count of users.
 243          $this->assertEquals(3, \gradereport_history\helper::get_users_count($c1ctx));
 244          $this->assertEquals(1, \gradereport_history\helper::get_users_count($c2ctx));
 245          $this->assertEquals(1, \gradereport_history\helper::get_users_count($c1ctx, 'c'));
 246      }
 247  
 248      /**
 249       * Data provider for \gradereport_history_report_testcase::test_get_users_with_profile_fields()
 250       * Testing get_users() and get_users_count() test cases.
 251       *
 252       * @return array List of data sets (test cases)
 253       */
 254      public function get_users_with_profile_fields_provider(): array {
 255          return [
 256              // User identity check boxes, 'email', 'profile_field_lang' and 'profile_field_height' are checked.
 257                  'show email,lang and height;search for all users' =>
 258                          ['email,profile_field_lang,profile_field_height', '', ['u1', 'u2', 'u3', 'u4']],
 259                  'show email,lang and height;search for users on .org ' =>
 260                          ['email,profile_field_lang,profile_field_height', '.org', ['u1', 'u2', 'u4']],
 261                  'show email,lang and height;search for users on .com ' =>
 262                          ['email,profile_field_lang,profile_field_height', '.com', []],
 263                  'show email,lang and height;search for users on .uk ' =>
 264                          ['email,profile_field_lang,profile_field_height', '.uk', ['u3']],
 265                  'show email,lang and height,search for Spanish speakers' =>
 266                          ['email,profile_field_lang,profile_field_height', 'spanish', ['u1', 'u4']],
 267                  'show email,lang and height,search for Spanish speakers' =>
 268                          ['email,profile_field_lang,profile_field_height', 'spa', ['u1', 'u4']],
 269                  'show email,lang and height,search for German speakers' =>
 270                          ['email,profile_field_lang,profile_field_height', 'german', ['u2']],
 271                  'show email,lang and height,search for German speakers' =>
 272                          ['email,profile_field_lang,profile_field_height', 'ger', ['u2']],
 273                  'show email,lang and height,search for English speakers' =>
 274                          ['email,profile_field_lang,profile_field_height', 'english', ['u3']],
 275                  'show email,lang and height,search for English speakers' =>
 276                          ['email,profile_field_lang,profile_field_height', 'eng', ['u3']],
 277                  'show email,lang and height,search for English speakers' =>
 278                          ['email,profile_field_lang,profile_field_height', 'ish', ['u3']],
 279                  'show email,lang and height,search for users with height 180cm' =>
 280                          ['email,profile_field_lang,profile_field_height', '180cm', ['u2', 'u3', 'u4']],
 281                  'show email,lang and height,search for users with height 180cm' =>
 282                          ['email,profile_field_lang,profile_field_height', '180', ['u2', 'u3', 'u4']],
 283                  'show email,lang and height,search for users with height 170cm' =>
 284                          ['email,profile_field_lang,profile_field_height', '170cm', ['u1']],
 285                  'show email,lang and height,search for users with height 170cm' =>
 286                          ['email,profile_field_lang,profile_field_height', '170', ['u1']],
 287  
 288              // User identity check boxes, 'email' and 'profile_field_height' are checked.
 289                  'show email and height;search for users on .org' =>
 290                          ['email,profile_field_height', '.org', ['u1', 'u2', 'u4']],
 291                  'show email and height;search for users on .com' =>
 292                          ['email,profile_field_height', '.com', []],
 293                  'show email and height;search for users on .co' =>
 294                          ['email,profile_field_height', '.co', ['u3']],
 295                  'show email and height,search for Spanish/German/English speakers' =>
 296                          ['email,profile_field_height', 'spanish', []],
 297                  'show email and height,search for Spanish/German/English speakers' =>
 298                          ['email,profile_field_height', 'german', []],
 299                  'show email and height,search for Spanish/German/English speakers' =>
 300                          ['email,profile_field_height', 'english', []],
 301                  'show email,lang and height,search for English speakers' =>
 302                          ['email,profile_field_height', 'english', []],
 303                  'show email and height,search for English speakers' =>
 304                          ['email,profile_field_height', 'eng', []],
 305                  'show email and height,search for English speakers' =>
 306                          ['email,profile_field_height', 'ish', []],
 307                  'show email and height,search for users with height 180cm' =>
 308                          ['email,profile_field_height', '180cm', ['u2', 'u3', 'u4']],
 309                  'show email,lang and height,search for users with height 180cm' =>
 310                          ['email,profile_field_height', '180', ['u2', 'u3', 'u4']],
 311                  'show email,lang and height,search for users with height 170cm' =>
 312                          ['email,profile_field_height', '170cm', ['u1']],
 313                  'show email,lang and height,search for users with height 170cm' =>
 314                          ['email,profile_field_height', '170', ['u1']],
 315  
 316              // User identity check boxes, only 'email' is checked.
 317                  'show email only;search for users on .org' => ['email', '.org', ['u1', 'u2', 'u4']],
 318                  'show email only;search for users on .com' => ['email', '.com', []],
 319                  'show email only;search for users on .co.uk' => ['email', 'co.uk', ['u3']],
 320                  'show email only;search for users on .uk' => ['email', '.uk', ['u3']],
 321                  'show email only;search for users on .co' => ['email', '.co', ['u3']],
 322                  'show email only;search for Spanish speakers' => ['email', 'spanish', []],
 323                  'show email only;search for German speakers' => ['email', 'german', []],
 324                  'show email only;search for English speakers' => ['email', 'english', []],
 325                  'show email only;search for users with height 180cm' => ['email', '180cm', []],
 326                  'show email only;search for users with height 180cm' => ['email', '180', []],
 327                  'show email only;search for users with height 170cm' => ['email', '170cm', []],
 328                  'show email only;search for users with height 170cm' => ['email', '170', []],
 329          ];
 330      }
 331  
 332      /**
 333       * Testing the search functionality on get_users() and get_users_count() and their inner methods.
 334       *
 335       * @dataProvider get_users_with_profile_fields_provider
 336       *
 337       * @param string $showuseridentity, list of user identities to be shown.
 338       * @param string $searchstring, the string to be searched.
 339       * @param array $expectedusernames, a list of expected usernames.
 340       * @return void
 341       */
 342      public function test_get_users_with_profile_fields(string $showuseridentity, string $searchstring,
 343              array $expectedusernames): void {
 344          global $CFG, $DB;
 345          require_once($CFG->dirroot . '/user/profile/lib.php');
 346          $this->resetAfterTest();
 347  
 348          // Create a couple of custom profile fields, which are in user identity.
 349          $generator = $this->getDataGenerator();
 350          $generator->create_custom_profile_field(['datatype' => 'text',
 351                  'shortname' => 'lang', 'name' => 'Language']);
 352          $generator->create_custom_profile_field(['datatype' => 'text',
 353                  'shortname' => 'height', 'name' => 'Height']);
 354  
 355          // Create a couple of test users.
 356          $u1 = $generator->create_user(['firstname' => 'Eduardo', 'lastname' => 'Gomes',
 357                  'username' => 'u1', 'email' => 'u1@x.org', 'profile_field_lang' => 'Spanish',
 358                  'profile_field_height' => '170cm']);
 359          $u2 = $generator->create_user(['firstname' => 'Dieter', 'lastname' => 'Schmitt',
 360                  'username' => 'u2', 'email' => 'u2@x.org', 'profile_field_lang' => 'German',
 361                  'profile_field_height' => '180cm']);
 362  
 363          $u3 = $generator->create_user(['firstname' => 'Peter', 'lastname' => 'Jones',
 364                  'username' => 'u3', 'email' => 'u3@x.co.uk', 'profile_field_lang' => 'English',
 365                  'profile_field_height' => '180cm']);
 366          $u4 = $generator->create_user(['firstname' => 'Pedro', 'lastname' => 'Gomes',
 367                  'username' => 'u4', 'email' => 'u3@x.org', 'profile_field_lang' => 'Spanish',
 368                  'profile_field_height' => '180cm']);
 369  
 370          // Do this as admin user.
 371          $this->setAdminUser();
 372  
 373          // Making the setup.
 374          $c1 = $this->getDataGenerator()->create_course();
 375          $c1ctx = \context_course::instance($c1->id);
 376          $c1m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
 377  
 378          // Creating grade history for some users.
 379          $gi = \grade_item::fetch(array('iteminstance' => $c1m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
 380          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id));
 381          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u2->id));
 382          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u3->id));
 383          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u4->id));
 384  
 385          // Checking fetching some users with this config settings.
 386          set_config('showuseridentity', $showuseridentity);
 387          $numberofexpectedusers = count($expectedusernames);
 388          $users = \gradereport_history\helper::get_users($c1ctx, $searchstring);
 389          $userscount = \gradereport_history\helper::get_users_count($c1ctx, $searchstring);
 390          $this->assertEquals($numberofexpectedusers, $userscount);
 391          $this->assertCount($numberofexpectedusers, $users);
 392          foreach ($users as $user) {
 393              if (in_array($user->username, $expectedusernames)) {
 394                  $this->assertArrayHasKey($user->id, $users);
 395              } else {
 396                  $this->assertArrayNotHasKey($user->id, $users);
 397              }
 398          }
 399      }
 400  
 401      /**
 402       * Data provider method for \gradereport_history_report_testcase::test_get_users_with_groups()
 403       */
 404      public function get_users_provider() {
 405          return [
 406              'Visible groups, non-editing teacher, not in any group' => [
 407                  VISIBLEGROUPS, 'teacher', ['g1', 'g2'], ['s1', 's2', 's3', 's4', 's5']
 408              ],
 409              'Visible groups, non-editing teacher' => [
 410                  VISIBLEGROUPS, 'teacher', [], ['s1', 's2', 's3', 's4', 's5']
 411              ],
 412              'Visible groups, editing teacher' => [
 413                  VISIBLEGROUPS, 'editingteacher', ['g1', 'g2'], ['s1', 's2', 's3', 's4', 's5']
 414              ],
 415              'Separate groups, non-editing teacher' => [
 416                  SEPARATEGROUPS, 'teacher', ['g1', 'g2'], ['s1', 's2']
 417              ],
 418              'Separate groups, non-editing teacher, not in any group' => [
 419                  SEPARATEGROUPS, 'teacher', [], []
 420              ],
 421              'Separate groups, non-editing teacher and student share two groups' => [
 422                  SEPARATEGROUPS, 'teacher', ['g4', 'g5'], ['s5']
 423              ],
 424              'Separate groups, editing teacher' => [
 425                  SEPARATEGROUPS, 'editingteacher', ['g1', 'g2'], ['s1', 's2', 's3', 's4', 's5']
 426              ],
 427          ];
 428      }
 429  
 430      /**
 431       * Test for helper::get_users() with course group mode set.
 432       *
 433       * @dataProvider get_users_provider
 434       * @param $groupmode
 435       * @param $teacherrole
 436       * @param $teachergroups
 437       * @param $expectedusers
 438       */
 439      public function test_get_users_with_groups($groupmode, $teacherrole, $teachergroups, $expectedusers) {
 440          global $DB;
 441          $this->resetAfterTest();
 442  
 443          $generator = $this->getDataGenerator();
 444  
 445          // Create a test course.
 446          $course = $generator->create_course(['groupmode' => $groupmode]);
 447  
 448          // Create an assignment module.
 449          $assign = $generator->create_module('assign', ['course' => $course]);
 450  
 451          // Fetch roles.
 452          $role = $DB->get_record('role', ['shortname' => $teacherrole], '*', MUST_EXIST);
 453          $studentrole =  $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
 454  
 455          // Create users.
 456          $t1 = $generator->create_user(['username' => 't1', 'email' => 't1@example.com']);
 457          $s1 = $generator->create_user(['username' => 's1', 'email' => 's1@example.com']);
 458          $s2 = $generator->create_user(['username' => 's2', 'email' => 's2@example.com']);
 459          $s3 = $generator->create_user(['username' => 's3', 'email' => 's3@example.com']);
 460          $s4 = $generator->create_user(['username' => 's4', 'email' => 's4@example.com']);
 461          $s5 = $generator->create_user(['username' => 's5', 'email' => 's5@example.com']);
 462  
 463          // Enrol users.
 464          $generator->enrol_user($t1->id, $course->id, $role->id);
 465          $generator->enrol_user($s1->id, $course->id, $studentrole->id);
 466          $generator->enrol_user($s2->id, $course->id, $studentrole->id);
 467          $generator->enrol_user($s3->id, $course->id, $studentrole->id);
 468          $generator->enrol_user($s4->id, $course->id, $studentrole->id);
 469          $generator->enrol_user($s5->id, $course->id, $studentrole->id);
 470  
 471          // Create groups.
 472          $groups = [];
 473          $groups['g1'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g1']);
 474          $groups['g2'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g2']);
 475          $groups['g3'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g3']);
 476          $groups['g4'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g4']);
 477          $groups['g5'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g5']);
 478  
 479          // Add teacher to the assigned groups.
 480          foreach ($teachergroups as $groupname) {
 481              $group = $groups[$groupname];
 482              $generator->create_group_member(['groupid' => $group->id, 'userid' => $t1->id]);
 483          }
 484  
 485          // Add students to groups.
 486          $generator->create_group_member(['groupid' => $groups['g1']->id, 'userid' => $s1->id]);
 487          $generator->create_group_member(['groupid' => $groups['g2']->id, 'userid' => $s2->id]);
 488          $generator->create_group_member(['groupid' => $groups['g3']->id, 'userid' => $s3->id]);
 489          $generator->create_group_member(['groupid' => $groups['g4']->id, 'userid' => $s5->id]);
 490          $generator->create_group_member(['groupid' => $groups['g5']->id, 'userid' => $s5->id]);
 491  
 492          // Creating grade history for the students.
 493          $gi = \grade_item::fetch(['iteminstance' => $assign->id, 'itemtype' => 'mod', 'itemmodule' => 'assign']);
 494          $this->create_grade_history(['itemid' => $gi->id, 'userid' => $s1->id]);
 495          $this->create_grade_history(['itemid' => $gi->id, 'userid' => $s2->id]);
 496          $this->create_grade_history(['itemid' => $gi->id, 'userid' => $s3->id]);
 497          $this->create_grade_history(['itemid' => $gi->id, 'userid' => $s4->id]);
 498          $this->create_grade_history(['itemid' => $gi->id, 'userid' => $s5->id]);
 499  
 500          // Log in as the teacher.
 501          $this->setUser($t1);
 502  
 503          // Fetch the users.
 504          $users = \gradereport_history\helper::get_users(\context_course::instance($course->id));
 505          // Confirm that the number of users fetched is the same as the count of expected users.
 506          $this->assertCount(count($expectedusers), $users);
 507          foreach ($users as $user) {
 508              // Confirm that each user returned is in the list of expected users.
 509              $this->assertTrue(in_array($user->username, $expectedusers));
 510          }
 511      }
 512  
 513      /**
 514       * Test the get graders helper method.
 515       */
 516      public function test_graders() {
 517          $this->resetAfterTest();
 518  
 519          // Making the setup.
 520          $c1 = $this->getDataGenerator()->create_course();
 521          $c2 = $this->getDataGenerator()->create_course();
 522          $c3 = $this->getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS]);
 523  
 524          $c1m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
 525          $c2m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c2));
 526          $c3m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c3));
 527  
 528          // Users.
 529          $u1 = $this->getDataGenerator()->create_user(array('firstname' => 'Eric', 'lastname' => 'Cartman'));
 530          $u2 = $this->getDataGenerator()->create_user(array('firstname' => 'Stan', 'lastname' => 'Marsh'));
 531          $u3 = $this->getDataGenerator()->create_user(array('firstname' => 'Kyle', 'lastname' => 'Broflovski'));
 532          $u4 = $this->getDataGenerator()->create_user(array('firstname' => 'Kenny', 'lastname' => 'McCormick'));
 533  
 534          foreach ([$c1, $c2, $c3] as $course) {
 535              foreach ([$u1, $u2, $u3, $u4] as $user) {
 536                  self::getDataGenerator()->enrol_user($user->id, $course->id, 'student');
 537              }
 538          }
 539  
 540          // Creating grade history for some users.
 541          $gi = \grade_item::fetch(array('iteminstance' => $c1m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
 542          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u1->id));
 543          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u2->id));
 544          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u3->id));
 545  
 546          $gi = \grade_item::fetch(array('iteminstance' => $c2m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
 547          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u4->id));
 548  
 549          $gi = \grade_item::fetch(array('iteminstance' => $c3m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
 550          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u1->id));
 551          $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u2->id, 'usermodified' => $u2->id));
 552  
 553          // Checking fetching some users.
 554          $graders = \gradereport_history\helper::get_graders($c1->id);
 555          $this->assertCount(4, $graders); // Including "all graders" .
 556          $this->assertArrayHasKey($u1->id, $graders);
 557          $this->assertArrayHasKey($u2->id, $graders);
 558          $this->assertArrayHasKey($u3->id, $graders);
 559          $graders = \gradereport_history\helper::get_graders($c2->id);
 560          $this->assertCount(2, $graders); // Including "all graders" .
 561          $this->assertArrayHasKey($u4->id, $graders);
 562  
 563          // Third course is in separate groups mode. Only graders from the same group will be returned.
 564          $g = self::getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'g1']);
 565          self::getDataGenerator()->create_group_member(['groupid' => $g->id, 'userid' => $u1->id]);
 566          self::getDataGenerator()->create_group_member(['groupid' => $g->id, 'userid' => $u2->id]);
 567          $this->setUser($u1);
 568          $graders = \gradereport_history\helper::get_graders($c3->id);
 569          $this->assertCount(3, $graders); // Including "all graders" .
 570          $this->setUser($u3);
 571          $graders = \gradereport_history\helper::get_graders($c3->id);
 572          $this->assertCount(1, $graders); // Including "all graders" .
 573      }
 574  
 575      /**
 576       * Asserts that the array of grade objects contains exactly the right IDs.
 577       *
 578       * @param array $expectedids Array of expected IDs.
 579       * @param array $objects Array of objects returned by the table.
 580       */
 581      protected function assertGradeHistoryIds(array $expectedids, array $objects) {
 582          $this->assertCount(count($expectedids), $objects);
 583          $expectedids = array_flip($expectedids);
 584          foreach ($objects as $object) {
 585              $this->assertArrayHasKey($object->id, $expectedids);
 586              unset($expectedids[$object->id]);
 587          }
 588          $this->assertCount(0, $expectedids);
 589      }
 590  
 591      /**
 592       * Create a new grade history entry.
 593       *
 594       * @param array $params Of values.
 595       * @return object The grade object.
 596       */
 597      protected function create_grade_history($params) {
 598          global $DB;
 599          $params = (array) $params;
 600  
 601          if (!isset($params['itemid'])) {
 602              throw new \coding_exception('Missing itemid key.');
 603          }
 604          if (!isset($params['userid'])) {
 605              throw new \coding_exception('Missing userid key.');
 606          }
 607  
 608          // Default object.
 609          $grade = new \stdClass();
 610          $grade->itemid = 0;
 611          $grade->userid = 0;
 612          $grade->oldid = 123;
 613          $grade->rawgrade = 50;
 614          $grade->finalgrade = 50;
 615          $grade->timecreated = time();
 616          $grade->timemodified = time();
 617          $grade->information = '';
 618          $grade->informationformat = FORMAT_PLAIN;
 619          $grade->feedback = '';
 620          $grade->feedbackformat = FORMAT_PLAIN;
 621          $grade->usermodified = 2;
 622  
 623          // Merge with data passed.
 624          $grade = (object) array_merge((array) $grade, $params);
 625  
 626          // Insert record.
 627          $grade->id = $DB->insert_record('grade_grades_history', $grade);
 628  
 629          return $grade;
 630      }
 631  
 632      /**
 633       * Returns a table log object.
 634       *
 635       * @param context_course $coursecontext The course context.
 636       * @param array $filters An array of filters.
 637       * @param boolean $count When true, returns a count rather than an array of objects.
 638       * @return mixed Count or array of objects.
 639       */
 640      protected function get_tablelog_results($coursecontext, $filters = array(), $count = false) {
 641          $table = new gradereport_history_tests_tablelog('something', $coursecontext, new \moodle_url(''), $filters);
 642          return $table->get_test_results($count);
 643      }
 644  
 645  }
 646  
 647  /**
 648   * Extended table log class.
 649   */
 650  class gradereport_history_tests_tablelog extends \gradereport_history\output\tablelog {
 651  
 652      /**
 653       * Get the test results.
 654       *
 655       * @param boolean $count Whether or not we want the count.
 656       * @return mixed Count or array of objects.
 657       */
 658      public function get_test_results($count = false) {
 659          global $DB;
 660          if ($count) {
 661              list($sql, $params) = $this->get_sql_and_params(true);
 662              return $DB->count_records_sql($sql, $params);
 663          } else {
 664              $this->setup();
 665              list($sql, $params) = $this->get_sql_and_params();
 666              return $DB->get_records_sql($sql, $params);
 667          }
 668      }
 669  
 670  }