This commit is contained in:
Ilya Tregubov 2023-07-27 08:59:44 +08:00 committed by Jun Pataleta
commit 01593b9332
No known key found for this signature in database
GPG Key ID: F83510526D99E2C7
31 changed files with 846 additions and 446 deletions

View File

@ -77,7 +77,7 @@ if (!empty($command)) {
} else {
$attempt = 1;
}
$attemptobject = scorm_get_attempt($aiccuser->id, $scormsession->scormid, $attempt);
if ($sco = scorm_get_sco($scoid, SCO_ONLY)) {
if (!$scorm = $DB->get_record('scorm', array('id' => $sco->scorm))) {
throw new \moodle_exception('cannotcallscript');
@ -226,7 +226,7 @@ if (!empty($command)) {
switch ($element) {
case 'cmi.core.lesson_location':
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attempt, $element, $value);
$attemptobject, $element, $value);
break;
case 'cmi.core.lesson_status':
$statuses = array(
@ -263,14 +263,14 @@ if (!empty($command)) {
if (empty($value) || isset($exites[$value])) {
$subelement = 'cmi.core.exit';
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attempt, $subelement, $value);
$attemptobject, $subelement, $value);
}
$value = trim(strtolower($values[0]));
$value = $value[0];
if (isset($statuses[$value]) && ($mode == 'normal')) {
$value = $statuses[$value];
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attempt, $element, $value);
$attemptobject, $element, $value);
}
$lessonstatus = $value;
break;
@ -280,12 +280,12 @@ if (!empty($command)) {
$subelement = 'cmi.core.score.max';
$value = trim($values[1]);
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attempt, $subelement, $value);
$attemptobject, $subelement, $value);
if ((count($values) == 3) && ($values[2] <= $values[0]) && is_numeric($values[2])) {
$subelement = 'cmi.core.score.min';
$value = trim($values[2]);
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attempt, $subelement, $value);
$attemptobject, $subelement, $value);
}
}
@ -293,7 +293,7 @@ if (!empty($command)) {
if (is_numeric($values[0])) {
$value = trim($values[0]);
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attempt, $element, $value);
$attemptobject, $element, $value);
}
$score = $value;
break;
@ -311,14 +311,14 @@ if (!empty($command)) {
next($datarows);
}
$value = rawurlencode($value);
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id, $attempt, $element, $value);
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id, $attemptobject, $element, $value);
}
}
}
if (($mode == 'browse') && ($initlessonstatus == 'not attempted')) {
$lessonstatus = 'browsed';
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attempt, 'cmi.core.lesson_status', 'browsed');
$attemptobject, 'cmi.core.lesson_status', 'browsed');
}
if ($mode == 'normal') {
if ($sco = scorm_get_sco($scoid)) {
@ -332,7 +332,7 @@ if (!empty($command)) {
}
}
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attempt, 'cmi.core.lesson_status', $lessonstatus);
$attemptobject, 'cmi.core.lesson_status', $lessonstatus);
}
}
}
@ -391,26 +391,24 @@ if (!empty($command)) {
case 'exitau':
if ($status == 'Running') {
if (isset($scormsession->sessiontime) && ($scormsession->sessiontime != '')) {
if ($track = $DB->get_record('scorm_scoes_track', array("userid" => $aiccuser->id,
"scormid" => $scorm->id,
"scoid" => $sco->id,
"attempt" => $attempt,
"element" => 'cmi.core.total_time'))) {
$track = scorm_get_sco_value($sco->id, $aiccuser->id, 'cmi.core.total_time', $attempt);
if (!empty($track)) {
// Add session_time to total_time.
$value = scorm_add_time($track->value, $scormsession->sessiontime);
$track->value = $value;
$track->timemodified = time();
$DB->update_record('scorm_scoes_track', $track);
$v = new stdClass();
$v->id = $track->valueid;
$v->value = $value;
$v->timemodified = time();
$DB->update_record('scorm_scoes_value', $v);
} else {
$track = new stdClass();
$track->userid = $aiccuser->id;
$track->scormid = $scorm->id;
$track->scoid = $sco->id;
$track->element = 'cmi.core.total_time';
$track->element = scorm_get_elementid('cmi.core.total_time');
$track->value = $scormsession->sessiontime;
$track->attempt = $attempt;
$atobject = scorm_get_attempt($userid, $scormsession->scormid, $attempt);
$track->attempt = $atobject->id;
$track->timemodified = time();
$id = $DB->insert_record('scorm_scoes_track', $track);
$id = $DB->insert_record('scorm_scoes_value', $track);
}
scorm_update_grades($scorm, $aiccuser->id);
}

View File

@ -141,7 +141,12 @@ class backup_scorm_activity_structure_step extends backup_activity_structure_ste
// All the rest of elements only happen if we are including user info
if ($userinfo) {
$scotrack->set_source_table('scorm_scoes_track', array('scoid' => backup::VAR_PARENTID), 'id ASC');
$sql = 'SELECT v.id, a.userid, a.attempt, e.element, v.value, v.timemodified
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON v.attemptid = a.id
JOIN {scorm_element} e ON e.id = v.elementid
WHERE v.scoid = :scoid';
$scotrack->set_source_sql($sql, ['scoid' => backup::VAR_PARENTID], 'id ASC');
}
// Define id annotations

View File

@ -173,17 +173,19 @@ class restore_scorm_activity_structure_step extends restore_activity_structure_s
}
protected function process_scorm_sco_track($data) {
global $DB;
global $DB, $CFG;
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$data = (object)$data;
$oldid = $data->id;
$data->scormid = $this->get_new_parentid('scorm');
$attemptobject = scorm_get_attempt($this->get_mappingid('user', $data->userid),
$this->get_new_parentid('scorm'),
$data->attempt);
$data->scoid = $this->get_new_parentid('scorm_sco');
$data->userid = $this->get_mappingid('user', $data->userid);
$data->attemptid = $attemptobject->id;
$data->elementid = scorm_get_elementid($data->element);
$newitemid = $DB->insert_record('scorm_scoes_track', $data);
// No need to save this mapping as far as nothing depend on it
// (child paths, file areas nor links decoder)
$DB->insert_record('scorm_scoes_value', $data);
// No need to save this mapping as far as nothing depend on it.
}
protected function after_execute() {

77
mod/scorm/classes/cache/elements.php vendored Normal file
View File

@ -0,0 +1,77 @@
<?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/>.
declare(strict_types=1);
namespace mod_scorm\cache;
use cache_definition;
/**
* Cache data source for the scorm elements.
*
* @package mod_scorm
* @copyright 2023 Catalyst IT Ltd
* @author Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class elements implements \cache_data_source {
/** @var elements the singleton instance of this class. */
protected static $instance = null;
/**
* Returns an instance of the data source class that the cache can use for loading data using the other methods
* specified by this interface.
*
* @param cache_definition $definition
* @return object
*/
public static function get_instance_for_cache(cache_definition $definition): elements {
if (is_null(self::$instance)) {
self::$instance = new elements();
}
return self::$instance;
}
/**
* Loads the data for the key provided ready formatted for caching.
*
* @param string|int $key The key to load.
* @return string What ever data should be returned, or null if it can't be loaded.
* @throws \coding_exception
*/
public function load_for_cache($key): ?string {
global $DB;
$element = $DB->get_field('scorm_element', 'id', ['element' => $key]);
// Return null instead of false, because false will not be cached.
return $element ?: null;
}
/**
* Loads several keys for the cache.
*
* @param array $keys An array of keys each of which will be string|int.
* @return array An array of matching data items.
*/
public function load_many_for_cache(array $keys): array {
global $DB;
list ($elementsql, $params) = $DB->get_in_or_equal($keys);
$sql = "SELECT element, id
FROM {scorm_element}
WHERE element ".$elementsql;
return $DB->get_records_sql_menu($sql, $params);
}
}

View File

@ -48,16 +48,18 @@ class custom_completion extends activity_custom_completion {
$this->validate_rule($rule);
// Base query used when fetching user's tracks data.
$basequery = "SELECT id, scoid, element, value
FROM {scorm_scoes_track}
WHERE scormid = ?
AND userid = ?";
$basequery = "SELECT v.id, v.scoid, e.element, v.value
FROM {scorm_scoes_value} v
JOIN {scorm_attempt} a ON a.id = v.attemptid
JOIN {scorm_element} e ON e.id = v.elementid
WHERE a.scormid = ?
AND a.userid = ?";
switch ($rule) {
case 'completionstatusrequired':
$status = COMPLETION_INCOMPLETE;
$query = $basequery .
" AND element IN (
" AND e.element IN (
'cmi.core.lesson_status',
'cmi.completion_status',
'cmi.success_status'
@ -85,7 +87,7 @@ class custom_completion extends activity_custom_completion {
case 'completionscorerequired':
$status = COMPLETION_INCOMPLETE;
$query = $basequery .
" AND element IN (
" AND e.element IN (
'cmi.core.score.raw',
'cmi.score.raw'
)";
@ -110,7 +112,7 @@ class custom_completion extends activity_custom_completion {
// Assume complete unless we find a sco that is not complete.
$status = COMPLETION_COMPLETE;
$query = $basequery .
" AND element IN (
" AND e.element IN (
'cmi.core.lesson_status',
'cmi.completion_status',
'cmi.success_status'

View File

@ -49,7 +49,7 @@ abstract class cmielement_submitted extends \core\event\base {
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'scorm_scoes_track';
$this->data['objecttable'] = 'scorm_scoes_value';
}
/**

View File

@ -481,11 +481,12 @@ class mod_scorm_external extends external_api {
// Check settings / permissions to view the SCORM.
scorm_require_available($scorm);
$attemptobject = scorm_get_attempt($USER->id, $scorm->id, $params['attempt']);
foreach ($params['tracks'] as $track) {
$element = $track['element'];
$value = $track['value'];
$trackid = scorm_insert_track($USER->id, $scorm->id, $sco->id, $params['attempt'], $element, $value,
$trackid = scorm_insert_track($USER->id, $scorm->id, $sco->id, $attemptobject, $element, $value,
$scorm->forcecompleted);
if ($trackid) {

View File

@ -53,13 +53,10 @@ class provider implements
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection) : collection {
$collection->add_database_table('scorm_scoes_track', [
$collection->add_database_table('scorm_attempt', [
'userid' => 'privacy:metadata:userid',
'attempt' => 'privacy:metadata:attempt',
'element' => 'privacy:metadata:scoes_track:element',
'value' => 'privacy:metadata:scoes_track:value',
'timemodified' => 'privacy:metadata:timemodified'
], 'privacy:metadata:scorm_scoes_track');
], 'privacy:metadata:scorm_attempt');
$collection->add_database_table('scorm_aicc_session', [
'userid' => 'privacy:metadata:userid',
@ -100,7 +97,7 @@ class provider implements
$params = ['modlevel' => CONTEXT_MODULE, 'userid' => $userid];
$contextlist = new contextlist();
$contextlist->add_from_sql(sprintf($sql, 'scorm_scoes_track'), $params);
$contextlist->add_from_sql(sprintf($sql, 'scorm_attempt'), $params);
$contextlist->add_from_sql(sprintf($sql, 'scorm_aicc_session'), $params);
return $contextlist;
@ -132,7 +129,7 @@ class provider implements
$params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id];
$userlist->add_from_sql('userid', sprintf($sql, 'scorm_scoes_track'), $params);
$userlist->add_from_sql('userid', sprintf($sql, 'scorm_attempt'), $params);
$userlist->add_from_sql('userid', sprintf($sql, 'scorm_aicc_session'), $params);
}
@ -168,19 +165,21 @@ class provider implements
// Get scoes_track data.
list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
$sql = "SELECT ss.id,
ss.attempt,
ss.element,
ss.value,
ss.timemodified,
$sql = "SELECT v.id,
a.attempt,
e.element,
v.value,
v.timemodified,
ctx.id as contextid
FROM {scorm_scoes_track} ss
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON a.id = v.attemptid
JOIN {scorm_element} e on e.id = v.elementid
JOIN {course_modules} cm
ON cm.instance = ss.scormid
ON cm.instance = a.scormid
JOIN {context} ctx
ON ctx.instanceid = cm.id
WHERE ctx.id $insql
AND ss.userid = :userid";
AND a.userid = :userid";
$params = array_merge($inparams, ['userid' => $userid]);
$alldata = [];
@ -280,8 +279,9 @@ class provider implements
WHERE cm.id = :cmid";
$params = ['cmid' => $context->instanceid];
static::delete_data('scorm_scoes_track', $sql, $params);
static::delete_data('scorm_aicc_session', $sql, $params);
$coursemodule = get_coursemodule_from_id('scorm', $context->instanceid);
scorm_delete_tracks($coursemodule->instance);
}
/**
@ -319,8 +319,13 @@ class provider implements
AND ctx.id $insql";
$params = array_merge($inparams, ['userid' => $userid]);
static::delete_data('scorm_scoes_track', $sql, $params);
static::delete_data('scorm_aicc_session', $sql, $params);
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel == CONTEXT_MODULE) {
$coursemodule = get_coursemodule_from_id('scorm', $context->instanceid);
scorm_delete_tracks($coursemodule->instance, null, $userid);
}
}
}
/**
@ -353,8 +358,11 @@ class provider implements
AND ss.userid $insql";
$params = array_merge($inparams, ['contextid' => $context->id]);
static::delete_data('scorm_scoes_track', $sql, $params);
static::delete_data('scorm_aicc_session', $sql, $params);
$coursemodule = get_coursemodule_from_id('scorm', $context->instanceid);
foreach ($userlist->get_userids() as $userid) {
scorm_delete_tracks($coursemodule->instance, null, $userid);
}
}
/**

View File

@ -55,14 +55,20 @@ if (confirm_sesskey() && (!empty($scoid))) {
$request = null;
if (has_capability('mod/scorm:savetrack', context_module::instance($cm->id))) {
// Preload all current tracking data.
$trackdata = $DB->get_records('scorm_scoes_track', array('userid' => $USER->id, 'scormid' => $scorm->id, 'scoid' => $scoid,
'attempt' => $attempt), '', 'element, id, value, timemodified');
$sql = "SELECT e.element, v.value, v.timemodified, v.id as valueid
FROM {scorm_scoes_value} v
JOIN {scorm_attempt} a ON a.id = v.attemptid
JOIN {scorm_element} e on e.id = v.elementid
WHERE a.scormid = :scormid AND a.userid = :userid AND v.scoid = :scoid AND a.attempt = :attempt";
$trackdata = $DB->get_records_sql($sql, ['userid' => $USER->id, 'scormid' => $scorm->id,
'scoid' => $scoid, 'attempt' => $attempt]);
$attemptobject = scorm_get_attempt($USER->id, $scorm->id, $attempt);
foreach (data_submitted() as $element => $value) {
$element = str_replace('__', '.', $element);
if (substr($element, 0, 3) == 'cmi') {
$netelement = preg_replace('/\.N(\d+)\./', "\.\$1\.", $element);
$result = scorm_insert_track($USER->id, $scorm->id, $scoid, $attempt, $element, $value, $scorm->forcecompleted,
$trackdata) && $result;
$result = scorm_insert_track($USER->id, $scorm->id, $scoid, $attemptobject, $element, $value,
$scorm->forcecompleted, $trackdata) && $result;
}
if (substr($element, 0, 15) == 'adl.nav.request') {
// SCORM 2004 Sequencing Request.

View File

@ -374,8 +374,8 @@ function scorm_parse_aicc(&$scorm) {
}
if (!empty($oldscoes)) {
foreach ($oldscoes as $oldsco) {
$DB->delete_records('scorm_scoes', array('id' => $oldsco->id));
$DB->delete_records('scorm_scoes_track', array('scoid' => $oldsco->id));
scorm_delete_tracks($scorm->id, $oldsco->id);
$DB->delete_records('scorm_scoes', ['id' => $oldsco->id]);
}
}
@ -461,8 +461,8 @@ function scorm_aicc_generate_simple_sco($scorm) {
}
// Get rid of old ones.
foreach ($scos as $oldsco) {
$DB->delete_records('scorm_scoes', array('id' => $oldsco->id));
$DB->delete_records('scorm_scoes_track', array('scoid' => $oldsco->id));
scorm_delete_tracks($scorm->id, $oldsco->id);
$DB->delete_records('scorm_scoes', ['id' => $oldsco->id]);
}
$sco->identifier = 'A1';

View File

@ -81,8 +81,7 @@ function scorm_seq_navigation ($scoid, $userid, $request, $attempt=0) {
case 'resumeall_':
if (empty($seq->currentactivity)) {
// TODO: I think it's suspend instead of suspendedactivity.
if ($track = $DB->get_record('scorm_scoes_track',
array('scoid' => $scoid, 'userid' => $userid, 'element' => 'suspendedactivity'))) {
if (scorm_get_sco_value($scoid, $userid, 'suspendedactivity')) {
$seq->navigation = true;
$seq->sequencing = 'resumeall';
@ -323,11 +322,8 @@ function scorm_seq_end_attempt($sco, $userid, $seq) {
if (!scorm_seq_is('suspended', $sco->id, $userid)) {
if (!isset($sco->completionsetbycontent) || ($sco->completionsetbycontent == 0)) {
if (!scorm_seq_is('attemptprogressstatus', $sco->id, $userid, $seq->attempt)) {
$incomplete = $DB->get_field('scorm_scoes_track', 'value',
array('scoid' => $sco->id,
'userid' => $userid,
'element' => 'cmi.completion_status'));
if ($incomplete != 'incomplete') {
$r = scorm_get_sco_value($sco->id, $userid, 'cmi.completion_status');
if ($r->value != 'incomplete') {
scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt);
scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt);
}
@ -366,12 +362,9 @@ function scorm_seq_end_attempt($sco, $userid, $seq) {
}
function scorm_seq_is($what, $scoid, $userid, $attempt=0) {
global $DB;
// Check if passed activity $what is active.
$active = false;
if ($track = $DB->get_record('scorm_scoes_track',
array('scoid' => $scoid, 'userid' => $userid, 'attempt' => $attempt, 'element' => $what))) {
if (scorm_get_sco_value($scoid, $userid, $what, $attempt)) {
$active = true;
}
return $active;
@ -384,8 +377,11 @@ function scorm_seq_set($what, $scoid, $userid, $attempt=0, $value='true') {
// Set passed activity to active or not.
if ($value == false) {
$DB->delete_records('scorm_scoes_track', array('scoid' => $scoid, 'userid' => $userid,
'attempt' => $attempt, 'element' => $what));
$params = ['userid' => $userid, 'scormid' => $sco->scorm, 'attempt' => $attempt, 'element' => $what];
$sql = "WHERE scoid = :scoid AND attemptid = :attemptid AND elementid = (SELECT id
FROM {scorm_element}
WHERE element = :element)";
$DB->delete_records_select('scorm_scoes_value', $sql, $params);
} else {
scorm_insert_track($userid, $sco->scorm, $sco->id, $attempt, $what, $value);
}
@ -425,11 +421,9 @@ function scorm_evaluate_condition ($rollupruleconds, $sco, $userid) {
}
switch ($condition['condition']) {
case 'satisfied':
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivesatisfiedstatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'objectivesatisfiedstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'objectiveprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
@ -437,43 +431,37 @@ function scorm_evaluate_condition ($rollupruleconds, $sco, $userid) {
break;
case 'objectiveStatusKnown':
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'objectiveprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
break;
case 'notobjectiveStatusKnown':
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'objectiveprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
break;
case 'objectiveMeasureKnown':
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivemeasurestatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'objectivemeasurestatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
break;
case 'notobjectiveMeasureKnown':
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivemeasurestatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'objectivemeasurestatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
break;
case 'completed':
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'attemptcompletionstatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'attemptcompletionstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'attemptprogressstatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'attemptprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
@ -481,30 +469,29 @@ function scorm_evaluate_condition ($rollupruleconds, $sco, $userid) {
break;
case 'attempted':
$attempt = $DB->get_field('scorm_scoes_track', 'attempt',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'x.start.time'));
if ($checknot && $attempt > 0) {
$r = scorm_get_sco_value($sco->id, $userid, 'x.start.time');
if ($checknot && $r->attempt > 0) {
$res = true;
} else if (!$checknot && $attempt <= 0) {
} else if (!$checknot && $r->attempt <= 0) {
$res = true;
}
break;
case 'attemptLimitExceeded':
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'activityprogressstatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'activityprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid,
'element' => 'limitconditionattemptlimitcontrol'));
$r = scorm_get_sco_value($sco->id, $userid, 'limitconditionattemptlimitcontrol');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
if ($r = $DB->get_field('scorm_scoes_track', 'attempt', array('scoid' => $sco->id, 'userid' => $userid)) &&
$r2 = $DB->get_record('scorm_scoes_track', array('scoid' => $sco->id, 'userid' => $userid,
'element' => 'limitconditionattemptlimit')) ) {
if ($checknot && ($r->value >= $r2->value)) {
$sql = "SELECT max(attempt) as attempt
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v on v.attemptid = a.id
WHERE v.scoid = :scoid AND a.userid = :userid";
$r2 = scorm_get_sco_value($sco->id, $userid, 'limitconditionattemptlimit');
$attempts = $DB->get_field_sql($sql, ['scoid' => $sco->id, 'userid' => $userid]);
if (!empty($attempts) && !empty($r2)) {
if ($checknot && ($attempts >= $r2->value)) {
$res = true;
} else if (!$checknot && ($r->value < $r2->value)) {
} else if (!$checknot && ($attempts < $r2->value)) {
$res = true;
}
}
@ -513,11 +500,9 @@ function scorm_evaluate_condition ($rollupruleconds, $sco, $userid) {
break;
case 'activityProgressKnown':
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'activityprogressstatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'activityprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'attemptprogressstatus'));
$r = scorm_get_sco_value($sco->id, $userid, 'attemptprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
@ -558,48 +543,42 @@ function scorm_limit_cond_check ($activity, $userid) {
}
if (!isset($activity->limitcontrol) || ($activity->limitcontrol == 1)) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'activityattemptcount'));
$r = scorm_get_sco_value($activity->id, $userid, 'activityattemptcount');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattempt)) {
return true;
}
}
if (!isset($activity->limitabsdurcontrol) || ($activity->limitabsdurcontrol == 1)) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'activityabsoluteduration'));
$r = scorm_get_sco_value($activity->id, $userid, 'activityabsoluteduration');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitabsduration)) {
return true;
}
}
if (!isset($activity->limitexpdurcontrol) || ($activity->limitexpdurcontrol == 1)) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'activityexperiencedduration'));
$r = scorm_get_sco_value($activity->id, $userid, 'activityexperiencedduration');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitexpduration)) {
return true;
}
}
if (!isset($activity->limitattabsdurcontrol) || ($activity->limitattabsdurcontrol == 1)) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'attemptabsoluteduration'));
$r = scorm_get_sco_value($activity->id, $userid, 'attemptabsoluteduration');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattabsduration)) {
return true;
}
}
if (!isset($activity->limitattexpdurcontrol) || ($activity->limitattexpdurcontrol == 1)) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'attemptexperiencedduration'));
$r = scorm_get_sco_value($activity->id, $userid, 'attemptexperiencedduration');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattexpduration)) {
return true;
}
}
if (!isset($activity->limitbegincontrol) || ($activity->limitbegincontrol == 1)) {
$r = $DB->get_record('scorm_scoes_track',
array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'begintime'));
$r = scorm_get_sco_value($activity->id, $userid, 'begintime');
if (isset($activity->limitbegintime) && time() >= $activity->limitbegintime) {
return true;
}
@ -711,8 +690,7 @@ function scorm_seq_measure_rollup($sco, $userid, $attempt = 0) {
$child = scorm_get_sco($child->id);
$countedmeasures = $countedmeasures + ($child->measureweight);
if (!scorm_seq_is('objectivemeasurestatus', $sco->id, $userid, $attempt)) {
$normalizedmeasure = $DB->get_record('scorm_scoes_track',
array('scoid' => $child->id, 'userid' => $userid, 'element' => 'objectivenormalizedmeasure'));
$normalizedmeasure = scorm_get_sco_value($child->id, $userid, 'objectivenormalizedmeasure');
$totalmeasure = $totalmeasure + (($normalizedmeasure->value) * ($child->measureweight));
$valid = true;
}
@ -795,9 +773,7 @@ function scorm_seq_objective_rollup_measure($sco, $userid, $attempt = 0) {
} else {
$isactive = false;
}
$normalizedmeasure = $DB->get_record('scorm_scoes_track',
array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivenormalizedmeasure'));
$normalizedmeasure = scorm_get_sco_value($sco->id, $userid, 'objectivenormalizedmeasure');
$sco = scorm_get_sco ($sco->id);
@ -891,8 +867,6 @@ function scorm_seq_rollup_rule_check ($sco, $userid, $action) {
foreach ($rolluprules as $rolluprule) {
foreach ($children as $child) {
/*$tracked = $DB->get_records('scorm_scoes_track', array('scoid'=>$child->id, 'userid'=>$userid));
if ($tracked && $tracked->attemp != 0) {*/
$child = scorm_get_sco ($child);
if (!isset($child->tracked) || ($child->tracked == 1)) {
if (scorm_seq_check_child ($child, $action, $userid)) {

View File

@ -709,15 +709,15 @@ function scorm_parse_scorm(&$scorm, $manifest) {
}
if (!empty($olditems)) {
foreach ($olditems as $olditem) {
$DB->delete_records('scorm_scoes', array('id' => $olditem->id));
$DB->delete_records('scorm_scoes_data', array('scoid' => $olditem->id));
$DB->delete_records('scorm_scoes_track', array('scoid' => $olditem->id));
$DB->delete_records('scorm_seq_objective', array('scoid' => $olditem->id));
$DB->delete_records('scorm_seq_mapinfo', array('scoid' => $olditem->id));
$DB->delete_records('scorm_seq_ruleconds', array('scoid' => $olditem->id));
$DB->delete_records('scorm_seq_rulecond', array('scoid' => $olditem->id));
$DB->delete_records('scorm_seq_rolluprule', array('scoid' => $olditem->id));
$DB->delete_records('scorm_seq_rolluprulecond', array('scoid' => $olditem->id));
$DB->delete_records('scorm_scoes', ['id' => $olditem->id]);
$DB->delete_records('scorm_scoes_data', ['scoid' => $olditem->id]);
scorm_delete_tracks($scorm->id, $olditem->id);
$DB->delete_records('scorm_seq_objective', ['scoid' => $olditem->id]);
$DB->delete_records('scorm_seq_mapinfo', ['scoid' => $olditem->id]);
$DB->delete_records('scorm_seq_ruleconds', ['scoid' => $olditem->id]);
$DB->delete_records('scorm_seq_rulecond', ['scoid' => $olditem->id]);
$DB->delete_records('scorm_seq_rolluprule', ['scoid' => $olditem->id]);
$DB->delete_records('scorm_seq_rolluprulecond', ['scoid' => $olditem->id]);
}
}
if (empty($scoes->version)) {

View File

@ -119,7 +119,7 @@ function scorm_seq_check_child ($sco, $action, $userid) {
$included = false;
$sco = scorm_get_sco($sco->id);
$r = $DB->get_record('scorm_scoes_track', array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'activityattemptcount'));
$r = scorm_get_sco_value($sco->id, $userid, 'activityattemptcount');
if ($action == 'satisfied' || $action == 'notsatisfied') {
if (!$sco->rollupobjectivesatisfied) {
$included = true;
@ -262,7 +262,7 @@ function scorm_seq_resume_all_sequencing($scoid, $userid, $seq) {
$seq->exception = 'SB.2.6-1';
return $seq;
}
$track = $DB->get_record('scorm_scoes_track', array('scoid' => $scoid, 'userid' => $userid, 'element' => 'suspendedactivity'));
$track = scorm_get_sco_value($scoid, $userid, 'suspendedactivity');
if (!$track) {
$seq->delivery = null;
$seq->exception = 'SB.2.6-2';
@ -752,13 +752,12 @@ function scorm_content_delivery_environment($seq, $userid) {
$seq->exception = 'DB.2-1';
return $seq;
}
$track = $DB->get_record('scorm_scoes_track', array('scoid' => $act->id,
'userid' => $userid,
'element' => 'suspendedactivity'));
$track = scorm_get_sco_value($act->id, $userid, 'suspendedactivity');
if ($track != null) {
$seq = scorm_clear_suspended_activity($seq->delivery, $seq, $userid);
}
$attemptobject = scorm_get_attempt($userid, $track->scormid, 0);
$seq = scorm_terminate_descendent_attempts ($seq->delivery, $userid, $seq);
$ancestors = scorm_get_ancestors($seq->delivery);
$arrpath = array_reverse($ancestors);
@ -767,24 +766,32 @@ function scorm_content_delivery_environment($seq, $userid) {
if (!scorm_seq_is('active', $activity->id, $userid)) {
if (!isset($activity->tracked) || ($activity->tracked == 1)) {
if (!scorm_seq_is('suspended', $activity->id, $userid)) {
$r = $DB->get_record('scorm_scoes_track', array('scoid' => $activity->id,
'userid' => $userid,
'element' => 'activityattemptcount'));
$r->value = ($r->value) + 1;
$DB->update_record('scorm_scoes_track', $r);
$r = scorm_get_sco_value($activity->id, $userid, 'activityattemptcount');
$value = new stdClass();
$value->id = $r->valueid;
$value->value = ($r->value) + 1;
$DB->update_record('scorm_scoes_value', $value);
if ($r->value == 1) {
scorm_seq_set('activityprogressstatus', $activity->id, $userid, 'true');
}
scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'objectiveprogressstatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'objectivesatisfiedstatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'objectivemeasurestatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'objectivenormalizedmeasure', 0.0);
scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptprogressstatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptcompletionstatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptabsoluteduration', 0.0);
scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptexperiencedduration', 0.0);
scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptcompletionamount', 0.0);
scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject,
'objectiveprogressstatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject,
'objectivesatisfiedstatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject,
'objectivemeasurestatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject,
'objectivenormalizedmeasure', 0.0);
scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject,
'attemptprogressstatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject,
'attemptcompletionstatus', 'false');
scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject,
'attemptabsoluteduration', 0.0);
scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject,
'attemptexperiencedduration', 0.0);
scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject,
'attemptcompletionamount', 0.0);
}
}
scorm_seq_set('active', $activity->id, $userid, 'true');
@ -793,26 +800,13 @@ function scorm_content_delivery_environment($seq, $userid) {
$seq->delivery = $seq->currentactivity;
scorm_seq_set('suspendedactivity', $activity->id, $userid, 'false');
// ONCE THE DELIVERY BEGINS (How should I check that?).
if (isset($activity->tracked) || ($activity->tracked == 0)) {
// How should I track the info and what should I do to not record the information for the activity during delivery?
$atabsdur = $DB->get_record('scorm_scoes_track', array('scoid' => $activity->id,
'userid' => $userid,
'element' => 'attemptabsoluteduration'));
$atexpdur = $DB->get_record('scorm_scoes_track', array('scoid' => $activity->id,
'userid' => $userid,
'element' => 'attemptexperiencedduration'));
}
return $seq;
}
function scorm_clear_suspended_activity($act, $seq, $userid) {
global $DB;
$currentact = $seq->currentactivity;
$track = $DB->get_record('scorm_scoes_track', array('scoid' => $currentact->id,
'userid' => $userid,
'element' => 'suspendedactivity'));
$track = scorm_get_sco_value($currentact->id, $userid, 'suspendedactivity');
if ($track != null) {
$ancestors = scorm_get_ancestors($act);
$commonpos = scorm_find_common_ancestor($ancestors, $currentact);
@ -849,9 +843,7 @@ function scorm_select_children_process($scoid, $userid) {
$sco = scorm_get_sco($scoid);
if (!scorm_is_leaf($sco)) {
if (!scorm_seq_is('suspended', $scoid, $userid) && !scorm_seq_is('active', $scoid, $userid)) {
$r = $DB->get_record('scorm_scoes_track', array('scoid' => $scoid,
'userid' => $userid,
'element' => 'selectiontiming'));
$r = scorm_get_sco_value($scoid, $userid, 'selectiontiming');
switch ($r->value) {
case 'oneachnewattempt':
@ -862,9 +854,7 @@ function scorm_select_children_process($scoid, $userid) {
if (!scorm_seq_is('activityprogressstatus', $scoid, $userid)) {
if (scorm_seq_is('selectioncountsstatus', $scoid, $userid)) {
$childlist = '';
$res = $DB->get_record('scorm_scoes_track', array('scoid' => $scoid,
'userid' => $userid,
'element' => 'selectioncount'));
$res = scorm_get_sco_value($scoid, $userid, 'selectioncount');
$i = ($res->value) - 1;
$children = scorm_get_children($sco);
@ -892,9 +882,7 @@ function scorm_randomize_children_process($scoid, $userid) {
$sco = scorm_get_sco($scoid);
if (!scorm_is_leaf($sco)) {
if (!scorm_seq_is('suspended', $scoid, $userid) && !scorm_seq_is('active', $scoid, $userid)) {
$r = $DB->get_record('scorm_scoes_track', array('scoid' => $scoid,
'userid' => $userid,
'element' => 'randomizationtiming'));
$r = scorm_get_sco_value($scoid, $userid, 'randomizationtiming');
switch ($r->value) {
case 'never':

33
mod/scorm/db/caches.php Normal file
View File

@ -0,0 +1,33 @@
<?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/>.
/**
* SCORM cache definition.
*
* @package mod_scorm
* @copyright 2023 Catalyst IT Ltd
* @author Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$definitions = [
'elements' => [
'mode' => cache_store::MODE_APPLICATION,
'datasource' => '\mod_scorm\cache\elements',
]
];

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/scorm/db" VERSION="20220127" COMMENT="XMLDB file for Moodle mod/scorm"
<XMLDB PATH="mod/scorm/db" VERSION="20230406" COMMENT="XMLDB file for Moodle mod/scorm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
@ -85,27 +85,6 @@
<KEY NAME="scorm_scoes_data_scoid" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id" COMMENT="The relative sco"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_scoes_track" COMMENT="to track SCOes">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="scormid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="attempt" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="element" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="value" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scormid" TYPE="foreign" FIELDS="scormid" REFTABLE="scorm" REFFIELDS="id"/>
<KEY NAME="scoid" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="userid-scormid-scoid-attempt-element" UNIQUE="true" FIELDS="userid, scormid, scoid, attempt, element"/>
<INDEX NAME="userid" UNIQUE="false" FIELDS="userid"/>
</INDEXES>
</TABLE>
<TABLE NAME="scorm_seq_objective" COMMENT="SCORM2004 objective description">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
@ -222,5 +201,46 @@
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_attempt" COMMENT="List of SCORM attempts made by user.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="scormid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the scorm table"/>
<FIELD NAME="attempt" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="The attempt number"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="user" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id" COMMENT="The id field of the user table"/>
<KEY NAME="scorm" TYPE="foreign" FIELDS="scormid" REFTABLE="scorm" REFFIELDS="id" COMMENT="The id of the scormtable"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_element" COMMENT="List of scorm elements.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="element" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Name of SCORM element"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="element" UNIQUE="true" FIELDS="element"/>
</INDEXES>
</TABLE>
<TABLE NAME="scorm_scoes_value" COMMENT="Values passed from SCORM package">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the scorm_scoes table"/>
<FIELD NAME="attemptid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="id from scorm_attempt"/>
<FIELD NAME="elementid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="id from scorm_element"/>
<FIELD NAME="value" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Value passed from SCORM package"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time value last changed."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scoe" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id" COMMENT="The id of the scorm_scoes table"/>
<KEY NAME="attempt" TYPE="foreign" FIELDS="attemptid" REFTABLE="scorm_attempt" REFFIELDS="id" COMMENT="id from scorm_attempt"/>
<KEY NAME="element" TYPE="foreign" FIELDS="elementid" REFTABLE="scorm_element" REFFIELDS="id" COMMENT="id from scorm_element"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>

View File

@ -30,7 +30,7 @@ defined('MOODLE_INTERNAL') || die();
* @return bool
*/
function xmldb_scorm_upgrade($oldversion) {
global $DB;
global $DB, $OUTPUT;
$dbman = $DB->get_manager();
@ -55,5 +55,118 @@ function xmldb_scorm_upgrade($oldversion) {
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// New table structure for scorm_scoes_track.
if ($oldversion < 2023042401) {
// Define table scorm_attempt to be created.
$table = new xmldb_table('scorm_attempt');
// Adding fields to table scorm_attempt.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('scormid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('attempt', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '1');
// Adding keys to table scorm_attempt.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$table->add_key('user', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
$table->add_key('scorm', XMLDB_KEY_FOREIGN, ['scormid'], 'scorm', ['id']);
// Conditionally launch create table for scorm_attempt.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table scorm_element to be created.
$table = new xmldb_table('scorm_element');
// Adding fields to table scorm_element.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('element', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
// Adding keys to table scorm_element.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
// Adding indexes to table scorm_element.
$table->add_index('element', XMLDB_INDEX_UNIQUE, ['element']);
// Conditionally launch create table for scorm_element.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table scorm_scoes_value to be created.
$table = new xmldb_table('scorm_scoes_value');
// Adding fields to table scorm_scoes_value.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('scoid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('attemptid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('elementid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('value', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
// Adding keys to table scorm_scoes_value.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$table->add_key('scoe', XMLDB_KEY_FOREIGN, ['scoid'], 'scorm_scoes', ['id']);
$table->add_key('attempt', XMLDB_KEY_FOREIGN, ['attemptid'], 'scorm_attempt', ['id']);
$table->add_key('element', XMLDB_KEY_FOREIGN, ['elementid'], 'scorm_element', ['id']);
// Conditionally launch create table for scorm_scoes_value.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
upgrade_mod_savepoint(true, 2023042401, 'scorm');
}
if ($oldversion < 2023042402) {
$trans = $DB->start_delegated_transaction();
// First grab all elements and store those.
$sql = "INSERT INTO {scorm_element} (element)
SELECT DISTINCT element FROM {scorm_scoes_track}";
$DB->execute($sql);
// Now store all data in the scorm_attempt table.
$sql = "INSERT INTO {scorm_attempt} (userid, scormid, attempt)
SELECT DISTINCT userid, scormid, attempt FROM {scorm_scoes_track}";
$DB->execute($sql);
$trans->allow_commit();
// Scorm savepoint reached.
upgrade_mod_savepoint(true, 2023042402, 'scorm');
}
if ($oldversion < 2023042403) {
// Now store all translated data in the scorm_scoes_value table.
$total = $DB->count_records('scorm_scoes_track');
if ($total > 500000) {
// This site has a large number of user track records, lets warn that this next part may take some time.
$notification = new \core\output\notification(get_string('largetrackupgrade', 'scorm', format_float($total, 0)),
\core\output\notification::NOTIFY_WARNING);
$notification->set_show_closebutton(false);
echo $OUTPUT->render($notification);
}
// We don't need a progress bar - just run the fastest option possible.
$sql = "INSERT INTO {scorm_scoes_value} (attemptid, scoid, elementid, value, timemodified)
SELECT a.id as attemptid, t.scoid as scoid, e.id as elementid, t.value as value, t.timemodified
FROM {scorm_scoes_track} t
JOIN {scorm_element} e ON e.element = t.element
JOIN {scorm_attempt} a ON (t.userid = a.userid AND t.scormid = a.scormid AND a.attempt = t.attempt)";
$DB->execute($sql);
// Drop old table scorm_scoes_track.
$table = new xmldb_table('scorm_scoes_track');
// Conditionally launch drop table for scorm_scoes_track.
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
// Scorm savepoint reached.
upgrade_mod_savepoint(true, 2023042403, 'scorm');
}
return true;
}

View File

@ -54,16 +54,18 @@ function scorm_get_completion_state($course, $cm, $userid, $type) {
$tracks = $DB->get_records_sql(
"
SELECT
id,
scoid,
element,
value
v.id,
v.scoid,
e.element,
v.value
FROM
{scorm_scoes_track}
{scorm_scoes_value} v
JOIN {scorm_attempt} a on a.id = v.attemptid
JOIN {scorm_element} e on e.id = v.elementid
WHERE
scormid = ?
AND userid = ?
AND element IN
a.scormid = ?
AND a.userid = ?
AND e.element IN
(
'cmi.core.lesson_status',
'cmi.completion_status',

View File

@ -71,6 +71,7 @@ $string['browse'] = 'Preview';
$string['browsed'] = 'Browsed';
$string['browsemode'] = 'Preview mode';
$string['browserepository'] = 'Browse repository';
$string['cachedef_elements'] = 'Element cache';
$string['calculatedweight'] = 'Calculated weight';
$string['calendarend'] = '{$a} closes';
$string['calendarstart'] = '{$a} opens';
@ -213,6 +214,7 @@ $string['indicator:socialbreadthdef_help'] = 'The participant has reached this p
$string['indicator:socialbreadthdef_link'] = 'Learning_analytics_indicators#Social_breadth';
$string['interactions'] = 'Interactions';
$string['largetrackupgrade'] = 'This next upgrade step may take some time to complete, your site has {$a} SCORM track records that need to be migrated to the new table structure, please be patient as a progress bar is not able to be displayed.';
$string['masteryoverride'] = 'Mastery score overrides status';
$string['masteryoverride_help'] = 'If enabled and a mastery score is provided, when LMSFinish is called and a raw score has been set, status will be recalculated using the raw score and mastery score and any status provided by the SCORM (including "incomplete") will be overridden.';
$string['masteryoverridedesc'] = 'This preference sets the default for the mastery score override setting';
@ -354,7 +356,7 @@ $string['privacy:metadata:attempt'] = 'The attempt number';
$string['privacy:metadata:scoes_track:element'] = 'The name of the element to be tracked';
$string['privacy:metadata:scoes_track:value'] = 'The value of the given element';
$string['privacy:metadata:scorm_aicc_session'] = 'The session information of the AICC HACP';
$string['privacy:metadata:scorm_scoes_track'] = 'The tracked data of the SCOes belonging to the activity';
$string['privacy:metadata:scorm_attempt'] = 'The SCORM attempts made by a user';
$string['privacy:metadata:timemodified'] = 'The time when the tracked element was last modified';
$string['privacy:metadata:userid'] = 'The ID of the user who accessed the SCORM activity';
$string['protectpackagedownloads'] = 'Protect package downloads';

View File

@ -293,9 +293,7 @@ function scorm_delete_instance($id) {
$result = true;
// Delete any dependent records.
if (! $DB->delete_records('scorm_scoes_track', array('scormid' => $scorm->id))) {
$result = false;
}
scorm_delete_tracks($scorm->id);
if ($scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id))) {
foreach ($scoes as $sco) {
if (! $DB->delete_records('scorm_scoes_data', array('scoid' => $sco->id))) {
@ -594,31 +592,33 @@ function scorm_get_user_grades($scorm, $userid=0) {
$grades = array();
if (empty($userid)) {
$scousers = $DB->get_records_select('scorm_scoes_track', "scormid=? GROUP BY userid",
array($scorm->id), "", "userid,null");
if ($scousers) {
foreach ($scousers as $scouser) {
$grades[$scouser->userid] = new stdClass();
$grades[$scouser->userid]->id = $scouser->userid;
$grades[$scouser->userid]->userid = $scouser->userid;
$grades[$scouser->userid]->rawgrade = scorm_grade_user($scorm, $scouser->userid);
}
} else {
return false;
}
$sql = "SELECT DISTINCT userid
FROM {scorm_attempt}
WHERE scormid = ?";
$scousers = $DB->get_recordset_sql($sql, [$scorm->id]);
foreach ($scousers as $scouser) {
$grades[$scouser->userid] = new stdClass();
$grades[$scouser->userid]->id = $scouser->userid;
$grades[$scouser->userid]->userid = $scouser->userid;
$grades[$scouser->userid]->rawgrade = scorm_grade_user($scorm, $scouser->userid);
}
$scousers->close();
} else {
$preattempt = $DB->get_records_select('scorm_scoes_track', "scormid=? AND userid=? GROUP BY userid",
array($scorm->id, $userid), "", "userid,null");
$preattempt = $DB->record_exists('scorm_attempt', ['scormid' => $scorm->id, 'userid' => $userid]);
if (!$preattempt) {
return false; // No attempt yet.
}
$grades[$userid] = new stdClass();
$grades[$userid]->id = $userid;
$grades[$userid]->userid = $userid;
$grades[$userid]->id = $userid;
$grades[$userid]->userid = $userid;
$grades[$userid]->rawgrade = scorm_grade_user($scorm, $userid);
}
if (empty($grades)) {
return false;
}
return $grades;
}
@ -819,30 +819,31 @@ function scorm_reset_gradebook($courseid, $type='') {
* @return array status array
*/
function scorm_reset_userdata($data) {
global $CFG, $DB;
global $DB;
$componentstr = get_string('modulenameplural', 'scorm');
$status = array();
$status = [];
if (!empty($data->reset_scorm)) {
$scormssql = "SELECT s.id
FROM {scorm} s
WHERE s.course=?";
$DB->delete_records_select('scorm_scoes_track', "scormid IN ($scormssql)", array($data->courseid));
$scorms = $DB->get_recordset('scorm', ['course' => $data->courseid]);
foreach ($scorms as $scorm) {
scorm_delete_tracks($scorm->id);
}
$scorms->close();
// Remove all grades from gradebook.
if (empty($data->reset_gradebook_grades)) {
scorm_reset_gradebook($data->courseid);
}
$status[] = array('component' => $componentstr, 'item' => get_string('deleteallattempts', 'scorm'), 'error' => false);
$status[] = ['component' => $componentstr, 'item' => get_string('deleteallattempts', 'scorm'), 'error' => false];
}
// Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
// See MDL-9367.
shift_course_mod_dates('scorm', array('timeopen', 'timeclose'), $data->timeshift, $data->courseid);
$status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => false);
$status[] = ['component' => $componentstr, 'item' => get_string('datechanged'), 'error' => false];
return $status;
}
@ -1296,7 +1297,7 @@ function scorm_check_mode($scorm, &$newattempt, &$attempt, $userid, &$mode) {
$mode = 'normal';
if ($attempt == 1) {
// Check if the user has any existing data or if this is really the first attempt.
$exists = $DB->record_exists('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scorm->id));
$exists = $DB->record_exists('scorm_attempt', ['userid' => $userid, 'scormid' => $scorm->id]);
if (!$exists) {
// No records yet - Attempt should == 1.
return;
@ -1326,12 +1327,17 @@ function scorm_check_mode($scorm, &$newattempt, &$attempt, $userid, &$mode) {
}
$completionelement = $completionelements[$scormversion];
$sql = "SELECT sc.id, t.value
$sql = "SELECT sc.id, sub.value
FROM {scorm_scoes} sc
LEFT JOIN {scorm_scoes_track} t ON sc.scorm = t.scormid AND sc.id = t.scoid
AND t.element = ? AND t.userid = ? AND t.attempt = ?
WHERE sc.scormtype = 'sco' AND sc.scorm = ?";
$tracks = $DB->get_recordset_sql($sql, array($completionelement, $userid, $attempt, $scorm->id));
LEFT JOIN (SELECT v.scoid, v.value
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON a.id = v.attemptid
JOIN {scorm_element} e on e.id = v.elementid AND e.element = :element
WHERE a.userid = :userid AND a.attempt = :attempt AND a.scormid = :scormid) sub ON sub.scoid = sc.id
WHERE sc.scormtype = 'sco' AND sc.scorm = :scormid2";
$tracks = $DB->get_recordset_sql($sql, ['userid' => $userid, 'attempt' => $attempt,
'element' => $completionelement, 'scormid' => $scorm->id,
'scormid2' => $scorm->id]);
foreach ($tracks as $track) {
if (($track->value == 'completed') || ($track->value == 'passed') || ($track->value == 'failed')) {
@ -1410,9 +1416,12 @@ function scorm_check_updates_since(cm_info $cm, $from, $filter = array()) {
$updates = course_check_module_updates_since($cm, $from, array('package'), $filter);
$updates->tracks = (object) array('updated' => false);
$select = 'scormid = ? AND userid = ? AND timemodified > ?';
$params = array($scorm->id, $USER->id, $from);
$tracks = $DB->get_records_select('scorm_scoes_track', $select, $params, '', 'id');
$sql = "SELECT v.id
FROM {scorm_scoes_value} v
JOIN {scorm_attempt} a ON a.id = v.attemptid
WHERE a.scormid = :scormid AND v.timemodified > :timemodified";
$params = ['scormid' => $scorm->id, 'timemodified' => $from, 'userid' => $USER->id];
$tracks = $DB->get_records_sql($sql ." AND a.userid = :userid", $params);
if (!empty($tracks)) {
$updates->tracks->updated = true;
$updates->tracks->itemids = array_keys($tracks);
@ -1420,21 +1429,21 @@ function scorm_check_updates_since(cm_info $cm, $from, $filter = array()) {
// Now, teachers should see other students updates.
if (has_capability('mod/scorm:viewreport', $cm->context)) {
$select = 'scormid = ? AND timemodified > ?';
$params = array($scorm->id, $from);
$params = ['scormid' => $scorm->id, 'timemodified' => $from];
if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
$groupusers = array_keys(groups_get_activity_shared_group_members($cm));
if (empty($groupusers)) {
return $updates;
}
list($insql, $inparams) = $DB->get_in_or_equal($groupusers);
$select .= ' AND userid ' . $insql;
list($insql, $inparams) = $DB->get_in_or_equal($groupusers, SQL_PARAMS_NAMED);
$sql .= ' AND userid ' . $insql;
$params = array_merge($params, $inparams);
}
$updates->usertracks = (object) array('updated' => false);
$tracks = $DB->get_records_select('scorm_scoes_track', $select, $params, '', 'id');
$tracks = $DB->get_records_sql($sql, $params);
if (!empty($tracks)) {
$updates->usertracks->updated = true;
$updates->usertracks->itemids = array_keys($tracks);

View File

@ -436,29 +436,46 @@ function scorm_get_scoes($id, $organisation=false) {
}
}
function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $value, $forcecompleted=false, $trackdata = null) {
/**
* Insert SCORM track into db.
*
* @param int $userid The userid
* @param int $scormid The id from scorm table
* @param int $scoid The scoid
* @param int|stdClass $attemptornumber - number of attempt or attempt record from scorm_attempt table.
* @param string $element The element being saved
* @param string $value The value of the element
* @param boolean $forcecompleted Force this sco as completed
* @param stdclass $trackdata - existing tracking data
* @return int - the id of the record being saved.
*/
function scorm_insert_track($userid, $scormid, $scoid, $attemptornumber, $element, $value, $forcecompleted=false, $trackdata = null) {
global $DB, $CFG;
if (is_object($attemptornumber)) {
$attempt = $attemptornumber;
} else {
$attempt = scorm_get_attempt($userid, $scormid, $attemptornumber);
}
$id = null;
if ($forcecompleted) {
// TODO - this could be broadened to encompass SCORM 2004 in future.
if (($element == 'cmi.core.lesson_status') && ($value == 'incomplete')) {
if ($track = $DB->get_record_select('scorm_scoes_track',
'userid=? AND scormid=? AND scoid=? AND attempt=? '.
'AND element=\'cmi.core.score.raw\'',
array($userid, $scormid, $scoid, $attempt))) {
$track = scorm_get_sco_value($scoid, $userid, 'cmi.core.score.raw', $attempt->attempt);
if (!empty($track)) {
$value = 'completed';
}
}
if ($element == 'cmi.core.score.raw') {
if ($tracktest = $DB->get_record_select('scorm_scoes_track',
'userid=? AND scormid=? AND scoid=? AND attempt=? '.
'AND element=\'cmi.core.lesson_status\'',
array($userid, $scormid, $scoid, $attempt))) {
$tracktest = scorm_get_sco_value($scoid, $userid, 'cmi.core.lesson_status', $attempt->attempt);
if (!empty($tracktest)) {
if ($tracktest->value == "incomplete") {
$tracktest->value = "completed";
$DB->update_record('scorm_scoes_track', $tracktest);
$v = new stdClass();
$v->id = $track->valueid;
$v->value = "completed";
$DB->update_record('scorm_scoes_value', $v);
}
}
}
@ -469,47 +486,40 @@ function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $valu
if ($value == 'passed') {
$objectivesatisfiedstatus = true;
}
if ($track = $DB->get_record('scorm_scoes_track', array('userid' => $userid,
'scormid' => $scormid,
'scoid' => $scoid,
'attempt' => $attempt,
'element' => 'objectiveprogressstatus'))) {
$track->value = $objectiveprogressstatus;
$track->timemodified = time();
$DB->update_record('scorm_scoes_track', $track);
$id = $track->id;
$track = scorm_get_sco_value($scoid, $userid, 'objectiveprogressstatus', $attempt->attempt);
if (!empty($track)) {
$v = new stdClass();
$v->id = $track->valueid;
$v->value = $objectiveprogressstatus;
$v->timemodified = time();
$DB->update_record('scorm_scoes_value', $v);
$id = $track->valueid;
} else {
$track = new stdClass();
$track->userid = $userid;
$track->scormid = $scormid;
$track->scoid = $scoid;
$track->attempt = $attempt;
$track->element = 'objectiveprogressstatus';
$track->attemptid = $attempt->id;
$track->elementid = scorm_get_elementid('objectiveprogressstatus');
$track->value = $objectiveprogressstatus;
$track->timemodified = time();
$id = $DB->insert_record('scorm_scoes_track', $track);
$id = $DB->insert_record('scorm_scoes_value', $track);
}
if ($objectivesatisfiedstatus) {
if ($track = $DB->get_record('scorm_scoes_track', array('userid' => $userid,
'scormid' => $scormid,
'scoid' => $scoid,
'attempt' => $attempt,
'element' => 'objectivesatisfiedstatus'))) {
$track->value = $objectivesatisfiedstatus;
$track->timemodified = time();
$DB->update_record('scorm_scoes_track', $track);
$id = $track->id;
$track = scorm_get_sco_value($scoid, $userid, 'objectivesatisfiedstatus', $attempt->attempt);
if (!empty($track)) {
$v = new stdClass();
$v->id = $track->valueid;
$v->value = $objectivesatisfiedstatus;
$v->timemodified = time();
$DB->update_record('scorm_scoes_value', $v);
$id = $track->valueid;
} else {
$track = new stdClass();
$track->userid = $userid;
$track->scormid = $scormid;
$track->scoid = $scoid;
$track->attempt = $attempt;
$track->element = 'objectivesatisfiedstatus';
$track->attemptid = $attempt->id;
$track->elementid = scorm_get_elementid('objectivesatisfiedstatus');
$track->value = $objectivesatisfiedstatus;
$track->timemodified = time();
$id = $DB->insert_record('scorm_scoes_track', $track);
$id = $DB->insert_record('scorm_scoes_value', $track);
}
}
}
@ -523,31 +533,27 @@ function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $valu
$track = $trackdata[$element];
}
} else {
$track = $DB->get_record('scorm_scoes_track', array('userid' => $userid,
'scormid' => $scormid,
'scoid' => $scoid,
'attempt' => $attempt,
'element' => $element));
$track = scorm_get_sco_value($scoid, $userid, $element, $attempt->attempt);
}
if ($track) {
if ($element != 'x.start.time' ) { // Don't update x.start.time - keep the original value.
if ($track->value != $value) {
$track->value = $value;
$track->timemodified = time();
$DB->update_record('scorm_scoes_track', $track);
$v = new stdClass();
$v->id = $track->valueid;
$v->value = $value;
$v->timemodified = time();
$DB->update_record('scorm_scoes_value', $v);
}
$id = $track->id;
$id = $track->valueid;
}
} else {
$track = new stdClass();
$track->userid = $userid;
$track->scormid = $scormid;
$track->scoid = $scoid;
$track->attempt = $attempt;
$track->element = $element;
$track->attemptid = $attempt->id;
$track->elementid = scorm_get_elementid($element);
$track->value = $value;
$track->timemodified = time();
$id = $DB->insert_record('scorm_scoes_track', $track);
$id = $DB->insert_record('scorm_scoes_value', $track);
$track->id = $id;
}
@ -570,7 +576,7 @@ function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $valu
}
$cm = get_coursemodule_from_instance('scorm', $scormid);
$data = array(
'other' => array('attemptid' => $attempt, 'cmielement' => $element, 'cmivalue' => $track->value),
'other' => array('attemptid' => $attempt->id, 'cmielement' => $element, 'cmivalue' => $track->value),
'objectid' => $scorm->id,
'context' => context_module::instance($cm->id),
'relateduserid' => $userid
@ -584,13 +590,12 @@ function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $valu
}
// Fix the missing track keys when the SCORM track record already exists, see $trackdata in datamodel.php.
// There, for performances reasons, columns are limited to: element, id, value, timemodified.
// Missing fields are: userid, scormid, scoid, attempt.
$track->userid = $userid;
$track->scormid = $scormid;
// Missing fields are: scoid, attempt.
$track->scoid = $scoid;
$track->attempt = $attempt;
$track->attempt = $attempt->id;
$track->id = $id;
// Trigger submitted event.
$event->add_record_snapshot('scorm_scoes_track', $track);
$event->add_record_snapshot('scorm_scoes_value', $track);
$event->add_record_snapshot('course_modules', $cm);
$event->add_record_snapshot('scorm', $scorm);
$event->trigger();
@ -608,7 +613,7 @@ function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $valu
*/
function scorm_has_tracks($scormid, $userid) {
global $DB;
return $DB->record_exists('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scormid));
return $DB->record_exists('scorm_attempt', ['userid' => $userid, 'scormid' => $scormid]);
}
function scorm_get_tracks($scoid, $userid, $attempt='') {
@ -616,14 +621,19 @@ function scorm_get_tracks($scoid, $userid, $attempt='') {
global $DB;
if (empty($attempt)) {
if ($scormid = $DB->get_field('scorm_scoes', 'scorm', array('id' => $scoid))) {
if ($scormid = $DB->get_field('scorm_scoes', 'scorm', ['id' => $scoid])) {
$attempt = scorm_get_last_attempt($scormid, $userid);
} else {
$attempt = 1;
}
}
if ($tracks = $DB->get_records('scorm_scoes_track', array('userid' => $userid, 'scoid' => $scoid,
'attempt' => $attempt), 'element ASC')) {
$sql = "SELECT v.id, a.userid, a.scormid, v.scoid, a.attempt, v.value, v.timemodified, e.element
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON v.attemptid = a.id
JOIN {scorm_element} e ON e.id = v.elementid
WHERE a.userid = ? AND v.scoid = ? AND a.attempt = ?
ORDER BY e.element ASC";
if ($tracks = $DB->get_records_sql($sql, [$userid, $scoid, $attempt])) {
$usertrack = scorm_format_interactions($tracks);
$usertrack->userid = $userid;
$usertrack->scoid = $scoid;
@ -636,7 +646,7 @@ function scorm_get_tracks($scoid, $userid, $attempt='') {
/**
* helper function to return a formatted list of interactions for reports.
*
* @param array $trackdata the records from scorm_scoes_track table
* @param array $trackdata the user tracking records from the database
* @return object formatted list of interactions
*/
function scorm_format_interactions($trackdata) {
@ -693,27 +703,24 @@ function scorm_format_interactions($trackdata) {
function scorm_get_sco_runtime($scormid, $scoid, $userid, $attempt=1) {
global $DB;
$timedata = new stdClass();
$params = array('userid' => $userid, 'scormid' => $scormid, 'attempt' => $attempt);
$sql = "SELECT min(timemodified) as start, max(timemodified) as finish
FROM {scorm_scoes_value} v
JOIN {scorm_attempt} a on a.id = v.attemptid
WHERE a.userid = :userid AND a.scormid = :scormid AND a.attempt = :attempt";
if (!empty($scoid)) {
$params['scoid'] = $scoid;
$sql .= " AND v.scoid = :scoid";
}
$tracks = $DB->get_records('scorm_scoes_track', $params, "timemodified ASC");
if ($tracks) {
$tracks = array_values($tracks);
}
if ($tracks) {
$timedata->start = $tracks[0]->timemodified;
$timedata = $DB->get_record_sql($sql, $params);
if (!empty($timedata)) {
return $timedata;
} else {
$timedata = new stdClass();
$timedata->start = false;
return $timedata;
}
if ($tracks && $track = array_pop($tracks)) {
$timedata->finish = $track->timemodified;
} else {
$timedata->finish = $timedata->start;
}
return $timedata;
}
function scorm_grade_user_attempt($scorm, $userid, $attempt=1) {
@ -840,7 +847,7 @@ function scorm_get_last_attempt($scormid, $userid) {
// Find the last attempt number for the given user id and scorm id.
$sql = "SELECT MAX(attempt)
FROM {scorm_scoes_track}
FROM {scorm_attempt}
WHERE userid = ? AND scormid = ?";
$lastattempt = $DB->get_field_sql($sql, array($userid, $scormid));
if (empty($lastattempt)) {
@ -863,7 +870,7 @@ function scorm_get_first_attempt($scormid, $userid) {
// Find the first attempt number for the given user id and scorm id.
$sql = "SELECT MIN(attempt)
FROM {scorm_scoes_track}
FROM {scorm_attempt}
WHERE userid = ? AND scormid = ?";
$lastattempt = $DB->get_field_sql($sql, array($userid, $scormid));
@ -886,12 +893,14 @@ function scorm_get_last_completed_attempt($scormid, $userid) {
global $DB;
// Find the last completed attempt number for the given user id and scorm id.
$sql = "SELECT MAX(attempt)
FROM {scorm_scoes_track}
$sql = "SELECT MAX(a.attempt)
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON v.attemptid = a.id
JOIN {scorm_element} e ON e.id = v.elementid
WHERE userid = ? AND scormid = ?
AND (".$DB->sql_compare_text('value')." = ".$DB->sql_compare_text('?')." OR ".
$DB->sql_compare_text('value')." = ".$DB->sql_compare_text('?').")";
$lastattempt = $DB->get_field_sql($sql, array($userid, $scormid, 'completed', 'passed'));
AND (" . $DB->sql_compare_text('v.value') . " = " . $DB->sql_compare_text('?') . " OR ".
$DB->sql_compare_text('v.value') . " = " . $DB->sql_compare_text('?') . ")";
$lastattempt = $DB->get_field_sql($sql, [$userid, $scormid, 'completed', 'passed']);
if (empty($lastattempt)) {
return '1';
} else {
@ -910,8 +919,8 @@ function scorm_get_last_completed_attempt($scormid, $userid) {
function scorm_get_all_attempts($scormid, $userid) {
global $DB;
$attemptids = array();
$sql = "SELECT DISTINCT attempt FROM {scorm_scoes_track} WHERE userid = ? AND scormid = ? ORDER BY attempt";
$attempts = $DB->get_records_sql($sql, array($userid, $scormid));
$sql = "SELECT DISTINCT attempt FROM {scorm_attempt} WHERE userid = ? AND scormid = ? ORDER BY attempt";
$attempts = $DB->get_records_sql($sql, [$userid, $scormid]);
foreach ($attempts as $attempt) {
$attemptids[] = $attempt->attempt;
}
@ -926,7 +935,7 @@ function scorm_get_all_attempts($scormid, $userid) {
* @param string $action base URL for the organizations select box
* @param stdClass $cm course module object
*/
function scorm_print_launch ($user, $scorm, $action, $cm) {
function scorm_print_launch($user, $scorm, $action, $cm) {
global $CFG, $DB, $OUTPUT;
if ($scorm->updatefreq == SCORM_UPDATE_EVERYTIME) {
@ -1084,7 +1093,7 @@ function scorm_get_count_users($scormid, $groupingid=null) {
if (!empty($groupingid)) {
$sql = "SELECT COUNT(DISTINCT st.userid)
FROM {scorm_scoes_track} st
FROM {scorm_attempt} st
INNER JOIN {groups_members} gm ON st.userid = gm.userid
INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid
WHERE st.scormid = ? AND gg.groupingid = ?
@ -1092,7 +1101,7 @@ function scorm_get_count_users($scormid, $groupingid=null) {
$params = array($scormid, $groupingid);
} else {
$sql = "SELECT COUNT(DISTINCT st.userid)
FROM {scorm_scoes_track} st
FROM {scorm_attempt} st
WHERE st.scormid = ?
";
$params = array($scormid);
@ -1334,7 +1343,7 @@ function scorm_get_attempt_status($user, $scorm, $cm='') {
if (!empty($cm)) {
$context = context_module::instance($cm->id);
if (has_capability('mod/scorm:deleteownresponses', $context) &&
$DB->record_exists('scorm_scoes_track', array('userid' => $user->id, 'scormid' => $scorm->id))) {
$DB->record_exists('scorm_attempt', ['userid' => $user->id, 'scormid' => $scorm->id])) {
// Check to see if any data is stored for this user.
$deleteurl = new moodle_url($PAGE->url, array('action' => 'delete', 'sesskey' => sesskey()));
$result .= $OUTPUT->single_button($deleteurl, get_string('deleteallattempts', 'scorm'));
@ -1370,17 +1379,30 @@ function scorm_get_attempt_count($userid, $scorm, $returnobjects = false, $ignor
$params = array('userid' => $userid, 'scormid' => $scorm->id);
if ($ignoremissingcompletion) { // Exclude attempts that don't have the completion element requested.
$params['element'] = $element;
$sql = "SELECT DISTINCT a.attempt AS attemptnumber
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON v.attemptid = a.id
JOIN {scorm_element} e ON e.id = v.elementid
WHERE a.userid = :userid AND a.scormid = :scormid AND e.element = :element ORDER BY a.attempt";
$attempts = $DB->get_records_sql($sql, $params);
} else {
$attempts = $DB->get_records('scorm_attempt', $params, 'attempt', 'DISTINCT attempt AS attemptnumber');
}
$attempts = $DB->get_records('scorm_scoes_track', $params, 'attempt', 'DISTINCT attempt AS attemptnumber');
return $attempts;
} else {
$params = array($userid, $scorm->id);
$sql = "SELECT COUNT(DISTINCT attempt)
FROM {scorm_scoes_track}
WHERE userid = ? AND scormid = ?";
$params = ['userid' => $userid, 'scormid' => $scorm->id];
if ($ignoremissingcompletion) { // Exclude attempts that don't have the completion element requested.
$sql .= ' AND element = ?';
$params[] = $element;
$params['element'] = $element;
$sql = "SELECT COUNT(DISTINCT a.attempt)
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON v.attemptid = a.id
JOIN {scorm_element} e ON e.id = v.elementid
WHERE a.userid = :userid AND a.scormid = :scormid AND e.element = :element";
} else {
$sql = "SELECT COUNT(DISTINCT attempt)
FROM {scorm_attempt}
WHERE userid = :userid AND scormid = :scormid";
}
$attemptscount = $DB->count_records_sql($sql, $params);
@ -1453,22 +1475,26 @@ function scorm_delete_responses($attemptids, $scorm) {
*
* @param int $userid ID of User
* @param stdClass $scorm Scorm object
* @param int $attemptid user attempt that need to be deleted
* @param int|stdClass $attemptornumber user attempt that need to be deleted
*
* @return bool true suceeded
*/
function scorm_delete_attempt($userid, $scorm, $attemptid) {
global $DB;
function scorm_delete_attempt($userid, $scorm, $attemptornumber) {
if (is_object($attemptornumber)) {
$attempt = $attemptornumber;
} else {
$attempt = scorm_get_attempt($userid, $scorm->id, $attemptornumber, false);
}
$DB->delete_records('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scorm->id, 'attempt' => $attemptid));
scorm_delete_tracks($scorm->id, null, $userid, $attempt->id);
$cm = get_coursemodule_from_instance('scorm', $scorm->id);
// Trigger instances list viewed event.
$event = \mod_scorm\event\attempt_deleted::create(array(
'other' => array('attemptid' => $attemptid),
$event = \mod_scorm\event\attempt_deleted::create([
'other' => ['attemptid' => $attempt->attempt],
'context' => context_module::instance($cm->id),
'relateduserid' => $userid
));
]);
$event->add_record_snapshot('course_modules', $cm);
$event->add_record_snapshot('scorm', $scorm);
$event->trigger();
@ -2496,6 +2522,123 @@ function scorm_update_calendar(stdClass $scorm, $cmid) {
calendar_event::create($event, false);
}
}
return true;
}
/**
* Function to delete user tracks from tables.
*
* @param int $scormid - id from scorm.
* @param int $scoid - id of sco that needs to be deleted.
* @param int $userid - userid that needs to be deleted.
* @param int $attemptid - attemptid that should be deleted.
* @since Moodle 4.3
*/
function scorm_delete_tracks($scormid, $scoid = null, $userid = null, $attemptid = null) {
global $DB;
$usersql = '';
$params = ['scormid' => $scormid];
if (!empty($attemptid)) {
$params['attemptid'] = $attemptid;
$sql = "attemptid = :attemptid";
} else {
if (!empty($userid)) {
$usersql = ' AND userid = :userid';
$params['userid'] = $userid;
}
$sql = "attemptid in (SELECT id FROM {scorm_attempt} WHERE scormid = :scormid $usersql)";
}
if (!empty($scoid)) {
$params['scoid'] = $scoid;
$sql .= " AND scoid = :scoid";
}
$DB->delete_records_select('scorm_scoes_value', $sql, $params);
if (empty($scoid)) {
if (empty($attemptid)) {
// Scoid is empty so we delete the attempt as well.
$DB->delete_records('scorm_attempt', $params);
} else {
$DB->delete_records('scorm_attempt', ['id' => $attemptid]);
}
}
}
/**
* Get specific scorm track data.
* Note: the $attempt var is optional as SCORM 2004 code doesn't always use it, probably a bug,
* but we do not want to change SCORM 2004 behaviour right now.
*
* @param int $scoid - scoid.
* @param int $userid - user id of user.
* @param string $element - name of element being requested.
* @param int $attempt - attempt number (not id)
* @since Moodle 4.3
* @return mixed
*/
function scorm_get_sco_value($scoid, $userid, $element, $attempt = null): ?stdClass {
global $DB;
$params = ['scoid' => $scoid, 'userid' => $userid, 'element' => $element];
$sql = "SELECT a.id, a.userid, a.scormid, a.attempt, v.id as valueid, v.scoid, v.value, v.timemodified, e.element
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON v.attemptid = a.id
JOIN {scorm_element} e on e.id = v.elementid
WHERE v.scoid = :scoid AND a.userid = :userid AND e.element = :element";
if ($attempt !== null) {
$params['attempt'] = $attempt;
$sql .= " AND a.attempt = :attempt";
}
$value = $DB->get_record_sql($sql, $params);
return $value ?: null;
}
/**
* Get attempt record, allow one to be created if doesn't exist.
*
* @param int $userid - user id.
* @param int $scormid - SCORM id.
* @param int $attempt - attempt number.
* @param boolean $create - should an attempt record be created if it does not exist.
* @since Moodle 4.3
* @return stdclass
*/
function scorm_get_attempt($userid, $scormid, $attempt, $create = true): ?stdClass {
global $DB;
$params = ['scormid' => $scormid, 'userid' => $userid, 'attempt' => $attempt];
$attemptobject = $DB->get_record('scorm_attempt', $params);
if (empty($attemptobject) && $create) {
// Create new attempt.
$attemptobject = new stdClass();
$attemptobject->userid = $userid;
$attemptobject->attempt = $attempt;
$attemptobject->scormid = $scormid;
$attemptobject->id = $DB->insert_record('scorm_attempt', $attemptobject);
}
return $attemptobject ?: null;
}
/**
* Get Scorm element id from cache, allow one to be created if doesn't exist.
*
* @param string $elementname - name of element that is being requested.
* @since Moodle 4.3
* @return int - element id.
*/
function scorm_get_elementid($elementname): ?int {
global $DB;
$cache = cache::make('mod_scorm', 'elements');
$element = $cache->get($elementname);
if (empty($element)) {
// Create new attempt.
$element = new stdClass();
$element->element = $elementname;
$elementid = $DB->insert_record('scorm_element', $element);
$cache->set($elementname, $elementid);
return $elementid;
} else {
return $element;
}
}

View File

@ -274,32 +274,32 @@ class report extends \mod_scorm\report {
$csvexport->add_data($headers);
}
// Construct the SQL.
$select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').' AS uniqueid, ';
$select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(sa.attempt, 0)').' AS uniqueid, ';
// TODO Does not support custom user profile fields (MDL-70456).
$userfields = \core_user\fields::for_identity($coursecontext, false)->with_userpic()->including('idnumber');
$selectfields = $userfields->get_sql('u', false, '', 'userid')->selects;
$select .= 'st.scormid AS scormid, st.attempt AS attempt ' . $selectfields . ' ';
$select .= 'sa.scormid AS scormid, sa.attempt AS attempt ' . $selectfields . ' ';
// This part is the same for all cases - join users and scorm_scoes_track tables.
// This part is the same for all cases - join users and user tracking tables.
$from = 'FROM {user} u ';
$from .= 'LEFT JOIN {scorm_scoes_track} st ON st.userid = u.id AND st.scormid = '.$scorm->id;
$from .= 'LEFT JOIN {scorm_attempt} sa ON sa.userid = u.id AND sa.scormid = '.$scorm->id;
switch ($attemptsmode) {
case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH:
// Show only students with attempts.
$where = " WHERE u.id IN ({$allowedlistsql}) AND st.userid IS NOT NULL";
$where = " WHERE u.id IN ({$allowedlistsql}) AND sa.userid IS NOT NULL";
break;
case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
// Show only students without attempts.
$where = " WHERE u.id IN ({$allowedlistsql}) AND st.userid IS NULL";
$where = " WHERE u.id IN ({$allowedlistsql}) AND sa.userid IS NULL";
break;
case SCORM_REPORT_ATTEMPTS_ALL_STUDENTS:
// Show all students with or without attempts.
$where = " WHERE u.id IN ({$allowedlistsql}) AND (st.userid IS NOT NULL OR st.userid IS NULL)";
$where = " WHERE u.id IN ({$allowedlistsql}) AND (sa.userid IS NOT NULL OR sa.userid IS NULL)";
break;
}
$countsql = 'SELECT COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').')) AS nbresults, ';
$countsql .= 'COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'st.attempt').')) AS nbattempts, ';
$countsql = 'SELECT COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(sa.attempt, 0)').')) AS nbresults, ';
$countsql .= 'COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'sa.attempt').')) AS nbattempts, ';
$countsql .= 'COUNT(DISTINCT(u.id)) AS nbusers ';
$countsql .= $from.$where;

View File

@ -61,13 +61,14 @@ class report extends \mod_scorm\report {
$params = array_merge($params, ['scoid' => $scoid]);
// Construct the SQL.
$sql = "SELECT DISTINCT " . $DB->sql_concat('st.userid', '\'#\'', 'COALESCE(st.attempt, 0)') . " AS uniqueid,
st.userid AS userid,
st.scormid AS scormid,
st.attempt AS attempt,
st.scoid AS scoid
FROM {scorm_scoes_track} st
WHERE st.userid IN ({$allowedlist}) AND st.scoid = :scoid";
$sql = "SELECT DISTINCT " . $DB->sql_concat('a.userid', '\'#\'', 'COALESCE(a.attempt, 0)') . " AS uniqueid,
a.userid AS userid,
a.scormid AS scormid,
a.attempt AS attempt,
v.scoid AS scoid
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON v.attemptid = a.id
WHERE a.userid IN ({$allowedlist}) AND v.scoid = :scoid";
$attempts = $DB->get_records_sql($sql, $params);
$usergrades = [];

View File

@ -163,32 +163,32 @@ class report extends \mod_scorm\report {
}
// Construct the SQL.
$select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').' AS uniqueid, ';
$select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(sa.attempt, 0)').' AS uniqueid, ';
// TODO Does not support custom user profile fields (MDL-70456).
$userfields = \core_user\fields::for_identity($coursecontext, false)->with_userpic()->including('idnumber');
$selectfields = $userfields->get_sql('u', false, '', 'userid')->selects;
$select .= 'st.scormid AS scormid, st.attempt AS attempt ' . $selectfields . ' ';
$select .= 'sa.scormid AS scormid, sa.attempt AS attempt ' . $selectfields . ' ';
// This part is the same for all cases - join users and scorm_scoes_track tables.
// This part is the same for all cases - join users and user tracking tables.
$from = 'FROM {user} u ';
$from .= 'LEFT JOIN {scorm_scoes_track} st ON st.userid = u.id AND st.scormid = '.$scorm->id;
$from .= 'LEFT JOIN {scorm_attempt} sa ON sa.userid = u.id AND sa.scormid = '.$scorm->id;
switch ($attemptsmode) {
case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH:
// Show only students with attempts.
$where = " WHERE u.id IN ({$allowedlistsql}) AND st.userid IS NOT NULL";
$where = " WHERE u.id IN ({$allowedlistsql}) AND sa.userid IS NOT NULL";
break;
case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
// Show only students without attempts.
$where = " WHERE u.id IN ({$allowedlistsql}) AND st.userid IS NULL";
$where = " WHERE u.id IN ({$allowedlistsql}) AND sa.userid IS NULL";
break;
case SCORM_REPORT_ATTEMPTS_ALL_STUDENTS:
// Show all students with or without attempts.
$where = " WHERE u.id IN ({$allowedlistsql}) AND (st.userid IS NOT NULL OR st.userid IS NULL)";
$where = " WHERE u.id IN ({$allowedlistsql}) AND (sa.userid IS NOT NULL OR sa.userid IS NULL)";
break;
}
$countsql = 'SELECT COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').')) AS nbresults, ';
$countsql .= 'COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'st.attempt').')) AS nbattempts, ';
$countsql = 'SELECT COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(sa.attempt, 0)').')) AS nbresults, ';
$countsql .= 'COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'sa.attempt').')) AS nbattempts, ';
$countsql .= 'COUNT(DISTINCT(u.id)) AS nbusers ';
$countsql .= $from.$where;
$questioncount = get_scorm_question_count($scorm->id);

View File

@ -157,32 +157,32 @@ class report extends \mod_scorm\report {
}
// Construct the SQL.
$select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').' AS uniqueid, ';
$select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(sa.attempt, 0)').' AS uniqueid, ';
// TODO Does not support custom user profile fields (MDL-70456).
$userfields = \core_user\fields::for_identity($coursecontext, false)->with_userpic()->including('idnumber');
$selectfields = $userfields->get_sql('u', false, '', 'userid')->selects;
$select .= 'st.scormid AS scormid, st.attempt AS attempt ' . $selectfields . ' ';
$select .= 'sa.scormid AS scormid, sa.attempt AS attempt ' . $selectfields . ' ';
// This part is the same for all cases - join users and scorm_scoes_track tables.
// This part is the same for all cases - join users and user tracking tables.
$from = 'FROM {user} u ';
$from .= 'LEFT JOIN {scorm_scoes_track} st ON st.userid = u.id AND st.scormid = '.$scorm->id;
$from .= 'LEFT JOIN {scorm_attempt} sa ON sa.userid = u.id AND sa.scormid = '.$scorm->id;
switch ($attemptsmode) {
case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH:
// Show only students with attempts.
$where = " WHERE u.id IN ({$allowedlistsql}) AND st.userid IS NOT NULL";
$where = " WHERE u.id IN ({$allowedlistsql}) AND sa.userid IS NOT NULL";
break;
case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
// Show only students without attempts.
$where = " WHERE u.id IN ({$allowedlistsql}) AND st.userid IS NULL";
$where = " WHERE u.id IN ({$allowedlistsql}) AND sa.userid IS NULL";
break;
case SCORM_REPORT_ATTEMPTS_ALL_STUDENTS:
// Show all students with or without attempts.
$where = " WHERE u.id IN ({$allowedlistsql}) AND (st.userid IS NOT NULL OR st.userid IS NULL)";
$where = " WHERE u.id IN ({$allowedlistsql}) AND (sa.userid IS NOT NULL OR sa.userid IS NULL)";
break;
}
$countsql = 'SELECT COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').')) AS nbresults, ';
$countsql .= 'COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'st.attempt').')) AS nbattempts, ';
$countsql = 'SELECT COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(sa.attempt, 0)').')) AS nbresults, ';
$countsql .= 'COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'sa.attempt').')) AS nbattempts, ';
$countsql .= 'COUNT(DISTINCT(u.id)) AS nbusers ';
$countsql .= $from.$where;
@ -620,23 +620,22 @@ class report extends \mod_scorm\report {
*/
function get_scorm_objectives($scormid) {
global $DB;
$objectives = array();
$params = array();
$select = "scormid = ? AND ";
$select .= $DB->sql_like("element", "?", false);
$params[] = $scormid;
$params[] = "cmi.objectives%.id";
$value = $DB->sql_compare_text('value');
$rs = $DB->get_recordset_select("scorm_scoes_track", $select, $params, 'value', "DISTINCT $value AS value, scoid");
if ($rs->valid()) {
foreach ($rs as $record) {
$objectives[$record->scoid][] = $record->value;
}
// Now naturally sort the sco arrays.
foreach ($objectives as $scoid => $sco) {
natsort($objectives[$scoid]);
}
$objectives = [];
$params = ['scormid' => $scormid, 'search' => 'cmi.objectives%.id'];
$value = $DB->sql_compare_text('v.value');
$sql = "SELECT DISTINCT $value as value, ss.id
FROM {scorm_scoes_value} v
JOIN {scorm_scoes} ss ON ss.id = v.scoid AND ss.scorm = :scormid
JOIN {scorm_element} e ON v.elementid = e.id
WHERE ".$DB->sql_like("element", ":search", false);
$rs = $DB->get_records_sql($sql, $params);
foreach ($rs as $record) {
$objectives[$record->scoid][] = $record->value;
}
// Now naturally sort the sco arrays.
foreach ($objectives as $scoid => $sco) {
natsort($objectives[$scoid]);
}
$rs->close();
return $objectives;
}

View File

@ -75,11 +75,16 @@ function get_scorm_question_count($scormid) {
global $DB;
$count = 0;
$params = array();
$select = "scormid = ? AND ";
$select .= $DB->sql_like("element", "?", false);
$sql = "SELECT DISTINCT e.id, e.element
FROM {scorm_element} e
JOIN {scorm_scoes_value} v ON e.id = v.elementid
JOIN {scorm_attempt} a ON a.id = v.attemptid
WHERE a.scormid = ? AND ". $DB->sql_like("element", "?", false) .
" ORDER BY e.element";
$params[] = $scormid;
$params[] = "cmi.interactions_%.id";
$rs = $DB->get_recordset_select("scorm_scoes_track", $select, $params, 'element');
$rs = $DB->get_recordset_sql($sql, $params);
$keywords = array("cmi.interactions_", ".id");
if ($rs->valid()) {
foreach ($rs as $record) {

View File

@ -69,8 +69,12 @@ $event->add_record_snapshot('course_modules', $cm);
$event->add_record_snapshot('scorm', $scorm);
$event->trigger();
$trackdata = $DB->get_records('scorm_scoes_track', array('userid' => $user->id, 'scormid' => $scorm->id,
'attempt' => $attempt));
$sql = "SELECT a.id, a.userid, a.scormid, v.scoid, a.attempt, v.value, v.timemodified, e.element
FROM {scorm_attempt} a
JOIN {scorm_scoes_value} v ON v.attemptid = a.id
JOIN {scorm_element} e ON e.id = v.elementid
WHERE a.userid = :userid AND a.scormid = :scormid AND a.attempt = :attempt";
$trackdata = $DB->get_records_sql($sql, ['userid' => $userid, 'scormid' => $scorm->id, 'attempt' => $attempt]);
$usertrack = scorm_format_interactions($trackdata);
$questioncount = get_scorm_question_count($scorm->id);

View File

@ -41,7 +41,7 @@ class restore_date_test extends \restore_date_testcase {
scorm_insert_track($USER->id, $scorm->id, $sco->id, 4, 'cmi.core.score.raw', 10);
// We do not want second differences to fail our test because of execution delays.
$DB->set_field('scorm_scoes_track', 'timemodified', $time);
$DB->set_field('scorm_scoes_value', 'timemodified', $time);
// Do backup and restore.
$newcourseid = $this->backup_and_restore($course);
@ -51,7 +51,11 @@ class restore_date_test extends \restore_date_testcase {
$props = ['timeopen', 'timeclose'];
$this->assertFieldsRolledForward($scorm, $newscorm, $props);
$tracks = $DB->get_records('scorm_scoes_track', ['scormid' => $newscorm->id]);
$sql = "SELECT *
FROM {scorm_scoes_value} v
JOIN {scorm_attempt} a ON a.id = v.attemptid
WHERE a.scormid = ?";
$tracks = $DB->get_records_sql($sql, [$newscorm->id]);
foreach ($tracks as $track) {
$this->assertEquals($time, $track->timemodified);
}

View File

@ -504,9 +504,12 @@ class externallib_test extends externallib_advanced_testcase {
$result = mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
$result = external_api::clean_returnvalue(mod_scorm_external::insert_scorm_tracks_returns(), $result);
$this->assertCount(0, $result['warnings']);
$trackids = $DB->get_records('scorm_scoes_track', array('userid' => $student->id, 'scoid' => $sco->id,
'scormid' => $scorm->id, 'attempt' => 1));
$sql = "SELECT v.id
FROM {scorm_scoes_value} v
JOIN {scorm_attempt} a ON a.id = v.attemptid
WHERE a.userid = :userid AND a.attempt = :attempt AND a.scormid = :scormid AND v.scoid = :scoid";
$params = ['userid' => $student->id, 'scoid' => $sco->id, 'scormid' => $scorm->id, 'attempt' => 1];
$trackids = $DB->get_records_sql($sql, $params);
// We use asort here to prevent problems with ids ordering.
$expectedkeys = array_keys($trackids);
$this->assertEquals(asort($expectedkeys), asort($result['trackids']));

View File

@ -158,8 +158,8 @@ class provider_test extends provider_testcase {
$this->setAdminUser();
$this->scorm_setup_test_scenario_data();
// Before deletion, we should have 8 entries in the scorm_scoes_track table.
$count = $DB->count_records('scorm_scoes_track');
// Before deletion, we should have 8 entries in the scorm_scoes_value table.
$count = $DB->count_records('scorm_scoes_value');
$this->assertEquals(8, $count);
// Before deletion, we should have 4 entries in the scorm_aicc_session table.
$count = $DB->count_records('scorm_aicc_session');
@ -168,8 +168,8 @@ class provider_test extends provider_testcase {
// Delete data based on the context.
provider::delete_data_for_all_users_in_context($this->context);
// After deletion, the scorm_scoes_track entries should have been deleted.
$count = $DB->count_records('scorm_scoes_track');
// After deletion, the scorm_scoes_value entries should have been deleted.
$count = $DB->count_records('scorm_scoes_value');
$this->assertEquals(0, $count);
// After deletion, the scorm_aicc_session entries should have been deleted.
$count = $DB->count_records('scorm_aicc_session');
@ -186,8 +186,8 @@ class provider_test extends provider_testcase {
$this->setAdminUser();
$this->scorm_setup_test_scenario_data();
// Before deletion, we should have 8 entries in the scorm_scoes_track table.
$count = $DB->count_records('scorm_scoes_track');
// Before deletion, we should have 8 entries in the scorm_scoes_value table.
$count = $DB->count_records('scorm_scoes_value');
$this->assertEquals(8, $count);
// Before deletion, we should have 4 entries in the scorm_aicc_session table.
$count = $DB->count_records('scorm_aicc_session');
@ -196,10 +196,10 @@ class provider_test extends provider_testcase {
$approvedcontextlist = new approved_contextlist($this->student1, 'scorm', [$this->context->id]);
provider::delete_data_for_user($approvedcontextlist);
// After deletion, the scorm_scoes_track entries for the first student should have been deleted.
$count = $DB->count_records('scorm_scoes_track', ['userid' => $this->student1->id]);
// After deletion, the scorm_attempt entries for the first student should have been deleted.
$count = $DB->count_records('scorm_attempt', ['userid' => $this->student1->id]);
$this->assertEquals(0, $count);
$count = $DB->count_records('scorm_scoes_track');
$count = $DB->count_records('scorm_scoes_value');
$this->assertEquals(4, $count);
// After deletion, the scorm_aicc_session entries for the first student should have been deleted.
$count = $DB->count_records('scorm_aicc_session', ['userid' => $this->student1->id]);
@ -214,7 +214,7 @@ class provider_test extends provider_testcase {
// Delete scoes_track for student0 (nothing has to be removed).
$approvedcontextlist = new approved_contextlist($this->student0, 'scorm', [$this->context->id]);
provider::delete_data_for_user($approvedcontextlist);
$count = $DB->count_records('scorm_scoes_track');
$count = $DB->count_records('scorm_scoes_value');
$this->assertEquals(4, $count);
$count = $DB->count_records('scorm_aicc_session');
$this->assertEquals(2, $count);
@ -231,8 +231,8 @@ class provider_test extends provider_testcase {
$this->setAdminUser();
$this->scorm_setup_test_scenario_data();
// Before deletion, we should have 8 entries in the scorm_scoes_track table.
$count = $DB->count_records('scorm_scoes_track');
// Before deletion, we should have 8 entries in the scorm_scoes_value table.
$count = $DB->count_records('scorm_scoes_value');
$this->assertEquals(8, $count);
// Before deletion, we should have 4 entries in the scorm_aicc_session table.
$count = $DB->count_records('scorm_aicc_session');
@ -243,10 +243,10 @@ class provider_test extends provider_testcase {
$approvedlist = new approved_userlist($this->context, $component, $approveduserids);
provider::delete_data_for_users($approvedlist);
// After deletion, the scorm_scoes_track entries for the first student should have been deleted.
$count = $DB->count_records('scorm_scoes_track', ['userid' => $this->student1->id]);
// After deletion, the scorm_attempt entries for the first student should have been deleted.
$count = $DB->count_records('scorm_attempt', ['userid' => $this->student1->id]);
$this->assertEquals(0, $count);
$count = $DB->count_records('scorm_scoes_track');
$count = $DB->count_records('scorm_scoes_value');
$this->assertEquals(4, $count);
// After deletion, the scorm_aicc_session entries for the first student should have been deleted.
@ -264,7 +264,7 @@ class provider_test extends provider_testcase {
$approvedlist = new approved_userlist($this->context, $component, $approveduserids);
provider::delete_data_for_users($approvedlist);
$count = $DB->count_records('scorm_scoes_track');
$count = $DB->count_records('scorm_scoes_value');
$this->assertEquals(4, $count);
$count = $DB->count_records('scorm_aicc_session');
$this->assertEquals(2, $count);

View File

@ -24,6 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2023042400; // The current module version (Date: YYYYMMDDXX).
$plugin->version = 2023042403; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2023041800; // Requires this Moodle version.
$plugin->component = 'mod_scorm'; // Full name of the plugin (used for diagnostics).
$plugin->component = 'mod_scorm'; // Full name of the plugin (used for diagnostics).

View File

@ -152,7 +152,7 @@ if (!empty($action) && confirm_sesskey() && has_capability('mod/scorm:deleteownr
exit;
} else if ($action == 'deleteconfirm') {
// Delete this users attempts.
$DB->delete_records('scorm_scoes_track', array('userid' => $USER->id, 'scormid' => $scorm->id));
scorm_delete_tracks($scorm->id, null, $USER->id);
scorm_update_grades($scorm, $USER->id, true);
echo $OUTPUT->notification(get_string('scormresponsedeleted', 'scorm'), 'notifysuccess');
}