Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   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 static count_attempts.
 742       */
 743      public function test_count_users_attempts() {
 744  
 745          $this->resetAfterTest();
 746          $this->setAdminUser();
 747  
 748          $course = $this->getDataGenerator()->create_course();
 749          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 750                  ['course' => $course]);
 751  
 752          $manager = manager::create_from_instance($activity);
 753  
 754          // User without attempts.
 755          $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 756  
 757          // User with 1 attempt.
 758          $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 759          $this->generate_fake_attempts($activity, $user2, 1);
 760  
 761          // User with 2 attempts.
 762          $user3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 763          $this->generate_fake_attempts($activity, $user3, 1);
 764  
 765          $attempts = $manager->count_users_attempts();
 766          $this->assertArrayNotHasKey($user1->id, $attempts);
 767          $this->assertArrayHasKey($user2->id, $attempts);
 768          $this->assertEquals(4, $attempts[$user2->id]);
 769          $this->assertArrayHasKey($user3->id, $attempts);
 770          $this->assertEquals(4, $attempts[$user3->id]);
 771      }
 772  
 773      /**
 774       * Test static get_report.
 775       *
 776       * @dataProvider get_report_data
 777       * @param int $enabletracking if tracking is enabled
 778       * @param int $reviewmode the attempt review mode
 779       * @param bool $createattempts if the student have attempts
 780       * @param string $role the user role (student or editingteacher)
 781       * @param array $results the expected classname (or null)
 782       */
 783      public function test_get_report(int $enabletracking, int $reviewmode, bool $createattempts,
 784              string $role, array $results) {
 785  
 786          $this->resetAfterTest();
 787          $this->setAdminUser();
 788  
 789          $course = $this->getDataGenerator()->create_course();
 790          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 791                  ['course' => $course, 'enabletracking' => $enabletracking, 'reviewmode' => $reviewmode]);
 792  
 793          $manager = manager::create_from_instance($activity);
 794          $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
 795  
 796          $users = [
 797              'editingteacher' => $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'),
 798              'student' => $this->getDataGenerator()->create_and_enrol($course, 'student'),
 799              'otheruser' => $this->getDataGenerator()->create_and_enrol($course, 'student'),
 800          ];
 801  
 802          $attempts = [];
 803          if ($createattempts) {
 804              $this->generate_fake_attempts($activity, $users['student'], 1);
 805              $this->generate_fake_attempts($activity, $users['otheruser'], 2);
 806              $attempts['student'] = attempt::last_attempt($users['student'], $cm);
 807              $attempts['otheruser'] = attempt::last_attempt($users['otheruser'], $cm);
 808          }
 809  
 810          $classnamebase = 'mod_h5pactivity\\local\\report\\';
 811  
 812          $attemptid = null;
 813          if (isset($attempts['student'])) {
 814              $attemptid = $attempts['student']->get_id() ?? null;
 815          }
 816          $userid = $users['student']->id;
 817  
 818          // Check reports.
 819          $this->setUser($users[$role]);
 820  
 821          $report = $manager->get_report(null, null);
 822          if ($results[0] === null) {
 823              $this->assertNull($report);
 824          } else {
 825              $this->assertEquals($classnamebase.$results[0], get_class($report));
 826          }
 827  
 828          $report = $manager->get_report($userid, null);
 829          if ($results[1] === null) {
 830              $this->assertNull($report);
 831          } else {
 832              $this->assertEquals($classnamebase.$results[1], get_class($report));
 833          }
 834  
 835          $report = $manager->get_report($userid, $attemptid);
 836          if ($results[2] === null) {
 837              $this->assertNull($report);
 838          } else {
 839              $this->assertEquals($classnamebase.$results[2], get_class($report));
 840          }
 841  
 842          // Check that student cannot access another student reports.
 843          if ($role == 'student') {
 844              $attemptid = null;
 845              if (isset($attempts['otheruser'])) {
 846                  $attemptid = $attempts['otheruser']->get_id() ?? null;
 847              }
 848              $userid = $users['otheruser']->id;
 849  
 850              $report = $manager->get_report($userid, null);
 851              $this->assertNull($report);
 852  
 853              $report = $manager->get_report($userid, $attemptid);
 854              $this->assertNull($report);
 855          }
 856      }
 857  
 858      /**
 859       * Data provider for test_get_report.
 860       *
 861       * @return array
 862       */
 863      public function get_report_data(): array {
 864          return [
 865              // No tracking scenarios.
 866              'No tracking, review none, no attempts, teacher' => [
 867                  0, manager::REVIEWNONE, false, 'editingteacher', [null, null, null]
 868              ],
 869              'No tracking, review own, no attempts, teacher' => [
 870                  0, manager::REVIEWCOMPLETION, false, 'editingteacher', [null, null, null]
 871              ],
 872              'No tracking, review none, no attempts, student' => [
 873                  0, manager::REVIEWNONE, false, 'student', [null, null, null]
 874              ],
 875              'No tracking, review own, no attempts, student' => [
 876                  0, manager::REVIEWCOMPLETION, false, 'student', [null, null, null]
 877              ],
 878              'No tracking, review none, with attempts, teacher' => [
 879                  0, manager::REVIEWNONE, true, 'editingteacher', [null, null, null]
 880              ],
 881              'No tracking, review own, with attempts, teacher' => [
 882                  0, manager::REVIEWCOMPLETION, true, 'editingteacher', [null, null, null]
 883              ],
 884              'No tracking, review none, with attempts, student' => [
 885                  0, manager::REVIEWNONE, true, 'student', [null, null, null]
 886              ],
 887              'No tracking, review own, with attempts, student' => [
 888                  0, manager::REVIEWCOMPLETION, true, 'student', [null, null, null]
 889              ],
 890              // Tracking enabled scenarios.
 891              'Tracking enabled, review none, no attempts, teacher' => [
 892                  1, manager::REVIEWNONE, false, 'editingteacher', ['participants', 'attempts', 'attempts']
 893              ],
 894              'Tracking enabled, review own, no attempts, teacher' => [
 895                  1, manager::REVIEWCOMPLETION, false, 'editingteacher', ['participants', 'attempts', 'attempts']
 896              ],
 897              'Tracking enabled, review none, no attempts, student' => [
 898                  1, manager::REVIEWNONE, false, 'student', [null, null, null]
 899              ],
 900              'Tracking enabled, review own, no attempts, student' => [
 901                  1, manager::REVIEWCOMPLETION, false, 'student', ['attempts', 'attempts', 'attempts']
 902              ],
 903              'Tracking enabled, review none, with attempts, teacher' => [
 904                  1, manager::REVIEWNONE, true, 'editingteacher', ['participants', 'attempts', 'results']
 905              ],
 906              'Tracking enabled, review own, with attempts, teacher' => [
 907                  1, manager::REVIEWCOMPLETION, true, 'editingteacher', ['participants', 'attempts', 'results']
 908              ],
 909              'Tracking enabled, review none, with attempts, student' => [
 910                  1, manager::REVIEWNONE, true, 'student', [null, null, null]
 911              ],
 912              'Tracking enabled, review own, with attempts, student' => [
 913                  1, manager::REVIEWCOMPLETION, true, 'student', ['attempts', 'attempts', 'results']
 914              ],
 915          ];
 916      }
 917  
 918      /**
 919       * Test get_attempt method.
 920       *
 921       * @dataProvider get_attempt_data
 922       * @param string $attemptname the attempt to use
 923       * @param string|null $result the expected attempt ID or null for none
 924       */
 925      public function test_get_attempt(string $attemptname, ?string $result): void {
 926  
 927          $this->resetAfterTest();
 928          $this->setAdminUser();
 929  
 930          $course = $this->getDataGenerator()->create_course();
 931  
 932          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 933          $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
 934  
 935          $otheractivity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 936          $othercm = get_coursemodule_from_id('h5pactivity', $otheractivity->cmid, 0, false, MUST_EXIST);
 937  
 938          $manager = manager::create_from_instance($activity);
 939  
 940          $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
 941  
 942          $attempts = ['inexistent' => 0];
 943  
 944          $this->generate_fake_attempts($activity, $user, 1);
 945          $attempt = attempt::last_attempt($user, $cm);
 946          $attempts['current'] = $attempt->get_id();
 947  
 948          $this->generate_fake_attempts($otheractivity, $user, 1);
 949          $attempt = attempt::last_attempt($user, $othercm);
 950          $attempts['other'] = $attempt->get_id();
 951  
 952          $attempt = $manager->get_attempt($attempts[$attemptname]);
 953          if ($result === null) {
 954              $this->assertNull($attempt);
 955          } else {
 956              $this->assertEquals($attempts[$attemptname], $attempt->get_id());
 957              $this->assertEquals($activity->id, $attempt->get_h5pactivityid());
 958              $this->assertEquals($user->id, $attempt->get_userid());
 959              $this->assertEquals(4, $attempt->get_attempt());
 960          }
 961      }
 962  
 963      /**
 964       * Data provider for test_get_attempt.
 965       *
 966       * @return array
 967       */
 968      public function get_attempt_data(): array {
 969          return [
 970              'Get the current activity attempt' => [
 971                  'current', 'current'
 972              ],
 973              'Try to get another activity attempt' => [
 974                  'other', null
 975              ],
 976              'Try to get an inexistent attempt' => [
 977                  'inexistent', null
 978              ],
 979          ];
 980      }
 981  
 982      /**
 983       * Insert fake attempt data into h5pactiviyt_attempts.
 984       *
 985       * This function insert 4 attempts. 3 of them finished with different gradings
 986       * and timestamps and 1 unfinished.
 987       *
 988       * @param stdClass $activity the activity record
 989       * @param stdClass $user user record
 990       * @param int $basescore a score to be used to generate all attempts
 991       */
 992      private function generate_fake_attempts(stdClass $activity, stdClass $user, int $basescore) {
 993          global $DB;
 994  
 995          $attempt = (object)[
 996              'h5pactivityid' => $activity->id,
 997              'userid' => $user->id,
 998              'timecreated' => $basescore,
 999              'timemodified' => ($basescore + 10),
1000              'attempt' => 1,
1001              'rawscore' => $basescore,
1002              'maxscore' => ($basescore + $basescore),
1003              'duration' => $basescore,
1004              'completion' => 1,
1005              'success' => 1,
1006          ];
1007          $attempt->scaled = $attempt->rawscore / $attempt->maxscore;
1008          $DB->insert_record('h5pactivity_attempts', $attempt);
1009  
1010          $attempt = (object)[
1011              'h5pactivityid' => $activity->id,
1012              'userid' => $user->id,
1013              'timecreated' => ($basescore + 20),
1014              'timemodified' => ($basescore + 30),
1015              'attempt' => 2,
1016              'rawscore' => $basescore,
1017              'maxscore' => ($basescore + $basescore - 1),
1018              'duration' => $basescore,
1019              'completion' => 1,
1020              'success' => 1,
1021          ];
1022          $attempt->scaled = $attempt->rawscore / $attempt->maxscore;
1023          $DB->insert_record('h5pactivity_attempts', $attempt);
1024  
1025          $attempt = (object)[
1026              'h5pactivityid' => $activity->id,
1027              'userid' => $user->id,
1028              'timecreated' => ($basescore + 40),
1029              'timemodified' => ($basescore + 50),
1030              'attempt' => 3,
1031              'rawscore' => $basescore,
1032              'maxscore' => ($basescore + $basescore + 1),
1033              'duration' => $basescore,
1034              'completion' => 1,
1035              'success' => 0,
1036          ];
1037          $attempt->scaled = $attempt->rawscore / $attempt->maxscore;
1038          $DB->insert_record('h5pactivity_attempts', $attempt);
1039  
1040          // Unfinished attempt.
1041          $attempt = (object)[
1042              'h5pactivityid' => $activity->id,
1043              'userid' => $user->id,
1044              'timecreated' => ($basescore + 60),
1045              'timemodified' => ($basescore + 60),
1046              'attempt' => 4,
1047              'rawscore' => $basescore,
1048              'maxscore' => $basescore,
1049              'duration' => $basescore,
1050          ];
1051          $DB->insert_record('h5pactivity_attempts', $attempt);
1052      }
1053  }