diff --git a/mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php b/mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php index 636e3aeac1f..107a02334c2 100644 --- a/mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php +++ b/mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php @@ -47,7 +47,8 @@ class backup_h5pactivity_activity_structure_step extends backup_activity_structu $attempts = new backup_nested_element('attempts'); $attempt = new backup_nested_element('attempt', ['id'], - ['h5pactivityid', 'userid', 'timecreated', 'timemodified', 'attempt', 'rawscore', 'maxscore'] + ['h5pactivityid', 'userid', 'timecreated', 'timemodified', 'attempt', 'rawscore', 'maxscore', + 'duration', 'completion', 'success'] ); $results = new backup_nested_element('results'); @@ -55,7 +56,8 @@ class backup_h5pactivity_activity_structure_step extends backup_activity_structu $result = new backup_nested_element('result', ['id'], [ 'attemptid', 'subcontent', 'timecreated', 'interactiontype', 'description', - 'correctpattern', 'response', 'additionals', 'rawscore', 'maxscore' + 'correctpattern', 'response', 'additionals', 'rawscore', 'maxscore', + 'duration', 'completion', 'success' ] ); diff --git a/mod/h5pactivity/classes/local/attempt.php b/mod/h5pactivity/classes/local/attempt.php index 84890034a99..e4221fb3e74 100644 --- a/mod/h5pactivity/classes/local/attempt.php +++ b/mod/h5pactivity/classes/local/attempt.php @@ -68,6 +68,9 @@ class attempt { $record->timemodified = $record->timecreated; $record->rawscore = 0; $record->maxscore = 0; + $record->duration = 0; + $record->completion = null; + $record->success = null; // Get last attempt number. $conditions = ['h5pactivityid' => $cm->instance, 'userid' => $user->id]; @@ -166,6 +169,7 @@ class attempt { } $definition = $xapidefinition->get_data(); $result = $xapiresult->get_data(); + $duration = $xapiresult->get_duration(); // Insert attempt_results record. $record = new stdClass(); @@ -183,6 +187,13 @@ class attempt { $record->rawscore = $result->score->raw ?? 0; $record->maxscore = $result->score->max ?? 0; } + $record->duration = $duration; + if (isset($result->completion)) { + $record->completion = ($result->completion) ? 1 : 0; + } + if (isset($result->success)) { + $record->success = ($result->success) ? 1 : 0; + } if (!$DB->insert_record('h5pactivity_attempts_results', $record)) { return false; } @@ -191,6 +202,9 @@ class attempt { if (empty($subcontent) && $record->rawscore) { $this->record->rawscore = $record->rawscore; $this->record->maxscore = $record->maxscore; + $this->record->duration = $record->duration; + $this->record->completion = $record->completion ?? null; + $this->record->success = $record->success ?? null; } // Refresh current attempt. return $this->save(); @@ -364,4 +378,31 @@ class attempt { public function get_rawscore(): int { return $this->record->maxscore; } + + /** + * Return the attempt duration. + * + * @return int the duration value + */ + public function get_duration(): int { + return $this->record->duration; + } + + /** + * Return the attempt completion. + * + * @return int|null the completion value + */ + public function get_completion(): ?int { + return $this->record->completion; + } + + /** + * Return the attempt success. + * + * @return int|null the success value + */ + public function get_success(): ?int { + return $this->record->success; + } } diff --git a/mod/h5pactivity/classes/privacy/provider.php b/mod/h5pactivity/classes/privacy/provider.php index 702ba9bcdd6..8f6f1760256 100644 --- a/mod/h5pactivity/classes/privacy/provider.php +++ b/mod/h5pactivity/classes/privacy/provider.php @@ -177,6 +177,7 @@ class provider implements har.additionals, har.rawscore, har.maxscore, + har.duration, har.timecreated, ctx.id as contextid FROM {h5pactivity_attempts_results} har @@ -200,6 +201,7 @@ class provider implements 'additionals' => $track->additionals, 'rawscore' => $track->rawscore, 'maxscore' => $track->maxscore, + 'duration' => $track->duration, 'timecreated' => transform::datetime($track->timecreated), ]; } diff --git a/mod/h5pactivity/db/install.xml b/mod/h5pactivity/db/install.xml index ac1fbde0b7e..549df4656cf 100644 --- a/mod/h5pactivity/db/install.xml +++ b/mod/h5pactivity/db/install.xml @@ -1,5 +1,5 @@ - @@ -31,6 +31,9 @@ + + + @@ -56,6 +59,9 @@ + + + diff --git a/mod/h5pactivity/db/upgrade.php b/mod/h5pactivity/db/upgrade.php index 198cf66e80b..a5932e3fee8 100644 --- a/mod/h5pactivity/db/upgrade.php +++ b/mod/h5pactivity/db/upgrade.php @@ -134,5 +134,61 @@ function xmldb_h5pactivity_upgrade($oldversion) { upgrade_mod_savepoint(true, 2020032300, 'h5pactivity'); } + if ($oldversion < 2020041400) { + + // Define field duration to be added to h5pactivity_attempts. + $table = new xmldb_table('h5pactivity_attempts'); + $field = new xmldb_field('duration', XMLDB_TYPE_INTEGER, '10', null, null, null, '0', 'maxscore'); + + // Conditionally launch add field duration. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field completion to be added to h5pactivity_attempts. + $field = new xmldb_field('completion', XMLDB_TYPE_INTEGER, '1', null, null, null, null, 'duration'); + + // Conditionally launch add field completion. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field success to be added to h5pactivity_attempts. + $field = new xmldb_field('success', XMLDB_TYPE_INTEGER, '1', null, null, null, null, 'completion'); + + // Conditionally launch add field success. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field duration to be added to h5pactivity_attempts_results. + $table = new xmldb_table('h5pactivity_attempts_results'); + $field = new xmldb_field('duration', XMLDB_TYPE_INTEGER, '10', null, null, null, '0', 'maxscore'); + + // Conditionally launch add field duration. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field completion to be added to h5pactivity_attempts_results. + $field = new xmldb_field('completion', XMLDB_TYPE_INTEGER, '1', null, null, null, null, 'duration'); + + // Conditionally launch add field completion. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field success to be added to h5pactivity_attempts_results. + $field = new xmldb_field('success', XMLDB_TYPE_INTEGER, '1', null, null, null, null, 'completion'); + + // Conditionally launch add field success. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // H5pactivity savepoint reached. + upgrade_mod_savepoint(true, 2020041400, 'h5pactivity'); + } + return true; } diff --git a/mod/h5pactivity/tests/local/attempt_test.php b/mod/h5pactivity/tests/local/attempt_test.php index 43dfa1bb64d..1af4c9202bf 100644 --- a/mod/h5pactivity/tests/local/attempt_test.php +++ b/mod/h5pactivity/tests/local/attempt_test.php @@ -135,6 +135,9 @@ class attempt_testcase extends \advanced_testcase { $this->assertEquals(0, $attempt->get_maxscore()); $this->assertEquals(0, $attempt->get_rawscore()); $this->assertEquals(0, $attempt->count_results()); + $this->assertEquals(0, $attempt->get_duration()); + $this->assertNull($attempt->get_completion()); + $this->assertNull($attempt->get_success()); $statement = $this->generate_statement($hasdefinition, $hasresult); $result = $attempt->save_statement($statement, $subcontent); @@ -142,6 +145,9 @@ class attempt_testcase extends \advanced_testcase { $this->assertEquals($results[1], $attempt->get_maxscore()); $this->assertEquals($results[2], $attempt->get_rawscore()); $this->assertEquals($results[3], $attempt->count_results()); + $this->assertEquals($results[4], $attempt->get_duration()); + $this->assertEquals($results[5], $attempt->get_completion()); + $this->assertEquals($results[6], $attempt->get_success()); } /** @@ -152,28 +158,28 @@ class attempt_testcase extends \advanced_testcase { public function save_statement_data(): array { return [ 'Statement without definition and result' => [ - '', false, false, [false, 0, 0, 0] + '', false, false, [false, 0, 0, 0, 0, null, null] ], 'Statement with definition but no result' => [ - '', true, false, [false, 0, 0, 0] + '', true, false, [false, 0, 0, 0, 0, null, null] ], 'Statement with result but no definition' => [ - '', true, false, [false, 0, 0, 0] + '', true, false, [false, 0, 0, 0, 0, null, null] ], 'Statement subcontent without definition and result' => [ - '111-222-333', false, false, [false, 0, 0, 0] + '111-222-333', false, false, [false, 0, 0, 0, 0, null, null] ], 'Statement subcontent with definition but no result' => [ - '111-222-333', true, false, [false, 0, 0, 0] + '111-222-333', true, false, [false, 0, 0, 0, 0, null, null] ], 'Statement subcontent with result but no definition' => [ - '111-222-333', true, false, [false, 0, 0, 0] + '111-222-333', true, false, [false, 0, 0, 0, 0, null, null] ], 'Statement with definition, result but no subcontent' => [ - '', true, true, [true, 2, 2, 1] + '', true, true, [true, 2, 2, 1, 25, 1, 1] ], 'Statement with definition, result and subcontent' => [ - '111-222-333', true, true, [true, 0, 0, 1] + '111-222-333', true, true, [true, 0, 0, 1, 0, null, null] ], ]; } @@ -340,6 +346,7 @@ class attempt_testcase extends \advanced_testcase { 'completion' => true, 'success' => true, 'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1], + 'duration' => 'PT25S', ])); } return $statement; diff --git a/mod/h5pactivity/tests/restore_test.php b/mod/h5pactivity/tests/restore_test.php index 95664b54da7..ca3f0da09c5 100644 --- a/mod/h5pactivity/tests/restore_test.php +++ b/mod/h5pactivity/tests/restore_test.php @@ -121,6 +121,9 @@ class mod_h5pactivity_restore_testcase extends advanced_testcase { $this->assertEquals($attempt->attempt, $attempt2->attempt); $this->assertEquals($attempt->rawscore, $attempt2->rawscore); $this->assertEquals($attempt->maxscore, $attempt2->maxscore); + $this->assertEquals($attempt->duration, $attempt2->duration); + $this->assertEquals($attempt->completion, $attempt2->completion); + $this->assertEquals($attempt->success, $attempt2->success); // Compare results. $results = $DB->get_records('h5pactivity_attempts_results', ['attemptid' => $attempt->id]); @@ -137,6 +140,9 @@ class mod_h5pactivity_restore_testcase extends advanced_testcase { $this->assertEquals($result->additionals, $result2->additionals); $this->assertEquals($result->rawscore, $result2->rawscore); $this->assertEquals($result->maxscore, $result2->maxscore); + $this->assertEquals($result->duration, $result2->duration); + $this->assertEquals($result->completion, $result2->completion); + $this->assertEquals($result->success, $result2->success); } } diff --git a/mod/h5pactivity/tests/xapi/handler_test.php b/mod/h5pactivity/tests/xapi/handler_test.php index 0594566252b..68d42bb45e3 100644 --- a/mod/h5pactivity/tests/xapi/handler_test.php +++ b/mod/h5pactivity/tests/xapi/handler_test.php @@ -285,6 +285,66 @@ class handler_testcase extends \advanced_testcase { ]; } + /** + * Test xapi_handler stored statements. + */ + public function test_stored_statements() { + global $DB; + + $data = $this->generate_testing_scenario(); + $xapihandler = $data->xapihandler; + $context = $data->context; + $student = $data->student; + $otheruser = $data->otheruser; + $activity = $data->activity; + + // Check we have 0 entries in the attempts tables. + $count = $DB->count_records('h5pactivity_attempts'); + $this->assertEquals(0, $count); + $count = $DB->count_records('h5pactivity_attempts_results'); + $this->assertEquals(0, $count); + + $statements = $this->generate_statements($context, $student); + + // Insert statements. + $stored = $xapihandler->process_statements($statements); + $this->assertCount(2, $stored); + $this->assertEquals(true, $stored[0]); + $this->assertEquals(true, $stored[1]); + $count = $DB->count_records('h5pactivity_attempts'); + $this->assertEquals(1, $count); + $count = $DB->count_records('h5pactivity_attempts_results'); + $this->assertEquals(2, $count); + + // Validate stored data. + $attempts = $DB->get_records('h5pactivity_attempts'); + $attempt = array_shift($attempts); + $statement = $statements[0]; + $data = $statement->get_result()->get_data(); + $this->assertEquals(1, $attempt->attempt); + $this->assertEquals($student->id, $attempt->userid); + $this->assertEquals($activity->id, $attempt->h5pactivityid); + $this->assertEquals($data->score->raw, $attempt->rawscore); + $this->assertEquals($data->score->max, $attempt->maxscore); + $this->assertEquals($statement->get_result()->get_duration(), $attempt->duration); + $this->assertEquals($data->completion, $attempt->completion); + $this->assertEquals($data->success, $attempt->success); + + $results = $DB->get_records('h5pactivity_attempts_results'); + foreach ($results as $result) { + $statement = (empty($result->subcontent)) ? $statements[0] : $statements[1]; + $xapiresult = $statement->get_result()->get_data(); + $xapiobject = $statement->get_object()->get_data(); + $this->assertEquals($attempt->id, $result->attemptid); + $this->assertEquals($xapiobject->definition->interactionType, $result->interactiontype); + $this->assertEquals($xapiresult->score->raw, $result->rawscore); + $this->assertEquals($xapiresult->score->max, $result->maxscore); + $this->assertEquals($statement->get_result()->get_duration(), $result->duration); + $this->assertEquals($xapiresult->completion, $result->completion); + $this->assertEquals($xapiresult->success, $result->success); + } + } + /** * Returns a basic xAPI statements simulating a H5P content. * @@ -307,6 +367,7 @@ class handler_testcase extends \advanced_testcase { 'completion' => true, 'success' => true, 'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1], + 'duration' => 'PT25S', ])); $statements[] = $statement; @@ -322,6 +383,7 @@ class handler_testcase extends \advanced_testcase { 'completion' => true, 'success' => true, 'score' => (object) ['min' => 0, 'max' => 1, 'raw' => 0, 'scaled' => 0], + 'duration' => 'PT20S', ])); $statements[] = $statement; diff --git a/mod/h5pactivity/version.php b/mod/h5pactivity/version.php index a23f286eccf..b200f901658 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 = 2020032300; +$plugin->version = 2020041400; $plugin->requires = 2020013000;