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  
  18  namespace mod_h5pactivity\local;
  19  use context_module;
  20  use stdClass;
  21  
  22  /**
  23   * Manager tests class for mod_h5pactivity.
  24   *
  25   * @package    mod_h5pactivity
  26   * @covers     \mod_h5pactivity\local\manager
  27   * @category   test
  28   * @copyright  2020 Ferran Recio <ferran@moodle.com>
  29   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class manager_test extends \advanced_testcase {
  32  
  33      /**
  34       * Test for static create methods.
  35       */
  36      public function test_create() {
  37  
  38          $this->resetAfterTest();
  39          $this->setAdminUser();
  40  
  41          $course = $this->getDataGenerator()->create_course();
  42          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
  43          $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
  44          $context = context_module::instance($cm->id);
  45  
  46          $manager = manager::create_from_instance($activity);
  47          $manageractivity = $manager->get_instance();
  48          $this->assertEquals($activity->id, $manageractivity->id);
  49          $managercm = $manager->get_coursemodule();
  50          $this->assertEquals($cm->id, $managercm->id);
  51          $managercontext = $manager->get_context();
  52          $this->assertEquals($context->id, $managercontext->id);
  53  
  54          $manager = manager::create_from_coursemodule($cm);
  55          $manageractivity = $manager->get_instance();
  56          $this->assertEquals($activity->id, $manageractivity->id);
  57          $managercm = $manager->get_coursemodule();
  58          $this->assertEquals($cm->id, $managercm->id);
  59          $managercontext = $manager->get_context();
  60          $this->assertEquals($context->id, $managercontext->id);
  61      }
  62  
  63      /**
  64       * Test for is_tracking_enabled.
  65       *
  66       * @dataProvider is_tracking_enabled_data
  67       * @param bool $login if the user is logged in
  68       * @param string $role user role in course
  69       * @param int $enabletracking if tracking is enabled
  70       * @param bool $expected expected result
  71       */
  72      public function test_is_tracking_enabled(bool $login, string $role, int $enabletracking, bool $expected) {
  73  
  74          $this->resetAfterTest();
  75          $this->setAdminUser();
  76  
  77          $course = $this->getDataGenerator()->create_course();
  78          $activity = $this->getDataGenerator()->create_module('h5pactivity',
  79                  ['course' => $course, 'enabletracking' => $enabletracking]);
  80  
  81          $user = $this->getDataGenerator()->create_and_enrol($course, $role);
  82          if ($login) {
  83              $this->setUser($user);
  84              $param = null;
  85          } else {
  86              $param = $user;
  87          }
  88  
  89          $manager = manager::create_from_instance($activity);
  90          $this->assertEquals($expected, $manager->is_tracking_enabled($param));
  91      }
  92  
  93      /**
  94       * Data provider for is_tracking_enabled.
  95       *
  96       * @return array
  97       */
  98      public function is_tracking_enabled_data(): array {
  99          return [
 100              'Logged student, tracking enabled' => [
 101                  true, 'student', 1, true
 102              ],
 103              'Logged student, tracking disabled' => [
 104                  true, 'student', 0, false
 105              ],
 106              'Logged teacher, tracking enabled' => [
 107                  true, 'editingteacher', 1, false
 108              ],
 109              'Logged teacher, tracking disabled' => [
 110                  true, 'editingteacher', 0, false
 111              ],
 112              'No logged student, tracking enabled' => [
 113                  true, 'student', 1, true
 114              ],
 115              'No logged student, tracking disabled' => [
 116                  true, 'student', 0, false
 117              ],
 118              'No logged teacher, tracking enabled' => [
 119                  true, 'editingteacher', 1, false
 120              ],
 121              'No logged teacher, tracking disabled' => [
 122                  true, 'editingteacher', 0, false
 123              ],
 124          ];
 125      }
 126  
 127      /**
 128       * Test for get_users_scaled_score.
 129       *
 130       * @dataProvider get_users_scaled_score_data
 131       * @param int $enabletracking if tracking is enabled
 132       * @param int $gradingmethod new grading method
 133       * @param array $result1 student 1 results (scaled, timemodified, attempt number)
 134       * @param array $result2 student 2 results (scaled, timemodified, attempt number)
 135       */
 136      public function test_get_users_scaled_score(int $enabletracking, int $gradingmethod, array $result1, array $result2) {
 137          global $DB;
 138  
 139          $this->resetAfterTest();
 140          $this->setAdminUser();
 141  
 142          $course = $this->getDataGenerator()->create_course();
 143          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 144                  ['course' => $course, 'enabletracking' => $enabletracking, 'grademethod' => $gradingmethod]);
 145  
 146          // Generate two users with 4 attempts each.
 147          $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 148          $this->generate_fake_attempts($activity, $user1, 1);
 149          $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 150          $this->generate_fake_attempts($activity, $user2, 2);
 151  
 152          $manager = manager::create_from_instance($activity);
 153  
 154          // Get all users scaled scores.
 155          $scaleds = $manager->get_users_scaled_score();
 156  
 157          // No results will be returned if tracking is dsabled or manual grading method is defined.
 158          if (empty($result1)) {
 159              $this->assertNull($scaleds);
 160              return;
 161          }
 162  
 163          $this->assertCount(2, $scaleds);
 164  
 165          // Check expected user1 scaled score.
 166          $scaled = $scaleds[$user1->id];
 167          $this->assertEquals($user1->id, $scaled->userid);
 168          $this->assertEquals($result1[0], $scaled->scaled);
 169          $this->assertEquals($result1[1], $scaled->timemodified);
 170          if ($result1[2]) {
 171              $attempt = $DB->get_record('h5pactivity_attempts', ['id' => $scaled->attemptid]);
 172              $this->assertEquals($attempt->h5pactivityid, $activity->id);
 173              $this->assertEquals($attempt->userid, $scaled->userid);
 174              $this->assertEquals($attempt->scaled, round($scaled->scaled, 5));
 175              $this->assertEquals($attempt->timemodified, $scaled->timemodified);
 176              $this->assertEquals($result1[2], $attempt->attempt);
 177          } else {
 178              $this->assertEquals(0, $scaled->attemptid);
 179          }
 180  
 181          // Check expected user2 scaled score.
 182          $scaled = $scaleds[$user2->id];
 183          $this->assertEquals($user2->id, $scaled->userid);
 184          $this->assertEquals($result2[0], round($scaled->scaled, 5));
 185          $this->assertEquals($result2[1], $scaled->timemodified);
 186          if ($result2[2]) {
 187              $attempt = $DB->get_record('h5pactivity_attempts', ['id' => $scaled->attemptid]);
 188              $this->assertEquals($attempt->h5pactivityid, $activity->id);
 189              $this->assertEquals($attempt->userid, $scaled->userid);
 190              $this->assertEquals($attempt->scaled, $scaled->scaled);
 191              $this->assertEquals($attempt->timemodified, $scaled->timemodified);
 192              $this->assertEquals($result2[2], $attempt->attempt);
 193          } else {
 194              $this->assertEquals(0, $scaled->attemptid);
 195          }
 196  
 197          // Now check a single user record.
 198          $scaleds = $manager->get_users_scaled_score($user2->id);
 199          $this->assertCount(1, $scaleds);
 200          $scaled2 = $scaleds[$user2->id];
 201          $this->assertEquals($scaled->userid, $scaled2->userid);
 202          $this->assertEquals($scaled->scaled, $scaled2->scaled);
 203          $this->assertEquals($scaled->attemptid, $scaled2->attemptid);
 204          $this->assertEquals($scaled->timemodified, $scaled2->timemodified);
 205      }
 206  
 207      /**
 208       * Data provider for get_users_scaled_score.
 209       *
 210       * @return array
 211       */
 212      public function get_users_scaled_score_data(): array {
 213          return [
 214              'Tracking with max attempt method' => [
 215                  1, manager::GRADEHIGHESTATTEMPT, [1.00000, 31, 2], [0.66667, 32, 2]
 216              ],
 217              'Tracking with average attempt method' => [
 218                  1, manager::GRADEAVERAGEATTEMPT, [0.61111, 51, 0], [0.52222, 52, 0]
 219              ],
 220              'Tracking with last attempt method' => [
 221                  1, manager::GRADELASTATTEMPT, [0.33333, 51, 3], [0.40000, 52, 3]
 222              ],
 223              'Tracking with first attempt method' => [
 224                  1, manager::GRADEFIRSTATTEMPT, [0.50000, 11, 1], [0.50000, 12, 1]
 225              ],
 226              'Tracking with manual attempt grading' => [
 227                  1, manager::GRADEMANUAL, [], []
 228              ],
 229              'No tracking with max attempt method' => [
 230                  0, manager::GRADEHIGHESTATTEMPT, [], []
 231              ],
 232              'No tracking with average attempt method' => [
 233                  0, manager::GRADEAVERAGEATTEMPT, [], []
 234              ],
 235              'No tracking with last attempt method' => [
 236                  0, manager::GRADELASTATTEMPT, [], []
 237              ],
 238              'No tracking with first attempt method' => [
 239                  0, manager::GRADEFIRSTATTEMPT, [], []
 240              ],
 241              'No tracking with manual attempt grading' => [
 242                  0, manager::GRADEMANUAL, [], []
 243              ],
 244          ];
 245      }
 246  
 247      /**
 248       * Test static get_grading_methods.
 249       */
 250      public function test_get_grading_methods() {
 251          $methods = manager::get_grading_methods();
 252          $this->assertCount(5, $methods);
 253          $this->assertNotEmpty($methods[manager::GRADEHIGHESTATTEMPT]);
 254          $this->assertNotEmpty($methods[manager::GRADEAVERAGEATTEMPT]);
 255          $this->assertNotEmpty($methods[manager::GRADELASTATTEMPT]);
 256          $this->assertNotEmpty($methods[manager::GRADEFIRSTATTEMPT]);
 257          $this->assertNotEmpty($methods[manager::GRADEMANUAL]);
 258      }
 259  
 260      /**
 261       * Test static get_selected_attempt.
 262       *
 263       * @dataProvider get_selected_attempt_data
 264       * @param int $enabletracking if tracking is enabled
 265       * @param int $gradingmethod new grading method
 266       * @param int $result the expected result
 267       */
 268      public function test_get_selected_attempt(int $enabletracking, int $gradingmethod, int $result) {
 269          $this->resetAfterTest();
 270          $this->setAdminUser();
 271  
 272          $course = $this->getDataGenerator()->create_course();
 273          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 274                  ['course' => $course, 'enabletracking' => $enabletracking, 'grademethod' => $gradingmethod]);
 275  
 276          $manager = manager::create_from_instance($activity);
 277  
 278          $selected = $manager->get_selected_attempt();
 279  
 280          $this->assertEquals($result, $selected[0]);
 281          $this->assertNotEmpty($selected[1]);
 282      }
 283  
 284      /**
 285       * Data provider for get_users_scaled_score.
 286       *
 287       * @return array
 288       */
 289      public function get_selected_attempt_data(): array {
 290          return [
 291              'Tracking with max attempt method' => [
 292                  1, manager::GRADEHIGHESTATTEMPT, manager::GRADEHIGHESTATTEMPT
 293              ],
 294              'Tracking with average attempt method' => [
 295                  1, manager::GRADEAVERAGEATTEMPT, manager::GRADEAVERAGEATTEMPT
 296              ],
 297              'Tracking with last attempt method' => [
 298                  1, manager::GRADELASTATTEMPT, manager::GRADELASTATTEMPT
 299              ],
 300              'Tracking with first attempt method' => [
 301                  1, manager::GRADEFIRSTATTEMPT, manager::GRADEFIRSTATTEMPT
 302              ],
 303              'Tracking with manual attempt grading' => [
 304                  1, manager::GRADEMANUAL, manager::GRADEMANUAL
 305              ],
 306              'No tracking with max attempt method' => [
 307                  0, manager::GRADEHIGHESTATTEMPT, manager::GRADEMANUAL
 308              ],
 309              'No tracking with average attempt method' => [
 310                  0, manager::GRADEAVERAGEATTEMPT, manager::GRADEMANUAL
 311              ],
 312              'No tracking with last attempt method' => [
 313                  0, manager::GRADELASTATTEMPT, manager::GRADEMANUAL
 314              ],
 315              'No tracking with first attempt method' => [
 316                  0, manager::GRADEFIRSTATTEMPT, manager::GRADEMANUAL
 317              ],
 318              'No tracking with manual attempt grading' => [
 319                  0, manager::GRADEMANUAL, manager::GRADEMANUAL
 320              ],
 321          ];
 322      }
 323  
 324      /**
 325       * Test static get_review_modes.
 326       */
 327      public function test_get_review_modes() {
 328          $methods = manager::get_review_modes();
 329          $this->assertCount(2, $methods);
 330          $this->assertNotEmpty($methods[manager::REVIEWCOMPLETION]);
 331          $this->assertNotEmpty($methods[manager::REVIEWNONE]);
 332      }
 333  
 334      /**
 335       * Test get_grader method.
 336       */
 337      public function test_get_grader() {
 338          $this->resetAfterTest();
 339          $this->setAdminUser();
 340  
 341          $course = $this->getDataGenerator()->create_course();
 342          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 343          $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
 344          $context = context_module::instance($cm->id);
 345  
 346          $manager = manager::create_from_instance($activity);
 347          $grader = $manager->get_grader();
 348  
 349          $this->assertInstanceOf('mod_h5pactivity\local\grader', $grader);
 350      }
 351  
 352  
 353      /**
 354       * Test static can_view_all_attempts.
 355       *
 356       * @dataProvider can_view_all_attempts_data
 357       * @param int $enabletracking if tracking is enabled
 358       * @param bool $usestudent if test must be done with a user role
 359       * @param bool $useloggedin if test must be done with the loggedin user
 360       * @param bool $result the expected result
 361       */
 362      public function test_can_view_all_attempts(int $enabletracking, bool $usestudent, bool $useloggedin, bool $result) {
 363          global $USER;
 364  
 365          $this->resetAfterTest();
 366          $this->setAdminUser();
 367  
 368          $course = $this->getDataGenerator()->create_course();
 369          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 370                  ['course' => $course, 'enabletracking' => $enabletracking]);
 371  
 372          $manager = manager::create_from_instance($activity);
 373  
 374          $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
 375          $loggedin = $USER;
 376  
 377          // We want to test what when the method is called to check a different user than $USER.
 378          if (!$usestudent) {
 379              $loggedin = $user;
 380              $user = $USER;
 381          }
 382  
 383          if ($useloggedin) {
 384              $this->setUser($user);
 385              $user = null;
 386          } else {
 387              $this->setUser($loggedin);
 388          }
 389  
 390          $this->assertEquals($result, $manager->can_view_all_attempts($user));
 391      }
 392  
 393      /**
 394       * Data provider for test_can_view_all_attempts.
 395       *
 396       * @return array
 397       */
 398      public function can_view_all_attempts_data(): array {
 399          return [
 400              // No tracking cases.
 401              'No tracking with admin using $USER' => [
 402                  0, false, false, false
 403              ],
 404              'No tracking with student using $USER' => [
 405                  0, true, false, false
 406              ],
 407              'No tracking with admin loggedin' => [
 408                  0, false, true, false
 409              ],
 410              'No tracking with student loggedin' => [
 411                  0, true, true, false
 412              ],
 413              // Tracking enabled cases.
 414              'Tracking with admin using $USER' => [
 415                  1, false, false, true
 416              ],
 417              'Tracking with student using $USER' => [
 418                  1, true, false, false
 419              ],
 420              'Tracking with admin loggedin' => [
 421                  1, false, true, true
 422              ],
 423              'Tracking with student loggedin' => [
 424                  1, true, true, false
 425              ],
 426          ];
 427      }
 428  
 429      /**
 430       * Test static can_view_own_attempts.
 431       *
 432       * @dataProvider can_view_own_attempts_data
 433       * @param int $enabletracking if tracking is enabled
 434       * @param int $reviewmode the attempt review mode
 435       * @param bool $useloggedin if test must be done with the loggedin user
 436       * @param bool $hasattempts if the student have attempts
 437       * @param bool $result the expected result
 438       */
 439      public function test_can_view_own_attempts(int $enabletracking, int $reviewmode,
 440              bool $useloggedin, bool $hasattempts, bool $result) {
 441  
 442          $this->resetAfterTest();
 443          $this->setAdminUser();
 444  
 445          $course = $this->getDataGenerator()->create_course();
 446          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 447                  ['course' => $course, 'enabletracking' => $enabletracking, 'reviewmode' => $reviewmode]);
 448  
 449          $manager = manager::create_from_instance($activity);
 450  
 451          $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
 452  
 453          if ($hasattempts) {
 454              $this->generate_fake_attempts($activity, $user, 1);
 455          }
 456  
 457          if ($useloggedin) {
 458              $this->setUser($user);
 459              $user = null;
 460          }
 461  
 462          $this->assertEquals($result, $manager->can_view_own_attempts($user));
 463      }
 464  
 465      /**
 466       * Data provider for test_can_view_own_attempts.
 467       *
 468       * @return array
 469       */
 470      public function can_view_own_attempts_data(): array {
 471          return [
 472              // No tracking cases.
 473              'No tracking, review none, using $USER, without attempts' => [
 474                  0, manager::REVIEWNONE, false, false, false
 475              ],
 476              'No tracking, review enabled, using $USER, without attempts' => [
 477                  0, manager::REVIEWCOMPLETION, false, false, false
 478              ],
 479              'No tracking, review none, loggedin, without attempts' => [
 480                  0, manager::REVIEWNONE, true, false, false
 481              ],
 482              'No tracking, review enabled, loggedin, without attempts' => [
 483                  0, manager::REVIEWCOMPLETION, true, false, false
 484              ],
 485              'No tracking, review none, using $USER, with attempts' => [
 486                  0, manager::REVIEWNONE, false, true, false
 487              ],
 488              'No tracking, review enabled, using $USER, with attempts' => [
 489                  0, manager::REVIEWCOMPLETION, false, true, false
 490              ],
 491              'No tracking, review none, loggedin, with attempts' => [
 492                  0, manager::REVIEWNONE, true, true, false
 493              ],
 494              'No tracking, review enabled, loggedin, with attempts' => [
 495                  0, manager::REVIEWCOMPLETION, true, true, false
 496              ],
 497              // Tracking enabled cases.
 498              'Tracking enabled, review none, using $USER, without attempts' => [
 499                  1, manager::REVIEWNONE, false, false, false
 500              ],
 501              'Tracking enabled, review enabled, using $USER, without attempts' => [
 502                  1, manager::REVIEWCOMPLETION, false, false, true
 503              ],
 504              'Tracking enabled, review none, loggedin, without attempts' => [
 505                  1, manager::REVIEWNONE, true, false, false
 506              ],
 507              'Tracking enabled, review enabled, loggedin, without attempts' => [
 508                  1, manager::REVIEWCOMPLETION, true, false, true
 509              ],
 510              'Tracking enabled, review none, using $USER, with attempts' => [
 511                  1, manager::REVIEWNONE, false, true, false
 512              ],
 513              'Tracking enabled, review enabled, using $USER, with attempts' => [
 514                  1, manager::REVIEWCOMPLETION, false, true, true
 515              ],
 516              'Tracking enabled, review none, loggedin, with attempts' => [
 517                  1, manager::REVIEWNONE, true, true, false
 518              ],
 519              'Tracking enabled, review enabled, loggedin, with attempts' => [
 520                  1, manager::REVIEWCOMPLETION, true, true, true
 521              ],
 522          ];
 523      }
 524  
 525      /**
 526       * Test static count_attempts of one user.
 527       */
 528      public function test_count_attempts() {
 529  
 530          $this->resetAfterTest();
 531          $this->setAdminUser();
 532  
 533          $course = $this->getDataGenerator()->create_course();
 534          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 535                  ['course' => $course]);
 536  
 537          $manager = manager::create_from_instance($activity);
 538  
 539          // User without attempts.
 540          $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 541  
 542          // User with 1 attempt.
 543          $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 544          $this->generate_fake_attempts($activity, $user2, 1);
 545  
 546          // User with 2 attempts.
 547          $user3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 548          $this->generate_fake_attempts($activity, $user3, 1);
 549  
 550          // Incomplete user2 and 3 has only 3 attempts completed.
 551          $this->assertEquals(0, $manager->count_attempts($user1->id));
 552          $this->assertEquals(3, $manager->count_attempts($user2->id));
 553          $this->assertEquals(3, $manager->count_attempts($user3->id));
 554      }
 555  
 556      /**
 557       * Test static count_attempts of all active participants.
 558       *
 559       * @dataProvider count_attempts_all_data
 560       * @param bool $canview if the student role has mod_h5pactivity/view capability
 561       * @param bool $cansubmit if the student role has mod_h5pactivity/submit capability
 562       * @param bool $extrarole if an extra role without submit capability is required
 563       * @param int $result the expected result
 564       */
 565      public function test_count_attempts_all(bool $canview, bool $cansubmit, bool $extrarole, int $result) {
 566          global $DB;
 567  
 568          $this->resetAfterTest();
 569          $this->setAdminUser();
 570  
 571          $course = $this->getDataGenerator()->create_course();
 572          $activity = $this->getDataGenerator()->create_module(
 573              'h5pactivity',
 574              ['course' => $course]
 575          );
 576  
 577          $manager = manager::create_from_instance($activity);
 578  
 579          $roleid = $DB->get_field('role', 'id', ['shortname' => 'student']);
 580  
 581          $newcap = ($canview) ? CAP_ALLOW : CAP_PROHIBIT;
 582          role_change_permission($roleid, $manager->get_context(), 'mod/h5pactivity:view', $newcap);
 583  
 584          $newcap = ($cansubmit) ? CAP_ALLOW : CAP_PROHIBIT;
 585          role_change_permission($roleid, $manager->get_context(), 'mod/h5pactivity:submit', $newcap);
 586  
 587          // Teacher with review capability and attempts (should not be listed).
 588          if ($extrarole) {
 589              $user1 = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
 590              $this->generate_fake_attempts($activity, $user1, 1);
 591          }
 592  
 593          // Student with attempts.
 594          $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 595          $this->generate_fake_attempts($activity, $user2, 1);
 596  
 597          // Another student with attempts.
 598          $user3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 599          $this->generate_fake_attempts($activity, $user3, 1);
 600  
 601          $this->assertEquals($result, $manager->count_attempts());
 602      }
 603  
 604      /**
 605       * Data provider for test_count_attempts_all.
 606       *
 607       * @return array
 608       */
 609      public function count_attempts_all_data(): array {
 610          return [
 611              'Students with both view and submit capability' => [true, true, false, 6],
 612              'Students without view but with submit capability' => [false, true, false, 0],
 613              'Students with view but without submit capability' => [true, false, false, 6],
 614              'Students without both view and submit capability' => [false, false, false, 0],
 615              'Students with both view and submit capability and extra role' => [true, true, true, 6],
 616              'Students without view but with submit capability and extra role' => [false, true, true, 0],
 617              'Students with view but without submit capability and extra role' => [true, false, true, 6],
 618              'Students without both view and submit capability and extra role' => [false, false, true, 0],
 619          ];
 620      }
 621  
 622      /**
 623       * Test static test_get_active_users_join of all active participants.
 624       *
 625       * Most method scenarios are tested in test_count_attempts_all so we only
 626       * need to test the with $allpotentialusers true and false.
 627       *
 628       * @dataProvider get_active_users_join_data
 629       * @param bool $allpotentialusers if the join should return all potential users or only the submitted ones.
 630       * @param int $result the expected result
 631       */
 632      public function test_get_active_users_join(bool $allpotentialusers, int $result) {
 633          global $DB;
 634  
 635          $this->resetAfterTest();
 636          $this->setAdminUser();
 637  
 638          $course = $this->getDataGenerator()->create_course();
 639          $activity = $this->getDataGenerator()->create_module(
 640              'h5pactivity',
 641              ['course' => $course]
 642          );
 643  
 644          $manager = manager::create_from_instance($activity);
 645  
 646          $user1 = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
 647          $this->generate_fake_attempts($activity, $user1, 1);
 648  
 649          // Student with attempts.
 650          $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 651          $this->generate_fake_attempts($activity, $user2, 1);
 652  
 653          // 2 more students without attempts.
 654          $this->getDataGenerator()->create_and_enrol($course, 'student');
 655          $this->getDataGenerator()->create_and_enrol($course, 'student');
 656  
 657          $usersjoin = $manager->get_active_users_join($allpotentialusers);
 658  
 659          // Final SQL.
 660          $num = $DB->count_records_sql(
 661              "SELECT COUNT(DISTINCT u.id)
 662                 FROM {user} u $usersjoin->joins
 663                WHERE $usersjoin->wheres",
 664              array_merge($usersjoin->params)
 665          );
 666  
 667          $this->assertEquals($result, $num);
 668      }
 669  
 670      /**
 671       * Data provider for test_get_active_users_join.
 672       *
 673       * @return array
 674       */
 675      public function get_active_users_join_data(): array {
 676          return [
 677              'All potential users' => [
 678                  'allpotentialusers' => true,
 679                  'result' => 3,
 680              ],
 681              'Users with attempts' => [
 682                  'allpotentialusers' => false,
 683                  'result' => 1,
 684              ],
 685          ];
 686      }
 687  
 688      /**
 689       * Test active users joins returns appropriate results for groups
 690       */
 691      public function test_get_active_users_join_groupmode(): void {
 692          global $DB;
 693  
 694          $this->resetAfterTest();
 695          $this->setAdminUser();
 696  
 697          $course = $this->getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1]);
 698  
 699          // Teacher/user one in group one.
 700          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 701          $userone = $this->getDataGenerator()->create_and_enrol($course, 'student');
 702  
 703          $groupone = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 704          $this->getDataGenerator()->create_group_member(['groupid' => $groupone->id, 'userid' => $teacher->id]);
 705          $this->getDataGenerator()->create_group_member(['groupid' => $groupone->id, 'userid' => $userone->id]);
 706  
 707          // User two in group two.
 708          $usertwo = $this->getDataGenerator()->create_and_enrol($course, 'student');
 709  
 710          $grouptwo = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 711          $this->getDataGenerator()->create_group_member(['groupid' => $grouptwo->id, 'userid' => $usertwo->id]);
 712  
 713          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 714          $manager = manager::create_from_instance($activity);
 715  
 716          // Admin user can view all participants.
 717          $usersjoin = $manager->get_active_users_join(true, 0);
 718          $users = $DB->get_fieldset_sql("SELECT u.username FROM {user} u {$usersjoin->joins} WHERE {$usersjoin->wheres}",
 719              $usersjoin->params);
 720  
 721          $this->assertEqualsCanonicalizing([$teacher->username, $userone->username, $usertwo->username], $users);
 722  
 723          // Switch to teacher, who cannot view all participants.
 724          $this->setUser($teacher);
 725  
 726          $usersjoin = $manager->get_active_users_join(true, 0);
 727          $users = $DB->get_fieldset_sql("SELECT u.username FROM {user} u {$usersjoin->joins} WHERE {$usersjoin->wheres}",
 728              $usersjoin->params);
 729  
 730          $this->assertEmpty($users);
 731  
 732          // Teacher can view participants inside group.
 733          $usersjoin = $manager->get_active_users_join(true, $groupone->id);
 734          $users = $DB->get_fieldset_sql("SELECT u.username FROM {user} u {$usersjoin->joins} WHERE {$usersjoin->wheres}",
 735              $usersjoin->params);
 736  
 737          $this->assertEqualsCanonicalizing([$teacher->username, $userone->username], $users);
 738      }
 739  
 740      /**
 741       * Test getting active users join where there are no roles with 'mod/h5pactivity:reviewattempts' capability
 742       */
 743      public function test_get_active_users_join_no_reviewers(): void {
 744          global $DB;
 745  
 746          $this->resetAfterTest();
 747          $this->setAdminUser();
 748  
 749          $course = $this->getDataGenerator()->create_course();
 750          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 751          $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
 752  
 753          $manager = manager::create_from_instance($activity);
 754  
 755          // By default manager and editingteacher can review attempts, prohibit both.
 756          $rolemanager = $DB->get_field('role', 'id', ['shortname' => 'manager']);
 757          role_change_permission($rolemanager, $manager->get_context(), 'mod/h5pactivity:reviewattempts', CAP_PROHIBIT);
 758  
 759          $roleeditingteacher = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
 760          role_change_permission($roleeditingteacher, $manager->get_context(), 'mod/h5pactivity:reviewattempts', CAP_PROHIBIT);
 761  
 762          // Generate users join SQL to find matching users.
 763          $usersjoin = $manager->get_active_users_join(true);
 764          $usernames = $DB->get_fieldset_sql(
 765              "SELECT u.username
 766                 FROM {user} u
 767                      {$usersjoin->joins}
 768                WHERE {$usersjoin->wheres}",
 769              $usersjoin->params
 770          );
 771  
 772          $this->assertEquals([$user->username], $usernames);
 773      }
 774  
 775      /**
 776       * Test static count_attempts.
 777       */
 778      public function test_count_users_attempts() {
 779  
 780          $this->resetAfterTest();
 781          $this->setAdminUser();
 782  
 783          $course = $this->getDataGenerator()->create_course();
 784          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 785                  ['course' => $course]);
 786  
 787          $manager = manager::create_from_instance($activity);
 788  
 789          // User without attempts.
 790          $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 791  
 792          // User with 1 attempt.
 793          $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 794          $this->generate_fake_attempts($activity, $user2, 1);
 795  
 796          // User with 2 attempts.
 797          $user3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 798          $this->generate_fake_attempts($activity, $user3, 1);
 799  
 800          $attempts = $manager->count_users_attempts();
 801          $this->assertArrayNotHasKey($user1->id, $attempts);
 802          $this->assertArrayHasKey($user2->id, $attempts);
 803          $this->assertEquals(4, $attempts[$user2->id]);
 804          $this->assertArrayHasKey($user3->id, $attempts);
 805          $this->assertEquals(4, $attempts[$user3->id]);
 806      }
 807  
 808      /**
 809       * Test static get_report.
 810       *
 811       * @dataProvider get_report_data
 812       * @param int $enabletracking if tracking is enabled
 813       * @param int $reviewmode the attempt review mode
 814       * @param bool $createattempts if the student have attempts
 815       * @param string $role the user role (student or editingteacher)
 816       * @param array $results the expected classname (or null)
 817       */
 818      public function test_get_report(int $enabletracking, int $reviewmode, bool $createattempts,
 819              string $role, array $results) {
 820  
 821          $this->resetAfterTest();
 822          $this->setAdminUser();
 823  
 824          $course = $this->getDataGenerator()->create_course();
 825          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 826                  ['course' => $course, 'enabletracking' => $enabletracking, 'reviewmode' => $reviewmode]);
 827  
 828          $manager = manager::create_from_instance($activity);
 829          $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
 830  
 831          $users = [
 832              'editingteacher' => $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'),
 833              'student' => $this->getDataGenerator()->create_and_enrol($course, 'student'),
 834              'otheruser' => $this->getDataGenerator()->create_and_enrol($course, 'student'),
 835          ];
 836  
 837          $attempts = [];
 838          if ($createattempts) {
 839              $this->generate_fake_attempts($activity, $users['student'], 1);
 840              $this->generate_fake_attempts($activity, $users['otheruser'], 2);
 841              $attempts['student'] = attempt::last_attempt($users['student'], $cm);
 842              $attempts['otheruser'] = attempt::last_attempt($users['otheruser'], $cm);
 843          }
 844  
 845          $classnamebase = 'mod_h5pactivity\\local\\report\\';
 846  
 847          $attemptid = null;
 848          if (isset($attempts['student'])) {
 849              $attemptid = $attempts['student']->get_id() ?? null;
 850          }
 851          $userid = $users['student']->id;
 852  
 853          // Check reports.
 854          $this->setUser($users[$role]);
 855  
 856          $report = $manager->get_report(null, null);
 857          if ($results[0] === null) {
 858              $this->assertNull($report);
 859          } else {
 860              $this->assertEquals($classnamebase.$results[0], get_class($report));
 861          }
 862  
 863          $report = $manager->get_report($userid, null);
 864          if ($results[1] === null) {
 865              $this->assertNull($report);
 866          } else {
 867              $this->assertEquals($classnamebase.$results[1], get_class($report));
 868          }
 869  
 870          $report = $manager->get_report($userid, $attemptid);
 871          if ($results[2] === null) {
 872              $this->assertNull($report);
 873          } else {
 874              $this->assertEquals($classnamebase.$results[2], get_class($report));
 875          }
 876  
 877          // Check that student cannot access another student reports.
 878          if ($role == 'student') {
 879              $attemptid = null;
 880              if (isset($attempts['otheruser'])) {
 881                  $attemptid = $attempts['otheruser']->get_id() ?? null;
 882              }
 883              $userid = $users['otheruser']->id;
 884  
 885              $report = $manager->get_report($userid, null);
 886              $this->assertNull($report);
 887  
 888              $report = $manager->get_report($userid, $attemptid);
 889              $this->assertNull($report);
 890          }
 891      }
 892  
 893      /**
 894       * Data provider for test_get_report.
 895       *
 896       * @return array
 897       */
 898      public function get_report_data(): array {
 899          return [
 900              // No tracking scenarios.
 901              'No tracking, review none, no attempts, teacher' => [
 902                  0, manager::REVIEWNONE, false, 'editingteacher', [null, null, null]
 903              ],
 904              'No tracking, review own, no attempts, teacher' => [
 905                  0, manager::REVIEWCOMPLETION, false, 'editingteacher', [null, null, null]
 906              ],
 907              'No tracking, review none, no attempts, student' => [
 908                  0, manager::REVIEWNONE, false, 'student', [null, null, null]
 909              ],
 910              'No tracking, review own, no attempts, student' => [
 911                  0, manager::REVIEWCOMPLETION, false, 'student', [null, null, null]
 912              ],
 913              'No tracking, review none, with attempts, teacher' => [
 914                  0, manager::REVIEWNONE, true, 'editingteacher', [null, null, null]
 915              ],
 916              'No tracking, review own, with attempts, teacher' => [
 917                  0, manager::REVIEWCOMPLETION, true, 'editingteacher', [null, null, null]
 918              ],
 919              'No tracking, review none, with attempts, student' => [
 920                  0, manager::REVIEWNONE, true, 'student', [null, null, null]
 921              ],
 922              'No tracking, review own, with attempts, student' => [
 923                  0, manager::REVIEWCOMPLETION, true, 'student', [null, null, null]
 924              ],
 925              // Tracking enabled scenarios.
 926              'Tracking enabled, review none, no attempts, teacher' => [
 927                  1, manager::REVIEWNONE, false, 'editingteacher', ['participants', 'attempts', 'attempts']
 928              ],
 929              'Tracking enabled, review own, no attempts, teacher' => [
 930                  1, manager::REVIEWCOMPLETION, false, 'editingteacher', ['participants', 'attempts', 'attempts']
 931              ],
 932              'Tracking enabled, review none, no attempts, student' => [
 933                  1, manager::REVIEWNONE, false, 'student', [null, null, null]
 934              ],
 935              'Tracking enabled, review own, no attempts, student' => [
 936                  1, manager::REVIEWCOMPLETION, false, 'student', ['attempts', 'attempts', 'attempts']
 937              ],
 938              'Tracking enabled, review none, with attempts, teacher' => [
 939                  1, manager::REVIEWNONE, true, 'editingteacher', ['participants', 'attempts', 'results']
 940              ],
 941              'Tracking enabled, review own, with attempts, teacher' => [
 942                  1, manager::REVIEWCOMPLETION, true, 'editingteacher', ['participants', 'attempts', 'results']
 943              ],
 944              'Tracking enabled, review none, with attempts, student' => [
 945                  1, manager::REVIEWNONE, true, 'student', [null, null, null]
 946              ],
 947              'Tracking enabled, review own, with attempts, student' => [
 948                  1, manager::REVIEWCOMPLETION, true, 'student', ['attempts', 'attempts', 'results']
 949              ],
 950          ];
 951      }
 952  
 953      /**
 954       * Test get_attempt method.
 955       *
 956       * @dataProvider get_attempt_data
 957       * @param string $attemptname the attempt to use
 958       * @param string|null $result the expected attempt ID or null for none
 959       */
 960      public function test_get_attempt(string $attemptname, ?string $result): void {
 961  
 962          $this->resetAfterTest();
 963          $this->setAdminUser();
 964  
 965          $course = $this->getDataGenerator()->create_course();
 966  
 967          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 968          $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
 969  
 970          $otheractivity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 971          $othercm = get_coursemodule_from_id('h5pactivity', $otheractivity->cmid, 0, false, MUST_EXIST);
 972  
 973          $manager = manager::create_from_instance($activity);
 974  
 975          $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
 976  
 977          $attempts = ['inexistent' => 0];
 978  
 979          $this->generate_fake_attempts($activity, $user, 1);
 980          $attempt = attempt::last_attempt($user, $cm);
 981          $attempts['current'] = $attempt->get_id();
 982  
 983          $this->generate_fake_attempts($otheractivity, $user, 1);
 984          $attempt = attempt::last_attempt($user, $othercm);
 985          $attempts['other'] = $attempt->get_id();
 986  
 987          $attempt = $manager->get_attempt($attempts[$attemptname]);
 988          if ($result === null) {
 989              $this->assertNull($attempt);
 990          } else {
 991              $this->assertEquals($attempts[$attemptname], $attempt->get_id());
 992              $this->assertEquals($activity->id, $attempt->get_h5pactivityid());
 993              $this->assertEquals($user->id, $attempt->get_userid());
 994              $this->assertEquals(4, $attempt->get_attempt());
 995          }
 996      }
 997  
 998      /**
 999       * Data provider for test_get_attempt.
1000       *
1001       * @return array
1002       */
1003      public function get_attempt_data(): array {
1004          return [
1005              'Get the current activity attempt' => [
1006                  'current', 'current'
1007              ],
1008              'Try to get another activity attempt' => [
1009                  'other', null
1010              ],
1011              'Try to get an inexistent attempt' => [
1012                  'inexistent', null
1013              ],
1014          ];
1015      }
1016  
1017      /**
1018       * Insert fake attempt data into h5pactiviyt_attempts.
1019       *
1020       * This function insert 4 attempts. 3 of them finished with different gradings
1021       * and timestamps and 1 unfinished.
1022       *
1023       * @param stdClass $activity the activity record
1024       * @param stdClass $user user record
1025       * @param int $basescore a score to be used to generate all attempts
1026       */
1027      private function generate_fake_attempts(stdClass $activity, stdClass $user, int $basescore) {
1028          global $DB;
1029  
1030          $attempt = (object)[
1031              'h5pactivityid' => $activity->id,
1032              'userid' => $user->id,
1033              'timecreated' => $basescore,
1034              'timemodified' => ($basescore + 10),
1035              'attempt' => 1,
1036              'rawscore' => $basescore,
1037              'maxscore' => ($basescore + $basescore),
1038              'duration' => $basescore,
1039              'completion' => 1,
1040              'success' => 1,
1041          ];
1042          $attempt->scaled = $attempt->rawscore / $attempt->maxscore;
1043          $DB->insert_record('h5pactivity_attempts', $attempt);
1044  
1045          $attempt = (object)[
1046              'h5pactivityid' => $activity->id,
1047              'userid' => $user->id,
1048              'timecreated' => ($basescore + 20),
1049              'timemodified' => ($basescore + 30),
1050              'attempt' => 2,
1051              'rawscore' => $basescore,
1052              'maxscore' => ($basescore + $basescore - 1),
1053              'duration' => $basescore,
1054              'completion' => 1,
1055              'success' => 1,
1056          ];
1057          $attempt->scaled = $attempt->rawscore / $attempt->maxscore;
1058          $DB->insert_record('h5pactivity_attempts', $attempt);
1059  
1060          $attempt = (object)[
1061              'h5pactivityid' => $activity->id,
1062              'userid' => $user->id,
1063              'timecreated' => ($basescore + 40),
1064              'timemodified' => ($basescore + 50),
1065              'attempt' => 3,
1066              'rawscore' => $basescore,
1067              'maxscore' => ($basescore + $basescore + 1),
1068              'duration' => $basescore,
1069              'completion' => 1,
1070              'success' => 0,
1071          ];
1072          $attempt->scaled = $attempt->rawscore / $attempt->maxscore;
1073          $DB->insert_record('h5pactivity_attempts', $attempt);
1074  
1075          // Unfinished attempt.
1076          $attempt = (object)[
1077              'h5pactivityid' => $activity->id,
1078              'userid' => $user->id,
1079              'timecreated' => ($basescore + 60),
1080              'timemodified' => ($basescore + 60),
1081              'attempt' => 4,
1082              'rawscore' => $basescore,
1083              'maxscore' => $basescore,
1084              'duration' => $basescore,
1085          ];
1086          $DB->insert_record('h5pactivity_attempts', $attempt);
1087      }
1088  }