Search moodle.org's
Developer Documentation

See Release Notes

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

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

   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          // User three in no group.
 714          $userthree = $this->getDataGenerator()->create_and_enrol($course, 'student');
 715  
 716          // User four in a non-participation group.
 717          $userfour = $this->getDataGenerator()->create_and_enrol($course, 'student');
 718          $groupthree = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'participation' => 0]);
 719          $this->getDataGenerator()->create_group_member(['groupid' => $groupthree->id, 'userid' => $userfour->id]);
 720  
 721          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 722          $manager = manager::create_from_instance($activity);
 723  
 724          // Admin user can view all participants (any group and none).
 725          $usersjoin = $manager->get_active_users_join(true, 0);
 726          $users = $DB->get_fieldset_sql("SELECT u.username FROM {user} u {$usersjoin->joins} WHERE {$usersjoin->wheres}",
 727              $usersjoin->params);
 728  
 729          $this->assertEqualsCanonicalizing(
 730                  [$teacher->username, $userone->username, $usertwo->username, $userthree->username, $userfour->username], $users);
 731  
 732          // Switch to teacher, who cannot view all participants.
 733          $this->setUser($teacher);
 734  
 735          $usersjoin = $manager->get_active_users_join(true, 0);
 736          $users = $DB->get_fieldset_sql("SELECT u.username FROM {user} u {$usersjoin->joins} WHERE {$usersjoin->wheres}",
 737              $usersjoin->params);
 738  
 739          $this->assertEmpty($users);
 740  
 741          // Teacher can view participants inside group.
 742          $usersjoin = $manager->get_active_users_join(true, $groupone->id);
 743          $users = $DB->get_fieldset_sql("SELECT u.username FROM {user} u {$usersjoin->joins} WHERE {$usersjoin->wheres}",
 744              $usersjoin->params);
 745  
 746          $this->assertEqualsCanonicalizing([$teacher->username, $userone->username], $users);
 747      }
 748  
 749      /**
 750       * Test getting active users join where there are no roles with 'mod/h5pactivity:reviewattempts' capability
 751       */
 752      public function test_get_active_users_join_no_reviewers(): void {
 753          global $DB;
 754  
 755          $this->resetAfterTest();
 756          $this->setAdminUser();
 757  
 758          $course = $this->getDataGenerator()->create_course();
 759          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 760          $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
 761  
 762          $manager = manager::create_from_instance($activity);
 763  
 764          // By default manager and editingteacher can review attempts, prohibit both.
 765          $rolemanager = $DB->get_field('role', 'id', ['shortname' => 'manager']);
 766          role_change_permission($rolemanager, $manager->get_context(), 'mod/h5pactivity:reviewattempts', CAP_PROHIBIT);
 767  
 768          $roleeditingteacher = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
 769          role_change_permission($roleeditingteacher, $manager->get_context(), 'mod/h5pactivity:reviewattempts', CAP_PROHIBIT);
 770  
 771          // Generate users join SQL to find matching users.
 772          $usersjoin = $manager->get_active_users_join(true);
 773          $usernames = $DB->get_fieldset_sql(
 774              "SELECT u.username
 775                 FROM {user} u
 776                      {$usersjoin->joins}
 777                WHERE {$usersjoin->wheres}",
 778              $usersjoin->params
 779          );
 780  
 781          $this->assertEquals([$user->username], $usernames);
 782      }
 783  
 784      /**
 785       * Test static count_attempts.
 786       */
 787      public function test_count_users_attempts() {
 788  
 789          $this->resetAfterTest();
 790          $this->setAdminUser();
 791  
 792          $course = $this->getDataGenerator()->create_course();
 793          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 794                  ['course' => $course]);
 795  
 796          $manager = manager::create_from_instance($activity);
 797  
 798          // User without attempts.
 799          $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 800  
 801          // User with 1 attempt.
 802          $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 803          $this->generate_fake_attempts($activity, $user2, 1);
 804  
 805          // User with 2 attempts.
 806          $user3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 807          $this->generate_fake_attempts($activity, $user3, 1);
 808  
 809          $attempts = $manager->count_users_attempts();
 810          $this->assertArrayNotHasKey($user1->id, $attempts);
 811          $this->assertArrayHasKey($user2->id, $attempts);
 812          $this->assertEquals(4, $attempts[$user2->id]);
 813          $this->assertArrayHasKey($user3->id, $attempts);
 814          $this->assertEquals(4, $attempts[$user3->id]);
 815      }
 816  
 817      /**
 818       * Test static get_report.
 819       *
 820       * @dataProvider get_report_data
 821       * @param int $enabletracking if tracking is enabled
 822       * @param int $reviewmode the attempt review mode
 823       * @param bool $createattempts if the student have attempts
 824       * @param string $role the user role (student or editingteacher)
 825       * @param array $results the expected classname (or null)
 826       */
 827      public function test_get_report(int $enabletracking, int $reviewmode, bool $createattempts,
 828              string $role, array $results) {
 829  
 830          $this->resetAfterTest();
 831          $this->setAdminUser();
 832  
 833          $course = $this->getDataGenerator()->create_course();
 834          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 835                  ['course' => $course, 'enabletracking' => $enabletracking, 'reviewmode' => $reviewmode]);
 836  
 837          $manager = manager::create_from_instance($activity);
 838          $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
 839  
 840          $users = [
 841              'editingteacher' => $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'),
 842              'student' => $this->getDataGenerator()->create_and_enrol($course, 'student'),
 843              'otheruser' => $this->getDataGenerator()->create_and_enrol($course, 'student'),
 844          ];
 845  
 846          $attempts = [];
 847          if ($createattempts) {
 848              $this->generate_fake_attempts($activity, $users['student'], 1);
 849              $this->generate_fake_attempts($activity, $users['otheruser'], 2);
 850              $attempts['student'] = attempt::last_attempt($users['student'], $cm);
 851              $attempts['otheruser'] = attempt::last_attempt($users['otheruser'], $cm);
 852          }
 853  
 854          $classnamebase = 'mod_h5pactivity\\local\\report\\';
 855  
 856          $attemptid = null;
 857          if (isset($attempts['student'])) {
 858              $attemptid = $attempts['student']->get_id() ?? null;
 859          }
 860          $userid = $users['student']->id;
 861  
 862          // Check reports.
 863          $this->setUser($users[$role]);
 864  
 865          $report = $manager->get_report(null, null);
 866          if ($results[0] === null) {
 867              $this->assertNull($report);
 868          } else {
 869              $this->assertEquals($classnamebase.$results[0], get_class($report));
 870          }
 871  
 872          $report = $manager->get_report($userid, null);
 873          if ($results[1] === null) {
 874              $this->assertNull($report);
 875          } else {
 876              $this->assertEquals($classnamebase.$results[1], get_class($report));
 877          }
 878  
 879          $report = $manager->get_report($userid, $attemptid);
 880          if ($results[2] === null) {
 881              $this->assertNull($report);
 882          } else {
 883              $this->assertEquals($classnamebase.$results[2], get_class($report));
 884          }
 885  
 886          // Check that student cannot access another student reports.
 887          if ($role == 'student') {
 888              $attemptid = null;
 889              if (isset($attempts['otheruser'])) {
 890                  $attemptid = $attempts['otheruser']->get_id() ?? null;
 891              }
 892              $userid = $users['otheruser']->id;
 893  
 894              $report = $manager->get_report($userid, null);
 895              $this->assertNull($report);
 896  
 897              $report = $manager->get_report($userid, $attemptid);
 898              $this->assertNull($report);
 899          }
 900      }
 901  
 902      /**
 903       * Data provider for test_get_report.
 904       *
 905       * @return array
 906       */
 907      public function get_report_data(): array {
 908          return [
 909              // No tracking scenarios.
 910              'No tracking, review none, no attempts, teacher' => [
 911                  0, manager::REVIEWNONE, false, 'editingteacher', [null, null, null]
 912              ],
 913              'No tracking, review own, no attempts, teacher' => [
 914                  0, manager::REVIEWCOMPLETION, false, 'editingteacher', [null, null, null]
 915              ],
 916              'No tracking, review none, no attempts, student' => [
 917                  0, manager::REVIEWNONE, false, 'student', [null, null, null]
 918              ],
 919              'No tracking, review own, no attempts, student' => [
 920                  0, manager::REVIEWCOMPLETION, false, 'student', [null, null, null]
 921              ],
 922              'No tracking, review none, with attempts, teacher' => [
 923                  0, manager::REVIEWNONE, true, 'editingteacher', [null, null, null]
 924              ],
 925              'No tracking, review own, with attempts, teacher' => [
 926                  0, manager::REVIEWCOMPLETION, true, 'editingteacher', [null, null, null]
 927              ],
 928              'No tracking, review none, with attempts, student' => [
 929                  0, manager::REVIEWNONE, true, 'student', [null, null, null]
 930              ],
 931              'No tracking, review own, with attempts, student' => [
 932                  0, manager::REVIEWCOMPLETION, true, 'student', [null, null, null]
 933              ],
 934              // Tracking enabled scenarios.
 935              'Tracking enabled, review none, no attempts, teacher' => [
 936                  1, manager::REVIEWNONE, false, 'editingteacher', ['participants', 'attempts', 'attempts']
 937              ],
 938              'Tracking enabled, review own, no attempts, teacher' => [
 939                  1, manager::REVIEWCOMPLETION, false, 'editingteacher', ['participants', 'attempts', 'attempts']
 940              ],
 941              'Tracking enabled, review none, no attempts, student' => [
 942                  1, manager::REVIEWNONE, false, 'student', [null, null, null]
 943              ],
 944              'Tracking enabled, review own, no attempts, student' => [
 945                  1, manager::REVIEWCOMPLETION, false, 'student', ['attempts', 'attempts', 'attempts']
 946              ],
 947              'Tracking enabled, review none, with attempts, teacher' => [
 948                  1, manager::REVIEWNONE, true, 'editingteacher', ['participants', 'attempts', 'results']
 949              ],
 950              'Tracking enabled, review own, with attempts, teacher' => [
 951                  1, manager::REVIEWCOMPLETION, true, 'editingteacher', ['participants', 'attempts', 'results']
 952              ],
 953              'Tracking enabled, review none, with attempts, student' => [
 954                  1, manager::REVIEWNONE, true, 'student', [null, null, null]
 955              ],
 956              'Tracking enabled, review own, with attempts, student' => [
 957                  1, manager::REVIEWCOMPLETION, true, 'student', ['attempts', 'attempts', 'results']
 958              ],
 959          ];
 960      }
 961  
 962      /**
 963       * Test get_attempt method.
 964       *
 965       * @dataProvider get_attempt_data
 966       * @param string $attemptname the attempt to use
 967       * @param string|null $result the expected attempt ID or null for none
 968       */
 969      public function test_get_attempt(string $attemptname, ?string $result): void {
 970  
 971          $this->resetAfterTest();
 972          $this->setAdminUser();
 973  
 974          $course = $this->getDataGenerator()->create_course();
 975  
 976          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 977          $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
 978  
 979          $otheractivity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 980          $othercm = get_coursemodule_from_id('h5pactivity', $otheractivity->cmid, 0, false, MUST_EXIST);
 981  
 982          $manager = manager::create_from_instance($activity);
 983  
 984          $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
 985  
 986          $attempts = ['inexistent' => 0];
 987  
 988          $this->generate_fake_attempts($activity, $user, 1);
 989          $attempt = attempt::last_attempt($user, $cm);
 990          $attempts['current'] = $attempt->get_id();
 991  
 992          $this->generate_fake_attempts($otheractivity, $user, 1);
 993          $attempt = attempt::last_attempt($user, $othercm);
 994          $attempts['other'] = $attempt->get_id();
 995  
 996          $attempt = $manager->get_attempt($attempts[$attemptname]);
 997          if ($result === null) {
 998              $this->assertNull($attempt);
 999          } else {
1000              $this->assertEquals($attempts[$attemptname], $attempt->get_id());
1001              $this->assertEquals($activity->id, $attempt->get_h5pactivityid());
1002              $this->assertEquals($user->id, $attempt->get_userid());
1003              $this->assertEquals(4, $attempt->get_attempt());
1004          }
1005      }
1006  
1007      /**
1008       * Data provider for test_get_attempt.
1009       *
1010       * @return array
1011       */
1012      public function get_attempt_data(): array {
1013          return [
1014              'Get the current activity attempt' => [
1015                  'current', 'current'
1016              ],
1017              'Try to get another activity attempt' => [
1018                  'other', null
1019              ],
1020              'Try to get an inexistent attempt' => [
1021                  'inexistent', null
1022              ],
1023          ];
1024      }
1025  
1026      /**
1027       * Insert fake attempt data into h5pactiviyt_attempts.
1028       *
1029       * This function insert 4 attempts. 3 of them finished with different gradings
1030       * and timestamps and 1 unfinished.
1031       *
1032       * @param stdClass $activity the activity record
1033       * @param stdClass $user user record
1034       * @param int $basescore a score to be used to generate all attempts
1035       */
1036      private function generate_fake_attempts(stdClass $activity, stdClass $user, int $basescore) {
1037          global $DB;
1038  
1039          $attempt = (object)[
1040              'h5pactivityid' => $activity->id,
1041              'userid' => $user->id,
1042              'timecreated' => $basescore,
1043              'timemodified' => ($basescore + 10),
1044              'attempt' => 1,
1045              'rawscore' => $basescore,
1046              'maxscore' => ($basescore + $basescore),
1047              'duration' => $basescore,
1048              'completion' => 1,
1049              'success' => 1,
1050          ];
1051          $attempt->scaled = $attempt->rawscore / $attempt->maxscore;
1052          $DB->insert_record('h5pactivity_attempts', $attempt);
1053  
1054          $attempt = (object)[
1055              'h5pactivityid' => $activity->id,
1056              'userid' => $user->id,
1057              'timecreated' => ($basescore + 20),
1058              'timemodified' => ($basescore + 30),
1059              'attempt' => 2,
1060              'rawscore' => $basescore,
1061              'maxscore' => ($basescore + $basescore - 1),
1062              'duration' => $basescore,
1063              'completion' => 1,
1064              'success' => 1,
1065          ];
1066          $attempt->scaled = $attempt->rawscore / $attempt->maxscore;
1067          $DB->insert_record('h5pactivity_attempts', $attempt);
1068  
1069          $attempt = (object)[
1070              'h5pactivityid' => $activity->id,
1071              'userid' => $user->id,
1072              'timecreated' => ($basescore + 40),
1073              'timemodified' => ($basescore + 50),
1074              'attempt' => 3,
1075              'rawscore' => $basescore,
1076              'maxscore' => ($basescore + $basescore + 1),
1077              'duration' => $basescore,
1078              'completion' => 1,
1079              'success' => 0,
1080          ];
1081          $attempt->scaled = $attempt->rawscore / $attempt->maxscore;
1082          $DB->insert_record('h5pactivity_attempts', $attempt);
1083  
1084          // Unfinished attempt.
1085          $attempt = (object)[
1086              'h5pactivityid' => $activity->id,
1087              'userid' => $user->id,
1088              'timecreated' => ($basescore + 60),
1089              'timemodified' => ($basescore + 60),
1090              'attempt' => 4,
1091              'rawscore' => $basescore,
1092              'maxscore' => $basescore,
1093              'duration' => $basescore,
1094          ];
1095          $DB->insert_record('h5pactivity_attempts', $attempt);
1096      }
1097  }