Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

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