From 2f2cdd2a4887b3db025ee37d3621dba0dacd5100 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Fri, 8 May 2020 09:34:29 +0200 Subject: [PATCH 1/2] MDL-68617 mod_h5pactivity: add attempt info to generator --- mod/h5pactivity/tests/generator/lib.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mod/h5pactivity/tests/generator/lib.php b/mod/h5pactivity/tests/generator/lib.php index a9721632dc7..cabec2fedd3 100644 --- a/mod/h5pactivity/tests/generator/lib.php +++ b/mod/h5pactivity/tests/generator/lib.php @@ -123,6 +123,9 @@ class mod_h5pactivity_generator extends testing_module_generator { 'attempt' => $attemptnum, 'rawscore' => 3, 'maxscore' => 5, + 'completion' => 1, + 'success' => 1, + 'scaled' => 0.6, ]; $attempt->id = $DB->insert_record('h5pactivity_attempts', $attempt); @@ -139,6 +142,9 @@ class mod_h5pactivity_generator extends testing_module_generator { $cmid.'},"contextExtensions":{}}', 'rawscore' => 3, 'maxscore' => 5, + 'completion' => 1, + 'success' => 1, + 'scaled' => 0.6, ]; $DB->insert_record('h5pactivity_attempts_results', $result); @@ -154,6 +160,7 @@ class mod_h5pactivity_generator extends testing_module_generator { ',"http:\/\/h5p.org\/x-api\/h5p-subContentId":"'.$result->interactiontype. '"},"contextExtensions":{}}'; $result->rawscore = 1; + $result->scaled = 0.2; $DB->insert_record('h5pactivity_attempts_results', $result); $result->subcontent = '14fcc986-728b-47f3-915b-'.$userid; @@ -167,6 +174,7 @@ class mod_h5pactivity_generator extends testing_module_generator { '{"id":"2","description":{"en-US":"Redcurrant\n"}}],'. '"contextExtensions":{}}'; $result->rawscore = 2; + $result->scaled = 0.4; $DB->insert_record('h5pactivity_attempts_results', $result); return $attempt; From c09bc8d897e965eeca93e4e20e64cdd46ab8348e Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Fri, 8 May 2020 09:35:41 +0200 Subject: [PATCH 2/2] MDL-68617 mod_h5pactivity: add attempts report webservice --- .../classes/external/get_attempts.php | 245 +++++++++ mod/h5pactivity/db/services.php | 9 + .../tests/external/get_attempts_test.php | 488 ++++++++++++++++++ mod/h5pactivity/version.php | 2 +- 4 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 mod/h5pactivity/classes/external/get_attempts.php create mode 100644 mod/h5pactivity/tests/external/get_attempts_test.php diff --git a/mod/h5pactivity/classes/external/get_attempts.php b/mod/h5pactivity/classes/external/get_attempts.php new file mode 100644 index 00000000000..d1f9aecf6cf --- /dev/null +++ b/mod/h5pactivity/classes/external/get_attempts.php @@ -0,0 +1,245 @@ +. + +/** + * This is the external method for getting the information needed to present an attempts report. + * + * @package mod_h5pactivity + * @since Moodle 3.9 + * @copyright 2020 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_h5pactivity\external; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/externallib.php'); + +use mod_h5pactivity\local\manager; +use mod_h5pactivity\local\attempt; +use mod_h5pactivity\local\report\attempts as report_attempts; +use external_api; +use external_function_parameters; +use external_value; +use external_multiple_structure; +use external_single_structure; +use external_warnings; +use moodle_exception; +use context_module; +use stdClass; + +/** + * This is the external method for getting the information needed to present an attempts report. + * + * @copyright 2020 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class get_attempts extends external_api { + + /** + * Webservice parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters( + [ + 'h5pactivityid' => new external_value(PARAM_INT, 'h5p activity instance id'), + 'userids' => new external_multiple_structure( + new external_value(PARAM_INT, 'The user ids to get attempts (null means only current user)', VALUE_DEFAULT), + 'User ids', VALUE_DEFAULT, [] + ), + ] + ); + } + + /** + * Return user attempts information in a h5p activity. + * + * @throws moodle_exception if the user cannot see the report + * @param int $h5pactivityid The h5p activity id + * @param int[]|null $userids The user ids (if no provided $USER will be used) + * @return stdClass report data + */ + public static function execute(int $h5pactivityid, ?array $userids = []): stdClass { + global $USER; + + $params = external_api::validate_parameters(self::execute_parameters(), [ + 'h5pactivityid' => $h5pactivityid, + 'userids' => $userids, + ]); + $h5pactivityid = $params['h5pactivityid']; + $userids = $params['userids']; + + if (empty($userids)) { + $userids = [$USER->id]; + } + + $warnings = []; + + // Request and permission validation. + list ($course, $cm) = get_course_and_cm_from_instance($h5pactivityid, 'h5pactivity'); + + $context = context_module::instance($cm->id); + self::validate_context($context); + + $manager = manager::create_from_coursemodule($cm); + + $instance = $manager->get_instance(); + + $usersattempts = []; + foreach ($userids as $userid) { + $report = $manager->get_report($userid); + if ($report && $report instanceof report_attempts) { + $usersattempts[] = self::export_user_attempts($report, $userid); + } else { + $warnings[] = [ + 'item' => 'user', + 'itemid' => $userid, + 'warningcode' => '1', + 'message' => "Cannot access user attempts", + ]; + } + } + + $result = (object)[ + 'activityid' => $instance->id, + 'usersattempts' => $usersattempts, + 'warnings' => $warnings, + ]; + + return $result; + } + + /** + * Export attempts data for a specific user. + * + * @param report_attempts $report the report attempts object + * @param int $userid the user id + * @return stdClass + */ + public static function export_user_attempts(report_attempts $report, int $userid): stdClass { + + $scored = $report->get_scored(); + $attempts = $report->get_attempts(); + + $result = (object)[ + 'userid' => $userid, + 'attempts' => [], + ]; + + foreach ($attempts as $attempt) { + $result->attempts[] = self::export_attempt($attempt); + } + + if (!empty($scored)) { + $result->scored = (object)[ + 'title' => $scored->title, + 'grademethod' => $scored->grademethod, + 'attempts' => [self::export_attempt($scored->attempt)], + ]; + } + + return $result; + } + + /** + * Return a data object from an attempt. + * + * @param attempt $attempt the attempt object + * @return stdClass a WS compatible version of the attempt + */ + private static function export_attempt(attempt $attempt): stdClass { + $result = (object)[ + 'id' => $attempt->get_id(), + 'h5pactivityid' => $attempt->get_h5pactivityid(), + 'userid' => $attempt->get_userid(), + 'timecreated' => $attempt->get_timecreated(), + 'timemodified' => $attempt->get_timemodified(), + 'attempt' => $attempt->get_attempt(), + 'rawscore' => $attempt->get_rawscore(), + 'maxscore' => $attempt->get_maxscore(), + 'duration' => $attempt->get_duration(), + 'scaled' => $attempt->get_scaled(), + ]; + if ($attempt->get_completion() !== null) { + $result->completion = $attempt->get_completion(); + } + if ($attempt->get_success() !== null) { + $result->success = $attempt->get_success(); + } + return $result; + } + + /** + * Describes the get_h5pactivity_access_information return value. + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'activityid' => new external_value(PARAM_INT, 'Activity course module ID'), + 'usersattempts' => new external_multiple_structure( + self::get_user_attempts_returns(), 'The complete users attempts list' + ), + 'warnings' => new external_warnings(), + ], 'Activity attempts data'); + } + + /** + * Describes the get_h5pactivity_access_information return value. + * + * @return external_single_structure + */ + public static function get_user_attempts_returns(): external_single_structure { + $structure = [ + 'userid' => new external_value(PARAM_INT, 'The user id'), + 'attempts' => new external_multiple_structure(self::get_attempt_returns(), 'The complete attempts list'), + 'scored' => new external_single_structure([ + 'title' => new external_value(PARAM_NOTAGS, 'Scored attempts title'), + 'grademethod' => new external_value(PARAM_NOTAGS, 'Scored attempts title'), + 'attempts' => new external_multiple_structure(self::get_attempt_returns(), 'List of the grading attempts'), + ], 'Attempts used to grade the activity', VALUE_OPTIONAL), + ]; + return new external_single_structure($structure); + } + + /** + * Return the external structure of an attempt. + * + * @return external_single_structure + */ + private static function get_attempt_returns(): external_single_structure { + + $result = new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'ID of the context'), + 'h5pactivityid' => new external_value(PARAM_INT, 'ID of the H5P activity'), + 'userid' => new external_value(PARAM_INT, 'ID of the user'), + 'timecreated' => new external_value(PARAM_INT, 'Attempt creation'), + 'timemodified' => new external_value(PARAM_INT, 'Attempt modified'), + 'attempt' => new external_value(PARAM_INT, 'Attempt number'), + 'rawscore' => new external_value(PARAM_INT, 'Attempt score value'), + 'maxscore' => new external_value(PARAM_INT, 'Attempt max score'), + 'duration' => new external_value(PARAM_INT, 'Attempt duration in seconds'), + 'completion' => new external_value(PARAM_INT, 'Attempt completion', VALUE_OPTIONAL), + 'success' => new external_value(PARAM_INT, 'Attempt success', VALUE_OPTIONAL), + 'scaled' => new external_value(PARAM_FLOAT, 'Attempt scaled'), + ]); + return $result; + } +} diff --git a/mod/h5pactivity/db/services.php b/mod/h5pactivity/db/services.php index d38c84b6bcc..db62e5e35ba 100644 --- a/mod/h5pactivity/db/services.php +++ b/mod/h5pactivity/db/services.php @@ -44,4 +44,13 @@ $functions = [ 'capabilities' => 'mod/h5pactivity:view', 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], ], + 'mod_h5pactivity_get_attempts' => [ + 'classname' => 'mod_h5pactivity\external\get_attempts', + 'methodname' => 'execute', + 'classpath' => '', + 'description' => 'Return the information needed to list a user attempts.', + 'type' => 'read', + 'capabilities' => 'mod/h5pactivity:view', + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], + ], ]; diff --git a/mod/h5pactivity/tests/external/get_attempts_test.php b/mod/h5pactivity/tests/external/get_attempts_test.php new file mode 100644 index 00000000000..bfc6408314f --- /dev/null +++ b/mod/h5pactivity/tests/external/get_attempts_test.php @@ -0,0 +1,488 @@ +. + +/** + * External function test for get_attempts. + * + * @package mod_h5pactivity + * @category external + * @since Moodle 3.9 + * @copyright 2020 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_h5pactivity\external; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); + +use mod_h5pactivity\local\manager; +use external_api; +use externallib_advanced_testcase; + +/** + * External function test for get_attempts. + * + * @package mod_h5pactivity + * @copyright 2020 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class get_attempts_testcase extends externallib_advanced_testcase { + + /** + * Test the behaviour of get_attempts. + * + * @dataProvider execute_data + * @param int $grademethod the activity grading method + * @param string $loginuser the user which calls the webservice + * @param string|null $participant the user to get the data + * @param bool $createattempts if the student user has attempts created + * @param int|null $count the expected number of attempts returned (null for exception) + */ + public function test_execute(int $grademethod, string $loginuser, ?string $participant, + bool $createattempts, ?int $count): void { + + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $activity = $this->getDataGenerator()->create_module('h5pactivity', + ['course' => $course, 'enabletracking' => 1, 'grademethod' => $grademethod]); + + $manager = manager::create_from_instance($activity); + $cm = $manager->get_coursemodule(); + + // Prepare users: 1 teacher, 2 students, 1 unenroled user. + $users = [ + 'editingteacher' => $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'), + 'student' => $this->getDataGenerator()->create_and_enrol($course, 'student'), + 'other' => $this->getDataGenerator()->create_and_enrol($course, 'student'), + 'noenrolled' => $this->getDataGenerator()->create_user(), + ]; + + $generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity'); + + if ($createattempts) { + $user = $users['student']; + $params = ['cmid' => $cm->id, 'userid' => $user->id]; + $generator->create_content($activity, $params); + $generator->create_content($activity, $params); + } + + // Create another user with 2 attempts to validate no cross attempts are returned. + $user = $users['other']; + $params = ['cmid' => $cm->id, 'userid' => $user->id]; + $generator->create_content($activity, $params); + $generator->create_content($activity, $params); + + // Execute external method. + $this->setUser($users[$loginuser]); + $userids = ($participant) ? [$users[$participant]->id] : []; + $checkuserid = ($participant) ? $users[$participant]->id : $users[$loginuser]->id; + + $result = get_attempts::execute($activity->id, $userids); + $result = external_api::clean_returnvalue( + get_attempts::execute_returns(), + $result + ); + + // Validate general structure. + $this->assertArrayHasKey('activityid', $result); + $this->assertArrayHasKey('usersattempts', $result); + $this->assertArrayHasKey('warnings', $result); + + $this->assertEquals($activity->id, $result['activityid']); + + if ($count === null) { + $this->assertCount(1, $result['warnings']); + $this->assertCount(0, $result['usersattempts']); + return; + } + + $this->assertCount(0, $result['warnings']); + $this->assertCount(1, $result['usersattempts']); + + $userattempts = $result['usersattempts'][0]; + $this->assertEquals($checkuserid, $userattempts['userid']); + + // Validate scored attempts. + if ($grademethod == manager::GRADEMANUAL || $grademethod == manager::GRADEAVERAGEATTEMPT || $count == 0) { + $this->assertArrayNotHasKey('scored', $userattempts); + } else { + $this->assertArrayHasKey('scored', $userattempts); + list($dbgrademethod, $title) = $manager->get_selected_attempt(); + $this->assertEquals($grademethod, $dbgrademethod); + $this->assertEquals($grademethod, $userattempts['scored']['grademethod']); + $this->assertEquals($title, $userattempts['scored']['title']); + $this->assertCount(1, $userattempts['scored']['attempts']); + } + + // Validate returned attempts. + $this->assertCount($count, $userattempts['attempts']); + foreach ($userattempts['attempts'] as $attempt) { + $this->assertArrayHasKey('id', $attempt); + $this->assertEquals($checkuserid, $attempt['userid']); + $this->assertArrayHasKey('timecreated', $attempt); + $this->assertArrayHasKey('timemodified', $attempt); + $this->assertArrayHasKey('attempt', $attempt); + $this->assertArrayHasKey('rawscore', $attempt); + $this->assertArrayHasKey('maxscore', $attempt); + $this->assertArrayHasKey('duration', $attempt); + $this->assertArrayHasKey('completion', $attempt); + $this->assertArrayHasKey('success', $attempt); + $this->assertArrayHasKey('scaled', $attempt); + } + } + + /** + * Data provider for the test_execute tests. + * + * @return array + */ + public function execute_data(): array { + return [ + // Teacher checking a user with attempts. + 'Manual grade, Teacher asking participant with attempts' => [ + manager::GRADEMANUAL, 'editingteacher', 'student', true, 2 + ], + 'Highest grade, Teacher asking participant with attempts' => [ + manager::GRADEHIGHESTATTEMPT, 'editingteacher', 'student', true, 2 + ], + 'Average grade, Teacher asking participant with attempts' => [ + manager::GRADEAVERAGEATTEMPT, 'editingteacher', 'student', true, 2 + ], + 'Last grade, Teacher asking participant with attempts' => [ + manager::GRADELASTATTEMPT, 'editingteacher', 'student', true, 2 + ], + 'First grade, Teacher asking participant with attempts' => [ + manager::GRADEFIRSTATTEMPT, 'editingteacher', 'student', true, 2 + ], + // Teacher checking a user without attempts. + 'Manual grade, Teacher asking participant without attempts' => [ + manager::GRADEMANUAL, 'editingteacher', 'student', false, 0 + ], + 'Highest grade, Teacher asking participant without attempts' => [ + manager::GRADEHIGHESTATTEMPT, 'editingteacher', 'student', false, 0 + ], + 'Average grade, Teacher asking participant without attempts' => [ + manager::GRADEAVERAGEATTEMPT, 'editingteacher', 'student', false, 0 + ], + 'Last grade, Teacher asking participant without attempts' => [ + manager::GRADELASTATTEMPT, 'editingteacher', 'student', false, 0 + ], + 'First grade, Teacher asking participant without attempts' => [ + manager::GRADEFIRSTATTEMPT, 'editingteacher', 'student', false, 0 + ], + // Student checking own attempts specifying userid. + 'Manual grade, check same user attempts report with attempts' => [ + manager::GRADEMANUAL, 'student', 'student', true, 2 + ], + 'Highest grade, check same user attempts report with attempts' => [ + manager::GRADEHIGHESTATTEMPT, 'student', 'student', true, 2 + ], + 'Average grade, check same user attempts report with attempts' => [ + manager::GRADEAVERAGEATTEMPT, 'student', 'student', true, 2 + ], + 'Last grade, check same user attempts report with attempts' => [ + manager::GRADELASTATTEMPT, 'student', 'student', true, 2 + ], + 'First grade, check same user attempts report with attempts' => [ + manager::GRADEFIRSTATTEMPT, 'student', 'student', true, 2 + ], + // Student checking own attempts. + 'Manual grade, check own attempts report with attempts' => [ + manager::GRADEMANUAL, 'student', null, true, 2 + ], + 'Highest grade, check own attempts report with attempts' => [ + manager::GRADEHIGHESTATTEMPT, 'student', null, true, 2 + ], + 'Average grade, check own attempts report with attempts' => [ + manager::GRADEAVERAGEATTEMPT, 'student', null, true, 2 + ], + 'Last grade, check own attempts report with attempts' => [ + manager::GRADELASTATTEMPT, 'student', null, true, 2 + ], + 'First grade, check own attempts report with attempts' => [ + manager::GRADEFIRSTATTEMPT, 'student', null, true, 2 + ], + // Student checking own report without attempts. + 'Manual grade, check own attempts report without attempts' => [ + manager::GRADEMANUAL, 'student', 'student', false, 0 + ], + 'Highest grade, check own attempts report without attempts' => [ + manager::GRADEHIGHESTATTEMPT, 'student', 'student', false, 0 + ], + 'Average grade, check own attempts report without attempts' => [ + manager::GRADEAVERAGEATTEMPT, 'student', 'student', false, 0 + ], + 'Last grade, check own attempts report without attempts' => [ + manager::GRADELASTATTEMPT, 'student', 'student', false, 0 + ], + 'First grade, check own attempts report without attempts' => [ + manager::GRADEFIRSTATTEMPT, 'student', 'student', false, 0 + ], + // Student trying to get another user attempts. + 'Manual grade, student trying to stalk another student' => [ + manager::GRADEMANUAL, 'student', 'other', false, null + ], + 'Highest grade, student trying to stalk another student' => [ + manager::GRADEHIGHESTATTEMPT, 'student', 'other', false, null + ], + 'Average grade, student trying to stalk another student' => [ + manager::GRADEAVERAGEATTEMPT, 'student', 'other', false, null + ], + 'Last grade, student trying to stalk another student' => [ + manager::GRADELASTATTEMPT, 'student', 'other', false, null + ], + 'First grade, student trying to stalk another student' => [ + manager::GRADEFIRSTATTEMPT, 'student', 'other', false, null + ], + // Teacher trying to get a non enroled user attempts. + 'Manual grade, teacher trying to get an non enrolled user attempts' => [ + manager::GRADEMANUAL, 'editingteacher', 'noenrolled', false, null + ], + 'Highest grade, teacher trying to get an non enrolled user attempts' => [ + manager::GRADEHIGHESTATTEMPT, 'editingteacher', 'noenrolled', false, null + ], + 'Average grade, teacher trying to get an non enrolled user attempts' => [ + manager::GRADEAVERAGEATTEMPT, 'editingteacher', 'noenrolled', false, null + ], + 'Last grade, teacher trying to get an non enrolled user attempts' => [ + manager::GRADELASTATTEMPT, 'editingteacher', 'noenrolled', false, null + ], + 'First grade, teacher trying to get an non enrolled user attempts' => [ + manager::GRADEFIRSTATTEMPT, 'editingteacher', 'noenrolled', false, null + ], + // Student trying to get a non enroled user attempts. + 'Manual grade, student trying to get an non enrolled user attempts' => [ + manager::GRADEMANUAL, 'student', 'noenrolled', false, null + ], + 'Highest grade, student trying to get an non enrolled user attempts' => [ + manager::GRADEHIGHESTATTEMPT, 'student', 'noenrolled', false, null + ], + 'Average grade, student trying to get an non enrolled user attempts' => [ + manager::GRADEAVERAGEATTEMPT, 'student', 'noenrolled', false, null + ], + 'Last grade, student trying to get an non enrolled user attempts' => [ + manager::GRADELASTATTEMPT, 'student', 'noenrolled', false, null + ], + 'First grade, student trying to get an non enrolled user attempts' => [ + manager::GRADEFIRSTATTEMPT, 'student', 'noenrolled', false, null + ], + ]; + } + + /** + * Test the behaviour of get_attempts when tracking is not enabled. + * + */ + public function test_execute_no_tracking(): void { + + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $activity = $this->getDataGenerator()->create_module('h5pactivity', + ['course' => $course, 'enabletracking' => 0]); + + $manager = manager::create_from_instance($activity); + $cm = $manager->get_coursemodule(); + + // Prepare users: 1 teacher, 1 student. + $users = [ + 'editingteacher' => $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'), + 'student' => $this->getDataGenerator()->create_and_enrol($course, 'student'), + ]; + + // Execute external method. + $this->setUser($users['editingteacher']); + + $result = get_attempts::execute($activity->id, [$users['student']->id]); + $result = external_api::clean_returnvalue( + get_attempts::execute_returns(), + $result + ); + + $this->assertCount(1, $result['warnings']); + $this->assertCount(0, $result['usersattempts']); + } + + /** + * Test the behaviour of get_attempts when own review is not allowed. + * + */ + public function test_execute_no_own_review(): void { + + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $activity = $this->getDataGenerator()->create_module('h5pactivity', + ['course' => $course, 'enabletracking' => 1, 'reviewmode' => manager::REVIEWNONE]); + + $manager = manager::create_from_instance($activity); + $cm = $manager->get_coursemodule(); + + // Prepare users: 1 student. + $users = [ + 'student' => $this->getDataGenerator()->create_and_enrol($course, 'student'), + ]; + + // Execute external method. + $this->setUser($users['student']); + + $result = get_attempts::execute($activity->id); + $result = external_api::clean_returnvalue( + get_attempts::execute_returns(), + $result + ); + + $this->assertCount(1, $result['warnings']); + $this->assertCount(0, $result['usersattempts']); + } + + /** + * Test the behaviour of get_attempts getting more than one user at once. + * + * @dataProvider execute_multipleusers_data + * @param string $loginuser the user which calls the webservice + * @param string[] $participants the users to get the data + * @param string[] $warnings the expected users with warnings + * @param string[] $resultusers expected users in the resultusers + */ + public function test_execute_multipleusers(string $loginuser, array $participants, + array $warnings, array $resultusers): void { + + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $activity = $this->getDataGenerator()->create_module('h5pactivity', + ['course' => $course]); + + $manager = manager::create_from_instance($activity); + $cm = $manager->get_coursemodule(); + + // Prepare users: 1 teacher, 2 students with attempts, 1 student without, 1 no enrolled. + $users = [ + 'editingteacher' => $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'), + 'student1' => $this->getDataGenerator()->create_and_enrol($course, 'student'), + 'student2' => $this->getDataGenerator()->create_and_enrol($course, 'student'), + 'noattempts' => $this->getDataGenerator()->create_and_enrol($course, 'student'), + 'noenrolled' => $this->getDataGenerator()->create_user(), + ]; + + // Generate attempts (student1 with 1 attempt, student2 with 2). + $generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity'); + + $user = $users['student1']; + $params = ['cmid' => $cm->id, 'userid' => $user->id]; + $generator->create_content($activity, $params); + + $user = $users['student2']; + $params = ['cmid' => $cm->id, 'userid' => $user->id]; + $generator->create_content($activity, $params); + $generator->create_content($activity, $params); + + $countattempts = [ + $users['editingteacher']->id => 0, + $users['student1']->id => 1, + $users['student2']->id => 2, + $users['noattempts']->id => 0, + $users['noenrolled']->id => 0, + ]; + + // Execute external method. + $this->setUser($users[$loginuser]); + + $userids = []; + foreach ($participants as $participant) { + $userids[] = $users[$participant]->id; + } + + $result = get_attempts::execute($activity->id, $userids); + $result = external_api::clean_returnvalue( + get_attempts::execute_returns(), + $result + ); + + $this->assertCount(count($warnings), $result['warnings']); + $this->assertCount(count($resultusers), $result['usersattempts']); + + $expectedwarnings = []; + foreach ($warnings as $warninguser) { + $id = $users[$warninguser]->id; + $expectedwarnings[$id] = $warninguser; + } + + foreach ($result['warnings'] as $warning) { + $this->assertEquals('user', $warning['item']); + $this->assertEquals(1, $warning['warningcode']); + $this->assertArrayHasKey($warning['itemid'], $expectedwarnings); + } + + $expectedusers = []; + foreach ($resultusers as $resultuser) { + $id = $users[$resultuser]->id; + $expectedusers[$id] = $resultuser; + } + + foreach ($result['usersattempts'] as $usersattempts) { + $this->assertArrayHasKey('userid', $usersattempts); + $userid = $usersattempts['userid']; + $this->assertArrayHasKey($userid, $expectedusers); + $this->assertCount($countattempts[$userid], $usersattempts['attempts']); + if ($countattempts[$userid]) { + $this->assertArrayHasKey('scored', $usersattempts); + } + } + } + + /** + * Data provider for the test_execute_multipleusers. + * + * @return array + */ + public function execute_multipleusers_data(): array { + return [ + // Teacher checks. + 'Teacher checking students with attempts' => [ + 'editingteacher', ['student1', 'student2'], [], ['student1', 'student2'] + ], + 'Teacher checking one student with atempts and one not' => [ + 'editingteacher', ['student1', 'noattempts'], [], ['student1', 'noattempts'] + ], + 'Teacher checking no students' => [ + 'editingteacher', [], ['editingteacher'], [] + ], + 'Teacher checking one student and a no enrolled user' => [ + 'editingteacher', ['student1', 'noenrolled'], ['noenrolled'], ['student1'] + ], + // Student checks. + 'Student checking self attempts and another user' => [ + 'student1', ['student1', 'student2'], ['student2'], ['student1'] + ], + 'Student checking no students' => [ + 'student1', [], [], ['student1'] + ], + 'Student checking self attempts and a no enrolled user' => [ + 'student1', ['student1', 'noenrolled'], ['noenrolled'], ['student1'] + ], + ]; + } +} diff --git a/mod/h5pactivity/version.php b/mod/h5pactivity/version.php index f0481c6a308..91081ce2060 100644 --- a/mod/h5pactivity/version.php +++ b/mod/h5pactivity/version.php @@ -25,5 +25,5 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'mod_h5pactivity'; -$plugin->version = 2020042204; +$plugin->version = 2020052000; $plugin->requires = 2020013000;