MDL-83899 mod_scorm: Add course overview page
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace mod_scorm\courseformat;
|
||||
|
||||
use cm_info;
|
||||
use core_calendar\output\humandate;
|
||||
use core_courseformat\local\overview\overviewitem;
|
||||
use core_courseformat\output\local\overview\overviewdialog;
|
||||
use core\output\action_link;
|
||||
use core\output\local\properties\text_align;
|
||||
use core\output\local\properties\button;
|
||||
use core\url;
|
||||
use mod_data\dates;
|
||||
use mod_scorm\manager;
|
||||
|
||||
/**
|
||||
* SCORM activity overview integration.
|
||||
*
|
||||
* @package mod_scorm
|
||||
* @copyright 2025 Laurent David <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class overview extends \core_courseformat\activityoverviewbase {
|
||||
/**
|
||||
* @var manager the SCORM manager instance.
|
||||
*/
|
||||
protected manager $manager;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param cm_info $cm the course module instance.
|
||||
* @param \core\output\renderer_helper $rendererhelper the renderer helper.
|
||||
*/
|
||||
public function __construct(
|
||||
cm_info $cm,
|
||||
/** @var \core\output\renderer_helper $rendererhelper the renderer helper */
|
||||
protected readonly \core\output\renderer_helper $rendererhelper,
|
||||
) {
|
||||
parent::__construct($cm);
|
||||
$this->manager = manager::create_from_coursemodule($cm);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_due_date_overview(): ?overviewitem {
|
||||
global $USER;
|
||||
|
||||
$dates = dates::get_dates_for_module($this->cm, $USER->id);
|
||||
$duedate = null;
|
||||
foreach ($dates as $date) {
|
||||
if ($date['dataid'] === 'timeclose') {
|
||||
$duedate = $date['timestamp'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($duedate)) {
|
||||
return new overviewitem(
|
||||
name: get_string('duedate', 'assign'),
|
||||
value: null,
|
||||
content: '-',
|
||||
);
|
||||
}
|
||||
|
||||
$content = humandate::create_from_timestamp($duedate);
|
||||
|
||||
return new overviewitem(
|
||||
name: get_string('duedate', 'assign'),
|
||||
value: $duedate,
|
||||
content: $content,
|
||||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_actions_overview(): ?overviewitem {
|
||||
if (!$this->manager->can_view_reports()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$viewresults = get_string('view');
|
||||
$content = new action_link(
|
||||
url: new url('/mod/scorm/report.php', ['id' => $this->cm->id]),
|
||||
text: $viewresults,
|
||||
attributes: ['class' => button::SECONDARY_OUTLINE->classes()],
|
||||
);
|
||||
|
||||
return new overviewitem(
|
||||
name: get_string('actions'),
|
||||
value: '',
|
||||
content: $content,
|
||||
textalign: text_align::CENTER,
|
||||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_extra_overview_items(): array {
|
||||
return [
|
||||
'attempted' => $this->get_extra_studentsattempted_overview(),
|
||||
'totalattempts' => $this->get_extra_totalattempts_overview(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the students who attempted.
|
||||
*
|
||||
* @return overviewitem|null The overview item.
|
||||
*/
|
||||
private function get_extra_studentsattempted_overview(): ?overviewitem {
|
||||
if (!$this->manager->can_view_reports()) {
|
||||
return null;
|
||||
}
|
||||
$attemptcount = $this->manager->count_users_who_attempted();
|
||||
$participantscount = $this->manager->count_participants();
|
||||
$params = [
|
||||
'count' => $attemptcount,
|
||||
'total' => $participantscount,
|
||||
];
|
||||
$content = get_string('count_of_total', 'core', $params);
|
||||
return new overviewitem(
|
||||
name: get_string('studentattempted', 'mod_scorm'),
|
||||
value: $attemptcount,
|
||||
content: $content,
|
||||
textalign: text_align::END,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "Total attempts" colum data.
|
||||
*
|
||||
* @return overviewitem|null The overview item.
|
||||
*/
|
||||
private function get_extra_totalattempts_overview(): ?overviewitem {
|
||||
if (!$this->manager->can_view_reports()) {
|
||||
return null;
|
||||
}
|
||||
$totalattempts = $this->manager->count_users_who_attempted();
|
||||
$userswhocanattempt = $this->manager->count_participants();
|
||||
$maxattempts = $this->manager->get_max_attempts();
|
||||
if ($maxattempts === 0) {
|
||||
$maxattemptstext = get_string('unlimited');
|
||||
} else {
|
||||
$maxattemptstext = (string) $maxattempts;
|
||||
}
|
||||
|
||||
$content = new overviewdialog(
|
||||
buttoncontent: $totalattempts,
|
||||
title: get_string('totalattempts', 'mod_scorm'),
|
||||
definition: ['buttonclasses' => button::SECONDARY_OUTLINE->classes() . ' dropdown-toggle'],
|
||||
);
|
||||
$gradingmethod = $this->manager->get_grading_method();
|
||||
$content->add_item(
|
||||
get_string('gradingmethod', 'grading'),
|
||||
$gradingmethod ?? '-',
|
||||
);
|
||||
|
||||
$content->add_item(get_string('allowedattemptsstudent', 'mod_scorm'), $maxattemptstext);
|
||||
$averageattempts = 0;
|
||||
if ($userswhocanattempt > 0) {
|
||||
$averageattempts = (int) round($totalattempts / $userswhocanattempt);
|
||||
}
|
||||
$content->add_item(get_string('averageattemptperstudent', 'mod_scorm'), $averageattempts);
|
||||
|
||||
return new overviewitem(
|
||||
name: get_string('totalattempts', 'mod_scorm'),
|
||||
value: $totalattempts,
|
||||
content: $content,
|
||||
textalign: text_align::START,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ $string['adminsettings'] = 'Admin settings';
|
||||
$string['advanced'] = 'Parameters';
|
||||
$string['aliasonly'] = 'When selecting an imsmanifest.xml file from a repository you must use an alias/shortcut for this file.';
|
||||
$string['allowapidebug'] = 'Activate API debug and tracing (set the capture mask with apidebugmask)';
|
||||
$string['allowedattemptsstudent'] = 'Allowed attempts per student';
|
||||
$string['allowtypeexternal'] = 'Enable external package type';
|
||||
$string['allowtypeexternalaicc'] = 'Enable direct AICC URL';
|
||||
$string['allowtypeexternalaicc_desc'] = 'If enabled this allows a direct url to a simple AICC package';
|
||||
@@ -64,6 +65,7 @@ $string['autocontinue'] = 'Auto-continue';
|
||||
$string['autocontinue_help'] = 'If enabled, subsequent learning objects are launched automatically, otherwise the Continue button must be used.';
|
||||
$string['autocontinuedesc'] = 'If enabled, subsequent learning objects are launched automatically, otherwise the Continue button must be used.';
|
||||
$string['averageattempt'] = 'Average attempts';
|
||||
$string['averageattemptperstudent'] = 'Average attempts per student';
|
||||
$string['badmanifest'] = 'Some manifest errors: see errors log';
|
||||
$string['badimsmanifestlocation'] = 'An imsmanifest.xml file was found but it was not in the root of your zip file, please re-package your SCORM';
|
||||
$string['badarchive'] = 'You must provide a valid zip file';
|
||||
@@ -413,6 +415,7 @@ $string['started'] = 'Started on';
|
||||
$string['status'] = 'Status';
|
||||
$string['statusbar'] = 'Show the status bar';
|
||||
$string['student_response'] = 'Response';
|
||||
$string['studentattempted'] = 'Student who attempted';
|
||||
$string['subplugintype_scormreport'] = 'Report';
|
||||
$string['subplugintype_scormreport_plural'] = 'Reports';
|
||||
$string['suspended'] = 'Suspended';
|
||||
@@ -423,6 +426,7 @@ $string['title'] = 'Title';
|
||||
$string['toolbar'] = 'Show the toolbar';
|
||||
$string['too_many_attributes'] = 'Tag {$a->tag} has too many attributes';
|
||||
$string['too_many_children'] = 'Tag {$a->tag} has too many children';
|
||||
$string['totalattempts'] = 'Total attempts';
|
||||
$string['totaltime'] = 'Time';
|
||||
$string['trackingloose'] = 'WARNING: The tracking data of this package will be lost!';
|
||||
$string['type'] = 'Type';
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
@mod @mod_scorm
|
||||
Feature: Testing overview integration in mod_scorm
|
||||
In order to summarize the scorm activities
|
||||
As a user
|
||||
I need to be able to see the scorm overview
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| student1 | Username | 1 |
|
||||
| student2 | Username | 2 |
|
||||
| student3 | Username | 3 |
|
||||
| student4 | Username | 4 |
|
||||
| student5 | Username | 5 |
|
||||
| student6 | Username | 6 |
|
||||
| student7 | Username | 7 |
|
||||
| student8 | Username | 8 |
|
||||
| teacher1 | Teacher | T |
|
||||
| editingteacher1 | EditingTeacher | T |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
| Course 2 | C2 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
| student3 | C1 | student |
|
||||
| teacher1 | C1 | teacher |
|
||||
| editingteacher1 | C1 | editingteacher |
|
||||
And the following "activities" exist:
|
||||
| activity | course | name | packagefilepath | forcenewattempt | idnumber | timeclose | grademethod |
|
||||
| scorm | C1 | Scorm 1 | mod/scorm/tests/packages/singlescobasic.zip | 0 | scorm1 | 1 January 2040 | 3 |
|
||||
| scorm | C1 | Scorm 2 | mod/scorm/tests/packages/singlescobasic.zip | 0 | scorm2 | | 3 |
|
||||
| scorm | C2 | Scorm 3 | mod/scorm/tests/packages/singlescobasic.zip | 0 | scorm3 | | 3 |
|
||||
And the following "mod_scorm > attempts" exist:
|
||||
| scorm | user | attempt | element | value | scoidentifier |
|
||||
| scorm1 | student1 | 1 | cmi.core.score.raw | 50 | item_1 |
|
||||
| scorm1 | student1 | 1 | cmi.completion_status | completed | item_1 |
|
||||
| scorm1 | student2 | 1 | cmi.core.score.raw | 100 | item_1 |
|
||||
| scorm1 | student2 | 1 | cmi.completion_status | completed | item_1 |
|
||||
|
||||
Scenario: The scorm overview report should generate log events
|
||||
Given I am on the "Course 1" "course > activities > scorm" page logged in as "editingteacher1"
|
||||
When I am on the "Course 1" "course" page logged in as "editingteacher1"
|
||||
And I navigate to "Reports" in current page administration
|
||||
And I click on "Logs" "link"
|
||||
And I click on "Get these logs" "button"
|
||||
Then I should see "Course activities overview page viewed"
|
||||
And I should see "viewed the instance list for the module 'scorm'"
|
||||
|
||||
@javascript
|
||||
Scenario: Teachers can see relevant columns in the scorm overview
|
||||
When I am on the "Course 1" "course > activities > scorm" page logged in as "teacher1"
|
||||
Then I should see "Name" in the "scorm_overview_collapsible" "region"
|
||||
And I should see "Due date" in the "scorm_overview_collapsible" "region"
|
||||
And I should see "Student who attempted" in the "scorm_overview_collapsible" "region"
|
||||
And I should see "Total attempts" in the "scorm_overview_collapsible" "region"
|
||||
And I should see "Actions" in the "scorm_overview_collapsible" "region"
|
||||
Then the following should exist in the "Table listing all SCORM package activities" table:
|
||||
| Name | Due date | Student who attempted | Total attempts | Actions |
|
||||
| Scorm 1 | Sunday, 1 January 2040, 12:00 AM | 2 of 5 | 2 | View |
|
||||
| Scorm 2 | - | 0 of 5 | 0 | View |
|
||||
|
||||
@javascript
|
||||
Scenario: Teachers can see relevant columns when there are no participants in the course
|
||||
When I am on the "Course 2" "course > activities > scorm" page logged in as "admin"
|
||||
Then I should see "Name" in the "scorm_overview_collapsible" "region"
|
||||
And I should see "Student who attempted" in the "scorm_overview_collapsible" "region"
|
||||
And I should see "Total attempts" in the "scorm_overview_collapsible" "region"
|
||||
And I should see "Actions" in the "scorm_overview_collapsible" "region"
|
||||
Then the following should exist in the "Table listing all SCORM package activities" table:
|
||||
| Name | Student who attempted | Total attempts | Actions |
|
||||
| Scorm 3 | 0 of 0 | 0 | View |
|
||||
|
||||
@javascript
|
||||
Scenario Template: Students can see relevant columns in the scorm overview
|
||||
When I am on the "Course 1" "course > activities > scorm" page logged in as "<student>"
|
||||
Then I should see "Name" in the "scorm_overview_collapsible" "region"
|
||||
And I should see "Due date" in the "scorm_overview_collapsible" "region"
|
||||
And I should see "Grade" in the "scorm_overview_collapsible" "region"
|
||||
Then the following should exist in the "Table listing all SCORM package activities" table:
|
||||
| Name | Due date | Grade |
|
||||
| Scorm 1 | 1 January 2040 | <grade> |
|
||||
| Scorm 2 | - | - |
|
||||
Examples:
|
||||
| student | grade |
|
||||
| student1 | 50.00 |
|
||||
| student2 | 100.00 |
|
||||
@@ -0,0 +1,441 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace mod_scorm\courseformat;
|
||||
|
||||
use core_courseformat\local\overview\overviewfactory;
|
||||
|
||||
/**
|
||||
* Tests for SCORM activity overview
|
||||
*
|
||||
* @covers \mod_scorm\courseformat\overview
|
||||
* @package mod_scorm
|
||||
* @category test
|
||||
* @copyright 2025 Laurent David <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class overview_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test get_actions_overview.
|
||||
*
|
||||
* @covers ::get_actions_overview
|
||||
*/
|
||||
public function test_get_actions_overview(): void {
|
||||
$this->resetAfterTest();
|
||||
['users' => $users, 'course' => $course, 'instances' => $instances] = $this->setup_users_and_activity();
|
||||
['withattempts' => $instancewa] = $instances;
|
||||
$cm = get_fast_modinfo($course)->get_cm($instancewa->cmid);
|
||||
// Students have no action column.
|
||||
$this->setUser($users['s1']);
|
||||
$this->assertNull(overviewfactory::create($cm)->get_actions_overview());
|
||||
|
||||
// Teachers have a 'View results' button.
|
||||
$this->setUser($users['t1']);
|
||||
$items = overviewfactory::create($cm)->get_actions_overview();
|
||||
$this->assertNotNull($items);
|
||||
$this->assertEquals(get_string('actions'), $items->get_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_due_date_overview method.
|
||||
*
|
||||
* @param int|null $timeincrement
|
||||
*
|
||||
* @covers ::get_due_date_overview
|
||||
* @dataProvider get_due_date_overview_data
|
||||
*/
|
||||
public function test_get_due_date_overview(?int $timeincrement = null): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$scormtemplate = [];
|
||||
$clock = $this->mock_clock_with_frozen();
|
||||
if (!is_null($timeincrement)) {
|
||||
$scormtemplate['timeclose'] = $clock->time() + $timeincrement;
|
||||
}
|
||||
['users' => $users, 'course' => $course, 'instances' => $instances] =
|
||||
$this->setup_users_and_activity(instancedata: $scormtemplate);
|
||||
['withattempts' => $instancewa] = $instances;
|
||||
|
||||
$cm = get_fast_modinfo($course)->get_cm($instancewa->cmid);
|
||||
$this->setUser($users['s1']);
|
||||
$overview = overviewfactory::create($cm);
|
||||
$this->assertEquals(
|
||||
is_null($timeincrement) ? null : $clock->time() + $timeincrement,
|
||||
$overview->get_due_date_overview()->get_value(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_due_date_overview.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_due_date_overview_data(): array {
|
||||
return [
|
||||
'tomorrow' => [
|
||||
'timeincrement' => DAYSECS,
|
||||
],
|
||||
'yesterday' => [
|
||||
'timeincrement' => -1 * DAYSECS,
|
||||
],
|
||||
'today' => [
|
||||
'timeincrement' => 0,
|
||||
],
|
||||
'No date' => [
|
||||
'timeincrement' => null,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_extra_studentsattempted_overview.
|
||||
*
|
||||
* @param string $username
|
||||
* @param int $groupmode
|
||||
* @param array $expected
|
||||
*
|
||||
* @covers ::get_extra_studentsattempted_overview
|
||||
* @dataProvider get_extra_overview_items_data
|
||||
*/
|
||||
public function test_get_extra_totalattempts_overview(string $username, int $groupmode, array $expected): void {
|
||||
global $PAGE;
|
||||
$this->resetAfterTest();
|
||||
['users' => $users, 'course' => $course, 'instances' => $instances] = $this->setup_users_and_activity($groupmode);
|
||||
|
||||
foreach ($expected as $instancekey => $expectedvalues) {
|
||||
$instance = $instances[$instancekey];
|
||||
$cm = get_fast_modinfo($course)->get_cm($instance->cmid);
|
||||
$this->setUser($users[$username]);
|
||||
$items = overviewfactory::create($cm)->get_extra_overview_items();
|
||||
if (!isset($expectedvalues['totalattempts'])) {
|
||||
$this->assertArrayNotHasKey('totalattempts', $items);
|
||||
return;
|
||||
}
|
||||
$this->assertEquals(
|
||||
$expectedvalues['totalattempts']['value'],
|
||||
$items['totalattempts']->get_value(),
|
||||
"Failed for instance: $instancekey"
|
||||
);
|
||||
$content = $items['totalattempts']->get_content()->export_for_template($PAGE->get_renderer('core'));
|
||||
$contentitems = $content['items'] ?? [];
|
||||
$this->assertCount(
|
||||
count($expectedvalues['totalattempts']['items']),
|
||||
$contentitems,
|
||||
);
|
||||
foreach ($expectedvalues['totalattempts']['items'] as $item) {
|
||||
$currentitem = array_shift($contentitems);
|
||||
$this->assertEquals(
|
||||
(object) $item,
|
||||
$currentitem,
|
||||
"Failed for instance: $instancekey"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_extra_studentsattempted_overview.
|
||||
*
|
||||
* @param string $username
|
||||
* @param int $groupmode
|
||||
* @param array $expected
|
||||
*
|
||||
* @covers ::get_extra_studentsattempted_overview
|
||||
* @dataProvider get_extra_overview_items_data
|
||||
*/
|
||||
public function test_get_extra_studentsattempted_overview(string $username, int $groupmode, array $expected): void {
|
||||
$this->resetAfterTest();
|
||||
['users' => $users, 'course' => $course, 'instances' => $instances] = $this->setup_users_and_activity($groupmode);
|
||||
|
||||
foreach ($expected as $instancekey => $expectedvalues) {
|
||||
$instance = $instances[$instancekey];
|
||||
$cm = get_fast_modinfo($course)->get_cm($instance->cmid);
|
||||
$this->setUser($users[$username]);
|
||||
$items = overviewfactory::create($cm)->get_extra_overview_items();
|
||||
if (!isset($expectedvalues['attempted'])) {
|
||||
$this->assertArrayNotHasKey('attempted', $items);
|
||||
return;
|
||||
}
|
||||
$this->assertEquals(
|
||||
$expectedvalues['attempted']['value'],
|
||||
$items['attempted']->get_value(),
|
||||
"Failed for instance: $instancekey"
|
||||
);
|
||||
$this->assertEquals(
|
||||
$expectedvalues['attempted']['content'],
|
||||
$items['attempted']->get_content(),
|
||||
"Failed for instance: $instancekey"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_extra_overview_items when there are no users in the course
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_get_extra_overview_no_users(): void {
|
||||
global $PAGE;
|
||||
$this->resetAfterTest();
|
||||
['course' => $course, 'instances' => $instances] = $this->setup_users_and_activity(createusers: false);
|
||||
['withattempts' => $instancewa] = $instances; // This has no user so no attempt too.
|
||||
$cm = get_fast_modinfo($course)->get_cm($instancewa->cmid);
|
||||
$this->setAdminUser();
|
||||
$items = overviewfactory::create($cm)->get_extra_overview_items();
|
||||
$this->assertArrayHasKey('attempted', $items);
|
||||
$this->assertEquals(
|
||||
0,
|
||||
$items['attempted']->get_value(),
|
||||
'Expected no attempts when there are no users in the course.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<strong>0</strong> of 0',
|
||||
$items['attempted']->get_content(),
|
||||
'Expected no attempts when there are no users in the course.'
|
||||
);
|
||||
$this->assertArrayHasKey('totalattempts', $items);
|
||||
$this->assertEquals(
|
||||
0,
|
||||
$items['totalattempts']->get_value(),
|
||||
'Expected no total attempts when there are no users in the course.'
|
||||
);
|
||||
$content = $items['totalattempts']->get_content()->export_for_template($PAGE->get_renderer('core'));
|
||||
$this->assertCount(
|
||||
3,
|
||||
$content['items'],
|
||||
'Expected items even if the content is empty.'
|
||||
);
|
||||
$items = $content['items'];
|
||||
$this->assertEquals(
|
||||
(object) ['label' => 'Grading method', 'value' => 'Highest attempt'],
|
||||
array_shift($items),
|
||||
'Expected grading method item.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
(object) ['label' => 'Allowed attempts per student', 'value' => 'Unlimited'],
|
||||
array_shift($items),
|
||||
'Expected allowed attempts item.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
(object) ['label' => 'Average attempts per student', 'value' => '0'],
|
||||
array_shift($items),
|
||||
'Expected average attempts item.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_extra_studentsattempted_overview and test_get_extra_totalattempts_overview
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_extra_overview_items_data(): array {
|
||||
// Here we intentionally just test the case where course mode is set to NOGROUPS as groups are is not
|
||||
// yet supported by the overview page for SCORM module. This will be followed up in a future issue (MDL-85852).
|
||||
return [
|
||||
'teacher 1 - no groups' => [
|
||||
'username' => 't1',
|
||||
'groupmode' => NOGROUPS,
|
||||
'expected' => [
|
||||
'withattempts' => [
|
||||
'attempted' => [
|
||||
'value' => 2,
|
||||
'content' => '<strong>2</strong> of 4',
|
||||
],
|
||||
'totalattempts' => [
|
||||
'value' => 2,
|
||||
'items' => [
|
||||
[
|
||||
'label' => 'Grading method',
|
||||
'value' => 'Highest attempt',
|
||||
],
|
||||
[
|
||||
'label' => 'Allowed attempts per student',
|
||||
'value' => 'Unlimited',
|
||||
],
|
||||
[
|
||||
'label' => 'Average attempts per student',
|
||||
'value' => '1',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'withoutattempts' => [
|
||||
'attempted' => [
|
||||
'value' => 0,
|
||||
'content' => '<strong>0</strong> of 4',
|
||||
],
|
||||
'totalattempts' => [
|
||||
'value' => 0,
|
||||
'items' => [
|
||||
[
|
||||
'label' => 'Grading method',
|
||||
'value' => 'Highest attempt',
|
||||
],
|
||||
[
|
||||
'label' => 'Allowed attempts per student',
|
||||
'value' => 'Unlimited',
|
||||
],
|
||||
[
|
||||
'label' => 'Average attempts per student',
|
||||
'value' => '0',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'teacher 2 - no groups' => [
|
||||
'username' => 't2',
|
||||
'groupmode' => NOGROUPS,
|
||||
'expected' => [
|
||||
'withattempts' => [
|
||||
'attempted' => [
|
||||
'value' => 2,
|
||||
'content' => '<strong>2</strong> of 4',
|
||||
],
|
||||
'totalattempts' => [
|
||||
'value' => 2,
|
||||
'items' => [
|
||||
[
|
||||
'label' => 'Grading method',
|
||||
'value' => 'Highest attempt',
|
||||
],
|
||||
[
|
||||
'label' => 'Allowed attempts per student',
|
||||
'value' => 'Unlimited',
|
||||
],
|
||||
[
|
||||
'label' => 'Average attempts per student',
|
||||
'value' => '1',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'withoutattempts' => [
|
||||
'attempted' => [
|
||||
'value' => 0,
|
||||
'content' => '<strong>0</strong> of 4',
|
||||
],
|
||||
'totalattempts' => [
|
||||
'value' => 0,
|
||||
'items' => [
|
||||
[
|
||||
'label' => 'Grading method',
|
||||
'value' => 'Highest attempt',
|
||||
],
|
||||
[
|
||||
'label' => 'Allowed attempts per student',
|
||||
'value' => 'Unlimited',
|
||||
],
|
||||
[
|
||||
'label' => 'Average attempts per student',
|
||||
'value' => '0',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup users and activity for testing answers retrieval.
|
||||
*
|
||||
* @param int $groupmode the group mode to use for the course.
|
||||
* @param bool $createattempt whether to create an attempt for the student.
|
||||
* @param array|null $instancedata additional data for the instance.
|
||||
* @param array|null $grades the grade to set for the student.
|
||||
* @param bool $createusers whether to enrol users in the course.
|
||||
* @return array indexed array with 'users', 'course' and 'instance'.
|
||||
*/
|
||||
private function setup_users_and_activity(
|
||||
int $groupmode = NOGROUPS,
|
||||
bool $createattempt = true,
|
||||
?array $instancedata = null,
|
||||
?array $grades = null,
|
||||
bool $createusers = true,
|
||||
): array {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/scorm/locallib.php');
|
||||
$users = [];
|
||||
$generator = $this->getDataGenerator();
|
||||
$courseparams = [];
|
||||
if ($groupmode !== NOGROUPS) {
|
||||
// Set the group mode for the course.
|
||||
$courseparams['groupmode'] = $groupmode;
|
||||
$courseparams['groupmodeforce'] = 1; // Force the group mode.
|
||||
}
|
||||
$course = $generator->create_course($courseparams);
|
||||
$groups = [];
|
||||
if ($createusers) {
|
||||
$data = [
|
||||
's1' => ['role' => 'student', 'groups' => ['g1']],
|
||||
's2' => ['role' => 'student', 'groups' => ['g2']],
|
||||
't1' => ['role' => 'teacher', 'groups' => ['g1']],
|
||||
't2' => ['role' => 'teacher', 'groups' => []],
|
||||
];
|
||||
// Enrol users in the course.
|
||||
foreach ($data as $username => $userinfo) {
|
||||
['role' => $role, 'groups' => $groups] = $userinfo;
|
||||
$users[$username] = $generator->create_and_enrol($course, $role, ['username' => $username]);
|
||||
foreach ($groups as $group) {
|
||||
if (!isset($groups[$group])) {
|
||||
// Create the group if it does not exist.
|
||||
$groups[$group] = $generator->create_group(['courseid' => $course->id, 'name' => $group]);
|
||||
}
|
||||
// Add the user to the group.
|
||||
groups_add_member($groups[$group], $users[$username]->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->setAdminUser();
|
||||
$instancedata = $instancedata ?? [];
|
||||
$instancedata = array_merge($instancedata, [
|
||||
'course' => $course->id,
|
||||
'grademethod' => GRADEHIGHEST, // Use highest grade for grading.
|
||||
'whatgrade' => HIGHESTATTEMPT,
|
||||
]);
|
||||
$instances = [];
|
||||
$instances['withattempts'] = $generator->create_module('scorm', $instancedata);
|
||||
$instances['withoutattempts'] = $generator->create_module('scorm', $instancedata); // Create a second instance with no
|
||||
// attempt for testing purposes.
|
||||
if ($createattempt && $createusers) {
|
||||
$scormgenerator = $this->getDataGenerator()->get_plugin_generator('mod_scorm');
|
||||
// Create attempts for the students.
|
||||
// Two attempts for the first student, one for the second.
|
||||
foreach ($data as $username => $userinfo) {
|
||||
$record = [
|
||||
'userid' => $users[$username]->id,
|
||||
'scormid' => $instances['withattempts']->id,
|
||||
];
|
||||
if (isset($grades[$username])) {
|
||||
$record['element'] = 'cmi.core.score.raw';
|
||||
$record['value'] = $grades[$username];
|
||||
}
|
||||
// Create an attempt for each student.
|
||||
if ($userinfo['role'] === 'student') {
|
||||
$scormgenerator->create_attempt($record);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
'users' => $users,
|
||||
'course' => $course,
|
||||
'instances' => $instances,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Behat data generator for mod_scorm.
|
||||
*
|
||||
* @package mod_scorm
|
||||
* @copyright 2025 Laurent David <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_mod_scorm_generator extends behat_generator_base {
|
||||
/**
|
||||
* Get a list of the entities that Behat can create using the generator step.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_creatable_entities(): array {
|
||||
return [
|
||||
'grades' => [
|
||||
'singular' => 'grades',
|
||||
'datagenerator' => 'grade',
|
||||
'required' => ['scorm', 'user'],
|
||||
'switchids' => ['scorm' => 'scormid', 'user' => 'userid'],
|
||||
],
|
||||
'attempts' => [
|
||||
'singular' => 'attempt',
|
||||
'datagenerator' => 'attempt',
|
||||
'required' => ['scorm', 'user'],
|
||||
'switchids' => ['scorm' => 'scormid', 'user' => 'userid'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scorm cmid using an activity name or idnumber.
|
||||
*
|
||||
* @param string $identifier activity name or idnumber
|
||||
* @return int The scorm instance id of the scorm activity.
|
||||
*/
|
||||
protected function get_scorm_id(string $identifier): int {
|
||||
$cm = $this->get_cm_by_activity_name('scorm', $identifier);
|
||||
$manager = \mod_scorm\manager::create_from_coursemodule($cm);
|
||||
return $manager->get_instance()->id;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user