first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+462
View File
@@ -0,0 +1,462 @@
<?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/>.
// Prevent Caching Headers.
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Cache-Control: no-cache");
header("Pragma: no-cache");
require_once('../../config.php');
require_once($CFG->dirroot.'/mod/scorm/lib.php');
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
require_once($CFG->dirroot.'/mod/scorm/datamodels/aicclib.php');
foreach ($_POST as $key => $value) {
$tempkey = strtolower($key);
$_POST[$tempkey] = $value;
}
$command = required_param('command', PARAM_ALPHA);
$sessionid = required_param('session_id', PARAM_ALPHANUM);
$aiccdata = optional_param('aicc_data', '', PARAM_RAW);
$cfgscorm = get_config('scorm');
$url = new moodle_url('/mod/scorm/aicc.php', array('command' => $command, 'session_id' => $sessionid));
if ($aiccdata !== 0) {
$url->param('aicc_data', $aiccdata);
}
$PAGE->set_url($url);
if (empty($cfgscorm->allowaicchacp)) {
require_login();
if (!confirm_sesskey($sessionid)) {
throw new \moodle_exception('invalidsesskey');
}
$aiccuser = $USER;
$scormsession = $SESSION->scorm;
} else {
$scormsession = scorm_aicc_confirm_hacp_session($sessionid);
if (empty($scormsession)) {
throw new \moodle_exception('invalidhacpsession', 'scorm');
}
$aiccuser = $DB->get_record('user', array('id' => $scormsession->userid), 'id,username,lastname,firstname', MUST_EXIST);
}
if (!empty($command)) {
$command = strtolower($command);
if (isset($scormsession->scoid)) {
$scoid = $scormsession->scoid;
} else {
throw new \moodle_exception('cannotcallscript');
}
$mode = 'normal';
if (isset($scormsession->scormmode)) {
$mode = $scormsession->scormmode;
}
$status = 'Not Initialized';
if (isset($scormsession->scormstatus)) {
$status = $scormsession->scormstatus;
}
if (isset($scormsession->attempt)) {
$attempt = $scormsession->attempt;
} 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');
}
} else {
throw new \moodle_exception('cannotcallscript');
}
$aiccrequest = "MOODLE scoid: $scoid"
. "\r\nMOODLE mode: $mode"
. "\r\nMOODLE status: $status"
. "\r\nMOODLE attempt: $attempt"
. "\r\nAICC sessionid: $sessionid"
. "\r\nAICC command: $command"
. "\r\nAICC aiccdata:\r\n$aiccdata";
scorm_debug_log_write("aicc", "HACP Request:\r\n$aiccrequest", $scoid);
ob_start();
if ($scorm = $DB->get_record('scorm', array('id' => $sco->scorm))) {
switch ($command) {
case 'getparam':
if ($status == 'Not Initialized') {
$scormsession->scormstatus = 'Running';
$status = 'Running';
}
if ($status != 'Running') {
echo "error=101\r\nerror_text=Terminated\r\n";
} else {
if ($usertrack = scorm_get_tracks($scoid, $aiccuser->id, $attempt)) {
$userdata = $usertrack;
} else {
$userdata->status = '';
$userdata->score_raw = '';
}
$aiccuserid = get_config('scorm', 'aiccuserid');
if (!empty($aiccuserid)) {
$userdata->student_id = $aiccuser->id;
} else {
$userdata->student_id = $aiccuser->username;
}
$userdata->student_name = $aiccuser->lastname .', '. $aiccuser->firstname;
$userdata->mode = $mode;
if ($userdata->mode == 'normal') {
$userdata->credit = 'credit';
} else {
$userdata->credit = 'no-credit';
}
if ($sco = scorm_get_sco($scoid)) {
$userdata->course_id = $sco->identifier;
$userdata->datafromlms = isset($sco->datafromlms) ? $sco->datafromlms : '';
$userdata->mastery_score = isset($sco->mastery_score) && is_numeric($sco->mastery_score) ?
trim($sco->mastery_score) : '';
$userdata->max_time_allowed = isset($sco->max_time_allowed) ? $sco->max_time_allowed : '';
$userdata->time_limit_action = isset($sco->time_limit_action) ? $sco->time_limit_action : '';
echo "error=0\r\nerror_text=Successful\r\naicc_data=";
echo "[Core]\r\n";
echo 'Student_ID='.$userdata->student_id."\r\n";
echo 'Student_Name='.$userdata->student_name."\r\n";
if (isset($userdata->{'cmi.core.lesson_location'})) {
echo 'Lesson_Location='.$userdata->{'cmi.core.lesson_location'}."\r\n";
} else {
echo 'Lesson_Location='."\r\n";
}
echo 'Credit='.$userdata->credit."\r\n";
if (isset($userdata->status)) {
if ($userdata->status == '') {
$userdata->entry = ', ab-initio';
} else {
if (isset($userdata->{'cmi.core.exit'}) && ($userdata->{'cmi.core.exit'} == 'suspend')) {
$userdata->entry = ', resume';
} else {
$userdata->entry = '';
}
}
}
if (isset($userdata->{'cmi.core.lesson_status'})) {
echo 'Lesson_Status='.$userdata->{'cmi.core.lesson_status'}.$userdata->entry."\r\n";
$scormsession->scorm_lessonstatus = $userdata->{'cmi.core.lesson_status'};
} else {
echo 'Lesson_Status=not attempted'.$userdata->entry."\r\n";
$scormsession->scorm_lessonstatus = 'not attempted';
}
if (isset($userdata->{'cmi.core.score.raw'})) {
$max = '';
$min = '';
if (isset($userdata->{'cmi.core.score.max'}) && !empty($userdata->{'cmi.core.score.max'})) {
$max = ', '.$userdata->{'cmi.core.score.max'};
if (isset($userdata->{'cmi.core.score.min'}) && !empty($userdata->{'cmi.core.score.min'})) {
$min = ', '.$userdata->{'cmi.core.score.min'};
}
}
echo 'Score='.$userdata->{'cmi.core.score.raw'}.$max.$min."\r\n";
} else {
echo 'Score='."\r\n";
}
if (isset($userdata->{'cmi.core.total_time'})) {
echo 'Time='.$userdata->{'cmi.core.total_time'}."\r\n";
} else {
echo 'Time='.'00:00:00'."\r\n";
}
echo 'Lesson_Mode='.$userdata->mode."\r\n";
if (isset($userdata->{'cmi.suspend_data'})) {
$decoded = rawurldecode($userdata->{'cmi.suspend_data'});
$header = "[Core_Lesson]\r\n";
if (stripos($decoded, $header) === 0) {
// The header may have been stored with the content. If it was, trim it off the front.
$decoded = core_text::substr($decoded, core_text::strlen($header));
}
echo "[Core_Lesson]\r\n".$decoded."\r\n";
} else {
echo "[Core_Lesson]\r\n";
}
echo "[Core_Vendor]\r\n".$userdata->datafromlms."\r\n";
echo "[Evaluation]\r\nCourse_ID = {".$userdata->course_id."}\r\n";
echo "[Student_Data]\r\n";
echo 'Mastery_Score='.$userdata->mastery_score."\r\n";
echo 'Max_Time_Allowed='.$userdata->max_time_allowed."\r\n";
echo 'Time_Limit_Action='.$userdata->time_limit_action."\r\n";
} else {
throw new \moodle_exception('cannotfindsco', 'scorm');
}
}
break;
case 'putparam':
if ($status == 'Running') {
if (! $cm = get_coursemodule_from_instance("scorm", $scorm->id, $scorm->course)) {
echo "error=1\r\nerror_text=Unknown\r\n"; // No one must see this error message if not hacked.
}
$savetrack = has_capability('mod/scorm:savetrack', context_module::instance($cm->id), $aiccuser->id);
if (!empty($aiccdata) && $savetrack) {
$initlessonstatus = 'not attempted';
$lessonstatus = 'not attempted';
if (isset($scormsession->scorm_lessonstatus)) {
$initlessonstatus = $scormsession->scorm_lessonstatus;
}
$score = '';
$datamodel['lesson_location'] = 'cmi.core.lesson_location';
$datamodel['lesson_status'] = 'cmi.core.lesson_status';
$datamodel['score'] = 'cmi.core.score.raw';
$datamodel['time'] = 'cmi.core.session_time';
$datamodel['[core_lesson]'] = 'cmi.suspend_data';
$datamodel['[comments]'] = 'cmi.comments';
$datarows = explode("\r\n", $aiccdata);
reset($datarows);
$multirowvalue = '';
$multirowelement = '';
foreach ($datarows as $did => $datarow) {
$equal = strpos($datarow, '=');
if ($equal === false || !empty($multirowelement)) {
if (empty($multirowelement)) {
if (isset($datamodel[strtolower(trim($datarow))])) {
$multirowelement = $datamodel[strtolower(trim($datarow))];
} else {
// An element was passed by the external AICC package is not one we care about.
continue;
}
} else {
$multirowvalue .= $datarow . "\r\n";
}
if (isset($datarows[$did + 1]) && substr($datarows[$did + 1], 0, 1) != '[') {
// This is a multiline row, we haven't found the end yet.
continue;
}
$value = rawurlencode($multirowvalue);
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id, $attemptobject,
$multirowelement, $value);
$multirowvalue = $multirowelement = '';
} else {
$element = strtolower(trim(substr($datarow, 0, $equal)));
$value = trim(substr($datarow, $equal + 1));
if (isset($datamodel[$element])) {
$element = $datamodel[$element];
switch ($element) {
case 'cmi.core.lesson_location':
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attemptobject, $element, $value);
break;
case 'cmi.core.lesson_status':
$statuses = array(
'passed' => 'passed',
'completed' => 'completed',
'failed' => 'failed',
'incomplete' => 'incomplete',
'browsed' => 'browsed',
'not attempted' => 'not attempted',
'p' => 'passed',
'c' => 'completed',
'f' => 'failed',
'i' => 'incomplete',
'b' => 'browsed',
'n' => 'not attempted'
);
$exites = array(
'logout' => 'logout',
'time-out' => 'time-out',
'suspend' => 'suspend',
'l' => 'logout',
't' => 'time-out',
's' => 'suspend',
);
$values = explode(',', $value);
$value = '';
if (count($values) > 1) {
$value = trim(strtolower($values[1]));
$value = $value[0];
if (isset($exites[$value])) {
$value = $exites[$value];
}
}
if (empty($value) || isset($exites[$value])) {
$subelement = 'cmi.core.exit';
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$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,
$attemptobject, $element, $value);
}
$lessonstatus = $value;
break;
case 'cmi.core.score.raw':
$values = explode(',', $value);
if ((count($values) > 1) && ($values[1] >= $values[0]) && is_numeric($values[1])) {
$subelement = 'cmi.core.score.max';
$value = trim($values[1]);
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$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,
$attemptobject, $subelement, $value);
}
}
$value = '';
if (is_numeric($values[0])) {
$value = trim($values[0]);
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attemptobject, $element, $value);
}
$score = $value;
break;
case 'cmi.core.session_time':
$scormsession->sessiontime = $value;
break;
}
}
}
}
if (($mode == 'browse') && ($initlessonstatus == 'not attempted')) {
$lessonstatus = 'browsed';
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attemptobject, 'cmi.core.lesson_status', 'browsed');
}
if ($mode == 'normal') {
if ($sco = scorm_get_sco($scoid)) {
if (isset($sco->mastery_score) && is_numeric($sco->mastery_score)) {
if ($score != '') { // Score is correctly initialized w/ an empty string, see above.
if ($score >= trim($sco->mastery_score)) {
$lessonstatus = 'passed';
} else {
$lessonstatus = 'failed';
}
}
}
$id = scorm_insert_track($aiccuser->id, $scorm->id, $sco->id,
$attemptobject, 'cmi.core.lesson_status', $lessonstatus);
}
}
}
echo "error=0\r\nerror_text=Successful\r\n";
} else if ($status == 'Terminated') {
echo "error=1\r\nerror_text=Terminated\r\n";
} else {
echo "error=1\r\nerror_text=Not Initialized\r\n";
}
break;
case 'putcomments':
if ($status == 'Running') {
echo "error=0\r\nerror_text=Successful\r\n";
} else if ($status == 'Terminated') {
echo "error=1\r\nerror_text=Terminated\r\n";
} else {
echo "error=1\r\nerror_text=Not Initialized\r\n";
}
break;
case 'putinteractions':
if ($status == 'Running') {
echo "error=0\r\nerror_text=Successful\r\n";
} else if ($status == 'Terminated') {
echo "error=1\r\nerror_text=Terminated\r\n";
} else {
echo "error=1\r\nerror_text=Not Initialized\r\n";
}
break;
case 'putobjectives':
if ($status == 'Running') {
echo "error=0\r\nerror_text=Successful\r\n";
} else if ($status == 'Terminated') {
echo "error=1\r\nerror_text=Terminated\r\n";
} else {
echo "error=1\r\nerror_text=Not Initialized\r\n";
}
break;
case 'putpath':
if ($status == 'Running') {
echo "error=0\r\nerror_text=Successful\r\n";
} else if ($status == 'Terminated') {
echo "error=1\r\nerror_text=Terminated\r\n";
} else {
echo "error=1\r\nerror_text=Not Initialized\r\n";
}
break;
case 'putperformance':
if ($status == 'Running') {
echo "error=0\r\nerror_text=Successful\r\n";
} else if ($status == 'Terminated') {
echo "error=1\r\nerror_text=Terminated\r\n";
} else {
echo "error=1\r\nerror_text=Not Initialized\r\n";
}
break;
case 'exitau':
if ($status == 'Running') {
if (isset($scormsession->sessiontime) && ($scormsession->sessiontime != '')) {
$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);
$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->scoid = $sco->id;
$track->elementid = scorm_get_elementid('cmi.core.total_time');
$track->value = $scormsession->sessiontime;
$atobject = scorm_get_attempt($aiccuser->id, $scormsession->scormid, $attempt);
$track->attemptid = $atobject->id;
$track->timemodified = time();
$id = $DB->insert_record('scorm_scoes_value', $track);
}
scorm_update_grades($scorm, $aiccuser->id);
}
$scormsession->scormstatus = 'Terminated';
$scormsession->session_time = '';
echo "error=0\r\nerror_text=Successful\r\n";
} else if ($status == 'Terminated') {
echo "error=1\r\nerror_text=Terminated\r\n";
} else {
echo "error=1\r\nerror_text=Not Initialized\r\n";
}
break;
default:
echo "error=1\r\nerror_text=Invalid Command\r\n";
break;
}
}
} else {
if (empty($command)) {
echo "error=1\r\nerror_text=Invalid Command\r\n";
} else {
echo "error=3\r\nerror_text=Invalid Session ID\r\n";
}
}
if (empty($cfgscorm->allowaicchacp)) {
$SESSION->scorm = $scormsession;
} else {
$scormsession->timemodified = time();
$DB->update_record('scorm_aicc_session', $scormsession);
}
$aiccresponse = ob_get_contents();
scorm_debug_log_write("aicc", "HACP Response:\r\n$aiccresponse", $scoid);
ob_end_flush();
+178
View File
@@ -0,0 +1,178 @@
<?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/>.
/**
* Provides support for the conversion of moodle1 backup to the moodle2 format
*
* @package mod_scorm
* @copyright 2011 Aparup Banerjee <nebgor@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Scorm conversion handler
*/
class moodle1_mod_scorm_handler extends moodle1_mod_handler {
/** @var moodle1_file_manager */
protected $fileman = null;
/** @var int cmid */
protected $moduleid = null;
/**
* Declare the paths in moodle.xml we are able to convert
*
* The method returns list of {@link convert_path} instances.
* For each path returned, the corresponding conversion method must be
* defined.
*
* Note that the path /MOODLE_BACKUP/COURSE/MODULES/MOD/SCORM does not
* actually exist in the file. The last element with the module name was
* appended by the moodle1_converter class.
*
* @return array of {@link convert_path} instances
*/
public function get_paths() {
return array(
new convert_path('scorm', '/MOODLE_BACKUP/COURSE/MODULES/MOD/SCORM',
array(
'newfields' => array(
'whatgrade' => 0,
'scormtype' => 'local',
'sha1hash' => null,
'revision' => '0',
'forcecompleted' => 0,
'forcenewattempt' => 0,
'lastattemptlock' => 0,
'masteryoverride' => 1,
'displayattemptstatus' => 1,
'displaycoursestructure' => 0,
'timeopen' => '0',
'timeclose' => '0',
'introformat' => '0',
'completionstatusallscos' => 0,
),
'renamefields' => array(
'summary' => 'intro'
)
)
),
new convert_path('scorm_sco', '/MOODLE_BACKUP/COURSE/MODULES/MOD/SCORM/SCOES/SCO')
);
}
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/SCORM
* data available
*/
public function process_scorm($data) {
global $CFG;
// get the course module id and context id
$instanceid = $data['id'];
$currentcminfo = $this->get_cminfo($instanceid);
$this->moduleid = $currentcminfo['id'];
$contextid = $this->converter->get_contextid(CONTEXT_MODULE, $this->moduleid);
// conditionally migrate to html format in intro
if ($CFG->texteditors !== 'textarea') {
$data['intro'] = text_to_html($data['intro'], false, false, true);
$data['introformat'] = FORMAT_HTML;
}
// get a fresh new file manager for this instance
$this->fileman = $this->converter->get_file_manager($contextid, 'mod_scorm');
// convert course files embedded into the intro
$this->fileman->filearea = 'intro';
$this->fileman->itemid = 0;
$data['intro'] = moodle1_converter::migrate_referenced_files($data['intro'], $this->fileman);
// check 1.9 version where backup was created
$backupinfo = $this->converter->get_stash('backup_info');
if ($backupinfo['moodle_version'] < 2007110503) {
// as we have no module version data, assume $currmodule->version <= $module->version
// - fix data as the source 1.9 build hadn't yet at time of backing up.
$data['grademethod'] = $data['grademethod']%10;
}
// update scormtype (logic is consistent as done in scorm/db/upgrade.php)
$ismanifest = preg_match('/imsmanifest\.xml$/', $data['reference']);
$iszippif = preg_match('/.(zip|pif)$/', $data['reference']);
$isurl = preg_match('/^((http|https):\/\/|www\.)/', $data['reference']);
if ($isurl) {
if ($ismanifest) {
$data['scormtype'] = 'external';
} else if ($iszippif) {
$data['scormtype'] = 'localtype';
}
}
// migrate scorm package file
$this->fileman->filearea = 'package';
$this->fileman->itemid = 0;
$this->fileman->migrate_file('course_files/'.$data['reference']);
// start writing scorm.xml
$this->open_xml_writer("activities/scorm_{$this->moduleid}/scorm.xml");
$this->xmlwriter->begin_tag('activity', array('id' => $instanceid, 'moduleid' => $this->moduleid,
'modulename' => 'scorm', 'contextid' => $contextid));
$this->xmlwriter->begin_tag('scorm', array('id' => $instanceid));
foreach ($data as $field => $value) {
if ($field <> 'id') {
$this->xmlwriter->full_tag($field, $value);
}
}
$this->xmlwriter->begin_tag('scoes');
return $data;
}
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/SCORM/SCOES/SCO
* data available
*/
public function process_scorm_sco($data) {
$this->write_xml('sco', $data, array('/sco/id'));
}
/**
* This is executed when we reach the closing </MOD> tag of our 'scorm' path
*/
public function on_scorm_end() {
// close scorm.xml
$this->xmlwriter->end_tag('scoes');
$this->xmlwriter->end_tag('scorm');
$this->xmlwriter->end_tag('activity');
$this->close_xml_writer();
// write inforef.xml
$this->open_xml_writer("activities/scorm_{$this->moduleid}/inforef.xml");
$this->xmlwriter->begin_tag('inforef');
$this->xmlwriter->begin_tag('fileref');
foreach ($this->fileman->get_fileids() as $fileid) {
$this->write_xml('file', array('id' => $fileid));
}
$this->xmlwriter->end_tag('fileref');
$this->xmlwriter->end_tag('inforef');
$this->close_xml_writer();
}
}
@@ -0,0 +1,69 @@
<?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/>.
/**
* Defines backup_scorm_activity_task class
*
* @package mod_scorm
* @category backup
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/scorm/backup/moodle2/backup_scorm_stepslib.php');
/**
* Provides the steps to perform one complete backup of the SCORM instance
*/
class backup_scorm_activity_task extends backup_activity_task {
/**
* No specific settings for this activity
*/
protected function define_my_settings() {
}
/**
* Defines a backup step to store the instance data in the scorm.xml file
*/
protected function define_my_steps() {
$this->add_step(new backup_scorm_activity_structure_step('scorm_structure', 'scorm.xml'));
}
/**
* Encodes URLs to the index.php and view.php scripts
*
* @param string $content some HTML text that eventually contains URLs to the activity instance scripts
* @return string the content with the URLs encoded
*/
public static function encode_content_links($content) {
global $CFG;
$base = preg_quote($CFG->wwwroot, "/");
// Link to the list of scorms
$search="/(".$base."\/mod\/scorm\/index.php\?id\=)([0-9]+)/";
$content= preg_replace($search, '$@SCORMINDEX*$2@$', $content);
// Link to scorm view by moduleid
$search="/(".$base."\/mod\/scorm\/view.php\?id\=)([0-9]+)/";
$content= preg_replace($search, '$@SCORMVIEWBYID*$2@$', $content);
return $content;
}
}
@@ -0,0 +1,163 @@
<?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/>.
/**
* @package mod_scorm
* @subpackage backup-moodle2
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Define all the backup steps that will be used by the backup_scorm_activity_task
*/
/**
* Define the complete scorm structure for backup, with file and id annotations
*/
class backup_scorm_activity_structure_step extends backup_activity_structure_step {
protected function define_structure() {
// To know if we are including userinfo
$userinfo = $this->get_setting_value('userinfo');
// Define each element separated
$scorm = new backup_nested_element('scorm', array('id'), array(
'name', 'scormtype', 'reference', 'intro',
'introformat', 'version', 'maxgrade', 'grademethod',
'whatgrade', 'maxattempt', 'forcecompleted', 'forcenewattempt',
'lastattemptlock', 'masteryoverride', 'displayattemptstatus', 'displaycoursestructure', 'updatefreq',
'sha1hash', 'md5hash', 'revision', 'launch',
'skipview', 'hidebrowse', 'hidetoc', 'nav', 'navpositionleft', 'navpositiontop',
'auto', 'popup', 'options', 'width',
'height', 'timeopen', 'timeclose', 'timemodified',
'completionstatusrequired', 'completionscorerequired',
'completionstatusallscos',
'autocommit'));
$scoes = new backup_nested_element('scoes');
$sco = new backup_nested_element('sco', array('id'), array(
'manifest', 'organization', 'parent', 'identifier',
'launch', 'scormtype', 'title', 'sortorder'));
$scodatas = new backup_nested_element('sco_datas');
$scodata = new backup_nested_element('sco_data', array('id'), array(
'name', 'value'));
$seqruleconds = new backup_nested_element('seq_ruleconds');
$seqrulecond = new backup_nested_element('seq_rulecond', array('id'), array(
'conditioncombination', 'ruletype', 'action'));
$seqrulecondsdatas = new backup_nested_element('seq_rulecond_datas');
$seqrulecondsdata = new backup_nested_element('seq_rulecond_data', array('id'), array(
'refrencedobjective', 'measurethreshold', 'operator', 'cond'));
$seqrolluprules = new backup_nested_element('seq_rolluprules');
$seqrolluprule = new backup_nested_element('seq_rolluprule', array('id'), array(
'childactivityset', 'minimumcount', 'minimumpercent', 'conditioncombination',
'action'));
$seqrollupruleconds = new backup_nested_element('seq_rollupruleconds');
$seqrolluprulecond = new backup_nested_element('seq_rolluprulecond', array('id'), array(
'cond', 'operator'));
$seqobjectives = new backup_nested_element('seq_objectives');
$seqobjective = new backup_nested_element('seq_objective', array('id'), array(
'primaryobj', 'objectiveid', 'satisfiedbymeasure', 'minnormalizedmeasure'));
$seqmapinfos = new backup_nested_element('seq_mapinfos');
$seqmapinfo = new backup_nested_element('seq_mapinfo', array('id'), array(
'targetobjectiveid', 'readsatisfiedstatus', 'readnormalizedmeasure', 'writesatisfiedstatus',
'writenormalizedmeasure'));
$scotracks = new backup_nested_element('sco_tracks');
$scotrack = new backup_nested_element('sco_track', array('id'), array(
'userid', 'attempt', 'element', 'value',
'timemodified'));
// Build the tree
$scorm->add_child($scoes);
$scoes->add_child($sco);
$sco->add_child($scodatas);
$scodatas->add_child($scodata);
$sco->add_child($seqruleconds);
$seqruleconds->add_child($seqrulecond);
$seqrulecond->add_child($seqrulecondsdatas);
$seqrulecondsdatas->add_child($seqrulecondsdata);
$sco->add_child($seqrolluprules);
$seqrolluprules->add_child($seqrolluprule);
$seqrolluprule->add_child($seqrollupruleconds);
$seqrollupruleconds->add_child($seqrolluprulecond);
$sco->add_child($seqobjectives);
$seqobjectives->add_child($seqobjective);
$seqobjective->add_child($seqmapinfos);
$seqmapinfos->add_child($seqmapinfo);
$sco->add_child($scotracks);
$scotracks->add_child($scotrack);
// Define sources
$scorm->set_source_table('scorm', array('id' => backup::VAR_ACTIVITYID));
// Order is important for several SCORM calls (especially scorm_scoes) in the following calls to set_source_table
$sco->set_source_table('scorm_scoes', array('scorm' => backup::VAR_PARENTID), 'sortorder, id');
$scodata->set_source_table('scorm_scoes_data', array('scoid' => backup::VAR_PARENTID), 'id ASC');
$seqrulecond->set_source_table('scorm_seq_ruleconds', array('scoid' => backup::VAR_PARENTID), 'id ASC');
$seqrulecondsdata->set_source_table('scorm_seq_rulecond', array('ruleconditionsid' => backup::VAR_PARENTID), 'id ASC');
$seqrolluprule->set_source_table('scorm_seq_rolluprule', array('scoid' => backup::VAR_PARENTID), 'id ASC');
$seqrolluprulecond->set_source_table('scorm_seq_rolluprulecond', array('rollupruleid' => backup::VAR_PARENTID), 'id ASC');
$seqobjective->set_source_table('scorm_seq_objective', array('scoid' => backup::VAR_PARENTID), 'id ASC');
$seqmapinfo->set_source_table('scorm_seq_mapinfo', array('objectiveid' => backup::VAR_PARENTID), 'id ASC');
// All the rest of elements only happen if we are including user info
if ($userinfo) {
$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
$scotrack->annotate_ids('user', 'userid');
// Define file annotations
$scorm->annotate_files('mod_scorm', 'intro', null); // This file area hasn't itemid
$scorm->annotate_files('mod_scorm', 'content', null); // This file area hasn't itemid
$scorm->annotate_files('mod_scorm', 'package', null); // This file area hasn't itemid
// Return the root element (scorm), wrapped into standard activity structure
return $this->prepare_activity_structure($scorm);
}
}
@@ -0,0 +1,112 @@
<?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/>.
/**
* @package mod_scorm
* @subpackage backup-moodle2
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/scorm/backup/moodle2/restore_scorm_stepslib.php'); // Because it exists (must)
/**
* scorm restore task that provides all the settings and steps to perform one
* complete restore of the activity
*/
class restore_scorm_activity_task extends restore_activity_task {
/**
* Define (add) particular settings this activity can have
*/
protected function define_my_settings() {
// No particular settings for this activity
}
/**
* Define (add) particular steps this activity can have
*/
protected function define_my_steps() {
// scorm only has one structure step
$this->add_step(new restore_scorm_activity_structure_step('scorm_structure', 'scorm.xml'));
}
/**
* Define the contents in the activity that must be
* processed by the link decoder
*/
public static function define_decode_contents() {
$contents = array();
$contents[] = new restore_decode_content('scorm', array('intro'), 'scorm');
return $contents;
}
/**
* Define the decoding rules for links belonging
* to the activity to be executed by the link decoder
*/
public static function define_decode_rules() {
$rules = array();
$rules[] = new restore_decode_rule('SCORMVIEWBYID', '/mod/scorm/view.php?id=$1', 'course_module');
$rules[] = new restore_decode_rule('SCORMINDEX', '/mod/scorm/index.php?id=$1', 'course');
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* scorm logs. It must return one array
* of {@link restore_log_rule} objects
*/
public static function define_restore_log_rules() {
$rules = array();
$rules[] = new restore_log_rule('scorm', 'add', 'view.php?id={course_module}', '{scorm}');
$rules[] = new restore_log_rule('scorm', 'update', 'view.php?id={course_module}', '{scorm}');
$rules[] = new restore_log_rule('scorm', 'view', 'player.php?cm={course_module}&scoid={scorm_sco}', '{scorm}');
$rules[] = new restore_log_rule('scorm', 'pre-view', 'view.php?id={course_module}', '{scorm}');
$rules[] = new restore_log_rule('scorm', 'report', 'report.php?id={course_module}', '{scorm}');
$rules[] = new restore_log_rule('scorm', 'launch', 'view.php?id={course_module}', '[result]');
$rules[] = new restore_log_rule('scorm', 'delete attempts', 'report.php?id={course_module}', '[oldattempts]');
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* course logs. It must return one array
* of {@link restore_log_rule} objects
*
* Note this rules are applied when restoring course logs
* by the restore final task, but are defined here at
* activity level. All them are rules not linked to any module instance (cmid = 0)
*/
public static function define_restore_log_rules_for_course() {
$rules = array();
$rules[] = new restore_log_rule('scorm', 'view all', 'index.php?id={course}', null);
return $rules;
}
}
@@ -0,0 +1,227 @@
<?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/>.
/**
* @package mod_scorm
* @subpackage backup-moodle2
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Define all the restore steps that will be used by the restore_scorm_activity_task
*/
/**
* Structure step to restore one scorm activity
*/
class restore_scorm_activity_structure_step extends restore_activity_structure_step {
protected function define_structure() {
$paths = array();
$userinfo = $this->get_setting_value('userinfo');
$paths[] = new restore_path_element('scorm', '/activity/scorm');
$paths[] = new restore_path_element('scorm_sco', '/activity/scorm/scoes/sco');
$paths[] = new restore_path_element('scorm_sco_data', '/activity/scorm/scoes/sco/sco_datas/sco_data');
$paths[] = new restore_path_element('scorm_seq_objective', '/activity/scorm/scoes/sco/seq_objectives/seq_objective');
$paths[] = new restore_path_element('scorm_seq_rolluprule', '/activity/scorm/scoes/sco/seq_rolluprules/seq_rolluprule');
$paths[] = new restore_path_element('scorm_seq_rolluprulecond', '/activity/scorm/scoes/sco/seq_rollupruleconds/seq_rolluprulecond');
$paths[] = new restore_path_element('scorm_seq_rulecond', '/activity/scorm/scoes/sco/seq_ruleconds/seq_rulecond');
$paths[] = new restore_path_element('scorm_seq_rulecond_data', '/activity/scorm/scoes/sco/seq_rulecond_datas/seq_rulecond_data');
$paths[] = new restore_path_element('scorm_seq_mapinfo', '/activity/scorm/scoes/sco/seq_objectives/seq_objective/seq_mapinfos/seq_mapinfo');
if ($userinfo) {
$paths[] = new restore_path_element('scorm_sco_track', '/activity/scorm/scoes/sco/sco_tracks/sco_track');
}
// Return the paths wrapped into standard activity structure
return $this->prepare_activity_structure($paths);
}
protected function process_scorm($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->course = $this->get_courseid();
// Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
// See MDL-9367.
$data->timeopen = $this->apply_date_offset($data->timeopen);
$data->timeclose = $this->apply_date_offset($data->timeclose);
if (!isset($data->completionstatusallscos)) {
$data->completionstatusallscos = false;
}
// insert the scorm record
$newitemid = $DB->insert_record('scorm', $data);
// immediately after inserting "activity" record, call this
$this->apply_activity_instance($newitemid);
}
protected function process_scorm_sco($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->scorm = $this->get_new_parentid('scorm');
$newitemid = $DB->insert_record('scorm_scoes', $data);
$this->set_mapping('scorm_sco', $oldid, $newitemid);
}
protected function process_scorm_sco_data($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->scoid = $this->get_new_parentid('scorm_sco');
$newitemid = $DB->insert_record('scorm_scoes_data', $data);
// No need to save this mapping as far as nothing depend on it
// (child paths, file areas nor links decoder)
}
protected function process_scorm_seq_objective($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->scoid = $this->get_new_parentid('scorm_sco');
$newitemid = $DB->insert_record('scorm_seq_objective', $data);
$this->set_mapping('scorm_seq_objective', $oldid, $newitemid);
}
protected function process_scorm_seq_rolluprule($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->scoid = $this->get_new_parentid('scorm_sco');
$newitemid = $DB->insert_record('scorm_seq_rolluprule', $data);
$this->set_mapping('scorm_seq_rolluprule', $oldid, $newitemid);
}
protected function process_scorm_seq_rolluprulecond($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->scoid = $this->get_new_parentid('scorm_sco');
$data->ruleconditions = $this->get_new_parentid('scorm_seq_rolluprule');
$newitemid = $DB->insert_record('scorm_seq_rolluprulecond', $data);
// No need to save this mapping as far as nothing depend on it
// (child paths, file areas nor links decoder)
}
protected function process_scorm_seq_rulecond($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->scoid = $this->get_new_parentid('scorm_sco');
$newitemid = $DB->insert_record('scorm_seq_ruleconds', $data);
$this->set_mapping('scorm_seq_ruleconds', $oldid, $newitemid);
}
protected function process_scorm_seq_rulecond_data($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->scoid = $this->get_new_parentid('scorm_sco');
$data->ruleconditions = $this->get_new_parentid('scorm_seq_ruleconds');
$newitemid = $DB->insert_record('scorm_seq_rulecond', $data);
// No need to save this mapping as far as nothing depend on it
// (child paths, file areas nor links decoder)
}
protected function process_scorm_seq_mapinfo($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->scoid = $this->get_new_parentid('scorm_sco');
$data->objectiveid = $this->get_new_parentid('scorm_seq_objective');
$newitemid = $DB->insert_record('scorm_scoes_data', $data);
// No need to save this mapping as far as nothing depend on it
// (child paths, file areas nor links decoder)
}
protected function process_scorm_sco_track($data) {
global $DB, $CFG;
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$data = (object)$data;
$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);
$DB->insert_record('scorm_scoes_value', $data);
// No need to save this mapping as far as nothing depend on it.
}
protected function after_execute() {
global $DB;
// Add scorm related files, no need to match by itemname (just internally handled context)
$this->add_related_files('mod_scorm', 'intro', null);
$this->add_related_files('mod_scorm', 'content', null);
$this->add_related_files('mod_scorm', 'package', null);
// Fix launch param in scorm table to use new sco id.
$scormid = $this->get_new_parentid('scorm');
$scorm = $DB->get_record('scorm', array('id' => $scormid));
$scorm->launch = $this->get_mappingid('scorm_sco', $scorm->launch, '');
if (!empty($scorm->launch)) {
// Check that this sco has a valid launch value.
$scolaunch = $DB->get_field('scorm_scoes', 'launch', array('id' => $scorm->launch));
if (empty($scolaunch)) {
// This is not a valid sco - set to empty so we can find a valid launch sco.
$scorm->launch = '';
}
}
if (empty($scorm->launch)) {
// This scorm has an invalid launch param - we need to calculate it and get the first launchable sco.
$sqlselect = 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true);
// We use get_records here as we need to pass a limit in the query that works cross db.
$scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scormid), 'sortorder', 'id', 0, 1);
if (!empty($scoes)) {
$sco = reset($scoes); // We only care about the first record - the above query only returns one.
$scorm->launch = $sco->id;
}
}
if (!empty($scorm->launch)) {
$DB->update_record('scorm', $scorm);
}
}
}
@@ -0,0 +1,56 @@
<?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/>.
/**
* Activity base class.
*
* @package mod_scorm
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Activity base class.
*
* @package mod_scorm
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
/**
* feedback_viewed_events
*
* @return string[]
*/
protected function feedback_viewed_events() {
// Any view after the data graded counts as feedback viewed.
return array('\mod_scorm\event\course_module_viewed');
}
/**
* Returns the name of the field that controls activity availability.
*
* @return null|string
*/
protected function get_timeclose_field() {
return 'timeclose';
}
}
@@ -0,0 +1,56 @@
<?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/>.
/**
* Cognitive depth indicator - scorm.
*
* @package mod_scorm
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Cognitive depth indicator - scorm.
*
* @package mod_scorm
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cognitive_depth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:cognitivedepth', 'mod_scorm');
}
public function get_indicator_type() {
return self::INDICATOR_COGNITIVE;
}
public function get_cognitive_depth_level(\cm_info $cm) {
return self::COGNITIVE_LEVEL_3;
}
}
@@ -0,0 +1,56 @@
<?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/>.
/**
* Social breadth indicator - scorm.
*
* @package mod_scorm
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Social breadth indicator - scorm.
*
* @package mod_scorm
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class social_breadth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:socialbreadth', 'mod_scorm');
}
public function get_indicator_type() {
return self::INDICATOR_SOCIAL;
}
public function get_social_breadth_level(\cm_info $cm) {
return self::SOCIAL_LEVEL_2;
}
}
+77
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);
}
}
@@ -0,0 +1,220 @@
<?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\completion;
defined('MOODLE_INTERNAL') || die();
use core_completion\activity_custom_completion;
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
/**
* Activity custom completion subclass for the scorm activity.
*
* Contains the class for defining mod_scorm's custom completion rules
* and fetching a scorm instance's completion statuses for a user.
*
* @package mod_scorm
* @copyright Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_completion extends activity_custom_completion {
/**
* Fetches the completion state for a given completion rule.
*
* @param string $rule The completion rule.
* @return int The completion state.
*/
public function get_state(string $rule): int {
global $DB;
$this->validate_rule($rule);
// Base query used when fetching user's tracks data.
$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 e.element IN (
'cmi.core.lesson_status',
'cmi.completion_status',
'cmi.success_status'
)";
$tracks = $DB->get_records_sql($query, [$this->cm->instance, $this->userid]);
// Get available status list.
$statuses = array_flip(\scorm_status_options());
$requiredcompletionstatusid = $this->cm->customdata['customcompletionrules']['completionstatusrequired'] ?? 0;
$isanystatus = ($requiredcompletionstatusid == array_sum($statuses));
// Check at least one track meets the required completion status value(s).
foreach ($tracks as $track) {
if (array_key_exists($track->value, $statuses)
&& ($isanystatus || $statuses[$track->value] == $requiredcompletionstatusid)) {
$status = COMPLETION_COMPLETE;
break;
}
}
break;
case 'completionscorerequired':
$status = COMPLETION_INCOMPLETE;
$query = $basequery .
" AND e.element IN (
'cmi.core.score.raw',
'cmi.score.raw'
)";
$tracks = $DB->get_records_sql($query, [$this->cm->instance, $this->userid]);
$requiredscore = $this->cm->customdata['customcompletionrules']['completionscorerequired'];
// Check if any track meets or exceeds the minimum score required.
foreach ($tracks as $track) {
if (strlen($track->value) && (floatval($track->value) >= $requiredscore)) {
$status = COMPLETION_COMPLETE;
// No need to check any other tracks once condition is confirmed completed.
break;
}
}
break;
case 'completionstatusallscos':
// Assume complete unless we find a sco that is not complete.
$status = COMPLETION_COMPLETE;
$query = $basequery .
" AND e.element IN (
'cmi.core.lesson_status',
'cmi.completion_status',
'cmi.success_status'
)";
$tracks = $DB->get_records_sql($query, [$this->cm->instance, $this->userid]);
// Get available status list.
$statuses = array_flip(\scorm_status_options());
// Make a list of all sco IDs.
$scoids = [];
foreach ($tracks as $track) {
if (array_key_exists($track->value, $statuses)) {
$scoids[] = $track->scoid;
}
}
// Iterate over all scos and ensure each has a lesson_status.
$scos = $DB->get_records('scorm_scoes', ['scorm' => $this->cm->instance, 'scormtype' => 'sco']);
foreach ($scos as $sco) {
// If we find a sco without a lesson status, this condition is not completed.
if (!in_array($sco->id, $scoids)) {
$status = COMPLETION_INCOMPLETE;
}
}
break;
default:
$status = COMPLETION_INCOMPLETE;
break;
}
// If not yet meeting the requirement and no attempts remain to complete it, mark it as failed.
if ($status === COMPLETION_INCOMPLETE) {
$scorm = $DB->get_record('scorm', ['id' => $this->cm->instance]);
$attemptcount = scorm_get_attempt_count($this->userid, $scorm);
if ($scorm->maxattempt > 0 && $attemptcount >= $scorm->maxattempt) {
$status = COMPLETION_COMPLETE_FAIL;
}
}
return $status;
}
/**
* Fetch the list of custom completion rules that this module defines.
*
* @return array
*/
public static function get_defined_custom_rules(): array {
return [
'completionstatusrequired',
'completionscorerequired',
'completionstatusallscos',
];
}
/**
* Returns an associative array of the descriptions of custom completion rules.
*
* @return array
*/
public function get_custom_rule_descriptions(): array {
$scorerequired = $this->cm->customdata['customcompletionrules']['completionscorerequired'] ?? 0;
// Prepare completion status requirements string.
$statusstrings = \scorm_status_options();
$completionstatusid = $this->cm->customdata['customcompletionrules']['completionstatusrequired'] ?? 0;
if (array_key_exists($completionstatusid, $statusstrings)) {
// Single status required.
$statusrequired = $statusstrings[$completionstatusid];
} else {
// All statuses required.
$statusrequired = 'completedorpassed';
}
return [
'completionstatusrequired' => get_string("completiondetail:completionstatus{$statusrequired}", 'scorm'),
'completionscorerequired' => get_string('completiondetail:completionscore', 'scorm', $scorerequired),
'completionstatusallscos' => get_string('completiondetail:allscos', 'scorm'),
];
}
/**
* Returns an array of all completion rules, in the order they should be displayed to users.
*
* @return array
*/
public function get_sort_order(): array {
return [
'completionview',
'completionstatusallscos',
'completionstatusrequired',
'completionusegrade',
'completionpassgrade',
'completionscorerequired',
];
}
}
+70
View File
@@ -0,0 +1,70 @@
<?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/>.
/**
* Contains the class for fetching the important dates in mod_scorm for a given module instance and a user.
*
* @package mod_scorm
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace mod_scorm;
use core\activity_dates;
/**
* Class for fetching the important dates in mod_scorm for a given module instance and a user.
*
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dates extends activity_dates {
/**
* Returns a list of important dates in mod_scorm
*
* @return array
*/
protected function get_dates(): array {
$timeopen = $this->cm->customdata['timeopen'] ?? null;
$timeclose = $this->cm->customdata['timeclose'] ?? null;
$now = time();
$dates = [];
if ($timeopen) {
$openlabelid = $timeopen > $now ? 'activitydate:opens' : 'activitydate:opened';
$dates[] = [
'dataid' => 'timeopen',
'label' => get_string($openlabelid, 'core_course'),
'timestamp' => (int) $timeopen,
];
}
if ($timeclose) {
$closelabelid = $timeclose > $now ? 'activitydate:closes' : 'activitydate:closed';
$dates[] = [
'dataid' => 'timeclose',
'label' => get_string($closelabelid, 'core_course'),
'timestamp' => (int) $timeclose,
];
}
return $dates;
}
}
@@ -0,0 +1,98 @@
<?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/>.
/**
* The mod_scorm attempt deleted event.
*
* @package mod_scorm
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm attempt deleted event class.
*
* @property-read array $other {
* Extra information about event properties.
*
* - int attemptid: Attempt id.
* }
*
* @package mod_scorm
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class attempt_deleted extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' deleted the attempt with id '{$this->other['attemptid']}' " .
"for the scorm activity with course module id '$this->contextinstanceid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventattemptdeleted', 'mod_scorm');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/scorm/report.php', array('id' => $this->contextinstanceid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (empty($this->other['attemptid'])) {
throw new \coding_exception('The \'attemptid\' must be set in other.');
}
}
public static function get_other_mapping() {
// Nothing to map.
return false;
}
}
@@ -0,0 +1,105 @@
<?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/>.
/**
* The mod_scorm generic CMI element submitted event.
*
* @package mod_scorm
* @copyright 2016 onwards Matteo Scaramuccia
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm generic CMI element submitted event class.
*
* @property-read array $other {
* Extra information about event properties.
*
* - int attemptid: Attempt id.
* - string cmielement: CMI element.
* - string cmivalue: CMI value.
* }
*
* @package mod_scorm
* @since Moodle 3.1
* @copyright 2016 onwards Matteo Scaramuccia
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class cmielement_submitted extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'scorm_scoes_value';
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with the id '$this->userid' submitted the element '{$this->other['cmielement']}' " .
"with the value of '{$this->other['cmivalue']}' " .
"for the attempt with the id '{$this->other['attemptid']}' " .
"for a scorm activity with the course module id '$this->contextinstanceid'.";
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/scorm/report/userreport.php',
array('id' => $this->contextinstanceid, 'user' => $this->userid, 'attempt' => $this->other['attemptid']));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (empty($this->other['attemptid'])) {
throw new \coding_exception("The 'attemptid' must be set in other.");
}
if (empty($this->other['cmielement'])) {
throw new \coding_exception("The 'cmielement' must be set in other.");
}
// Trust that 'cmielement' represents a valid CMI datamodel element:
// just check that the given value starts with 'cmi.'.
if (strpos($this->other['cmielement'], 'cmi.', 0) !== 0) {
throw new \coding_exception(
"A valid 'cmielement' must start with 'cmi.' ({$this->other['cmielement']}).");
}
// Warning: 'cmivalue' could be also "0" e.g. when 'cmielement' represents a score.
if (!isset($this->other['cmivalue'])) {
throw new \coding_exception("The 'cmivalue' must be set in other.");
}
}
}
@@ -0,0 +1,38 @@
<?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/>.
/**
* The mod_scorm instance list viewed event.
*
* @package mod_scorm
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm instance list viewed event class.
*
* @package mod_scorm
* @since Moodle 2.7
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
}
@@ -0,0 +1,51 @@
<?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/>.
/**
* The mod_scorm course module viewed event.
*
* @package mod_scorm
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm course module viewed event class.
*
* @package mod_scorm
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_viewed extends \core\event\course_module_viewed {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'scorm';
}
public static function get_objectid_mapping() {
return array('db' => 'scorm', 'restore' => 'scorm');
}
}
@@ -0,0 +1,114 @@
<?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/>.
/**
* The mod_scorm interactions viewed event.
*
* @package mod_scorm
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm interactions viewed event class.
*
* @property-read array $other {
* Extra information about event properties.
*
* - int attemptid: Attempt id.
* - int instanceid: Instance id of the scorm activity.
* }
*
* @package mod_scorm
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class interactions_viewed extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the interactions for the user with id '$this->relateduserid' " .
"for the scorm activity with course module id '$this->contextinstanceid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventinteractionsviewed', 'mod_scorm');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
$params = array(
'id' => $this->contextinstanceid,
'user' => $this->relateduserid,
'attempt' => $this->other['attemptid']
);
return new \moodle_url('/mod/scorm/userreportinteractions.php', $params);
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (empty($this->other['attemptid'])) {
throw new \coding_exception('The \'attemptid\' must be set in other.');
}
if (empty($this->other['instanceid'])) {
throw new \coding_exception('The \'instanceid\' must be set in other.');
}
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['instanceid'] = array('db' => 'scorm', 'restore' => 'scorm');
return $othermapped;
}
}
+105
View File
@@ -0,0 +1,105 @@
<?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/>.
/**
* The mod_scorm report viewed event.
*
* @package mod_scorm
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm report viewed event class.
*
* @property-read array $other {
* Extra information about event properties.
*
* - int scormid: The ID of the scorm.
* - string mode: Mode of the report viewed.
* }
*
* @package mod_scorm
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_viewed extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the scorm report '{$this->other['mode']}' for the scorm with " .
"course module id '$this->contextinstanceid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventreportviewed', 'mod_scorm');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/scorm/report.php', array('id' => $this->contextinstanceid, 'mode' => $this->other['mode']));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (empty($this->other['scormid'])) {
throw new \coding_exception('The \'scormid\' value must be set in other.');
}
if (empty($this->other['mode'])) {
throw new \coding_exception('The \'mode\' value must be set in other.');
}
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['scormid'] = array('db' => 'scorm', 'restore' => 'scorm');
return $othermapped;
}
}
+106
View File
@@ -0,0 +1,106 @@
<?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/>.
/**
* The mod_scorm sco launched event.
*
* @package mod_scorm
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm sco launched event class.
*
* @property-read array $other {
* Extra information about event properties.
*
* - string loadedcontent: A reference to the content loaded.
* - int instanceid: (optional) Instance id of the scorm activity.
* }
*
* @package mod_scorm
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sco_launched extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'scorm_scoes';
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' launched the sco with id '$this->objectid' for the scorm with " .
"course module id '$this->contextinstanceid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventscolaunched', 'mod_scorm');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/scorm/player.php', array('cm' => $this->contextinstanceid, 'scoid' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (empty($this->other['loadedcontent'])) {
throw new \coding_exception('The \'loadedcontent\' value must be set in other.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'scorm_scoes', 'restore' => 'scorm_sco');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['instanceid'] = array('db' => 'scorm', 'restore' => 'scorm');
return $othermapped;
}
}
@@ -0,0 +1,71 @@
<?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/>.
/**
* The mod_scorm raw score submitted event.
*
* @package mod_scorm
* @copyright 2016 onwards Matteo Scaramuccia
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm raw score submitted event class.
*
* @property-read array $other {
* Extra information about event properties.
*
* - int attemptid: Attempt id.
* - string cmielement: CMI element representing a raw score.
* - string cmivalue: CMI value.
* }
*
* @package mod_scorm
* @since Moodle 3.1
* @copyright 2016 onwards Matteo Scaramuccia
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class scoreraw_submitted extends cmielement_submitted {
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventscorerawsubmitted', 'mod_scorm');
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!strstr($this->other['cmielement'], '.score.raw')) {
throw new \coding_exception(
"The 'cmielement' must represents a valid CMI raw score ({$this->other['cmielement']}).");
}
// Note: we trust that 'cmivalue' represents a valid SCORM CMI score value.
}
}
@@ -0,0 +1,76 @@
<?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/>.
/**
* The mod_scorm status submitted event.
*
* @package mod_scorm
* @copyright 2016 onwards Matteo Scaramuccia
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm status submitted event class.
*
* @property-read array $other {
* Extra information about event properties.
*
* - int attemptid: Attempt id.
* - string cmielement: CMI element representing a status.
* - string cmivalue: CMI value.
* }
*
* @package mod_scorm
* @since Moodle 3.1
* @copyright 2016 onwards Matteo Scaramuccia
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class status_submitted extends cmielement_submitted {
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventstatussubmitted', 'mod_scorm');
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!in_array($this->other['cmielement'],
array('cmi.completion_status', 'cmi.core.lesson_status', 'cmi.success_status'))) {
throw new \coding_exception(
"The 'cmielement' must represents a valid CMI status element ({$this->other['cmielement']}).");
}
if (!in_array($this->other['cmivalue'],
array('passed', 'completed', 'failed', 'incomplete', 'browsed', 'not attempted', 'unknown'))) {
throw new \coding_exception(
"The 'cmivalue' must represents a valid CMI status value ({$this->other['cmivalue']}).");
}
}
}
+118
View File
@@ -0,0 +1,118 @@
<?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/>.
/**
* The mod_scorm tracks viewed event.
*
* @package mod_scorm
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm tracks viewed event class.
*
* @property-read array $other {
* Extra information about event properties.
*
* - int attemptid: Attempt id.
* - int instanceid: Instance id of the scorm activity.
* - int scoid: Sco Id for which the trackes are viewed.
* }
*
* @package mod_scorm
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tracks_viewed extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the tracks for the user with id '$this->relateduserid' " .
"for the scorm activity with course module id '$this->contextinstanceid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventtracksviewed', 'mod_scorm');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
$params = array(
'id' => $this->contextinstanceid,
'user' => $this->relateduserid,
'attempt' => $this->other['attemptid'],
'scoid' => $this->other['scoid']
);
return new \moodle_url('/mod/scorm/userreporttracks.php', $params);
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (empty($this->other['attemptid'])) {
throw new \coding_exception('The \'attemptid\' value must be set in other.');
}
if (empty($this->other['instanceid'])) {
throw new \coding_exception('The \'instanceid\' value must be set in other.');
}
if (empty($this->other['scoid'])) {
throw new \coding_exception('The \'scoid\' value must be set in other.');
}
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['instanceid'] = array('db' => 'scorm', 'restore' => 'scorm');
$othermapped['scoid'] = array('db' => 'scorm_scoes', 'restore' => 'scorm_scoe');
return $othermapped;
}
}
@@ -0,0 +1,111 @@
<?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/>.
/**
* The mod_scorm tracks user report viewed event.
*
* @package mod_scorm
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_scorm tracks user report viewed event class.
*
* @property-read array $other {
* Extra information about event properties.
*
* - int attemptid: Attempt id.
* - int instanceid: Instance id of the scorm activity.
* }
*
* @package mod_scorm
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_report_viewed extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the scorm user report for the user with id '$this->relateduserid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventuserreportviewed', 'mod_scorm');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
$params = array(
'id' => $this->contextinstanceid,
'user' => $this->relateduserid,
'attempt' => $this->other['attemptid']
);
return new \moodle_url('/mod/scorm/userreport.php', $params);
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (empty($this->other['attemptid'])) {
throw new \coding_exception('The \'attemptid\' value must be set in other.');
}
if (empty($this->other['instanceid'])) {
throw new \coding_exception('The \'instanceid\' value must be set in other.');
}
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['instanceid'] = array('db' => 'scorm', 'restore' => 'scorm');
return $othermapped;
}
}
+986
View File
@@ -0,0 +1,986 @@
<?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 module external API
*
* @package mod_scorm
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
use core_course\external\helper_for_get_mods_by_courses;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\util;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot . '/mod/scorm/lib.php');
require_once($CFG->dirroot . '/mod/scorm/locallib.php');
/**
* SCORM module external functions
*
* @package mod_scorm
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
class mod_scorm_external extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function view_scorm_parameters() {
return new external_function_parameters(
array(
'scormid' => new external_value(PARAM_INT, 'scorm instance id')
)
);
}
/**
* Trigger the course module viewed event.
*
* @param int $scormid the scorm instance id
* @return array of warnings and status result
* @since Moodle 3.0
* @throws moodle_exception
*/
public static function view_scorm($scormid) {
global $DB, $CFG;
require_once($CFG->dirroot . '/mod/scorm/lib.php');
$params = self::validate_parameters(self::view_scorm_parameters(),
array(
'scormid' => $scormid
));
$warnings = array();
// Request and permission validation.
$scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($scorm, 'scorm');
$context = context_module::instance($cm->id);
self::validate_context($context);
// Call the scorm/lib API.
scorm_view($scorm, $course, $cm, $context);
$result = array();
$result['status'] = true;
$result['warnings'] = $warnings;
return $result;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.0
*/
public static function view_scorm_returns() {
return new external_single_structure(
array(
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
)
);
}
/**
* Describes the parameters for get_scorm_attempt_count.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_scorm_attempt_count_parameters() {
return new external_function_parameters(
array(
'scormid' => new external_value(PARAM_INT, 'SCORM instance id'),
'userid' => new external_value(PARAM_INT, 'User id'),
'ignoremissingcompletion' => new external_value(PARAM_BOOL,
'Ignores attempts that haven\'t reported a grade/completion',
VALUE_DEFAULT, false),
)
);
}
/**
* Return the number of attempts done by a user in the given SCORM.
*
* @param int $scormid the scorm id
* @param int $userid the user id
* @param bool $ignoremissingcompletion ignores attempts that haven't reported a grade/completion
* @return array of warnings and the attempts count
* @since Moodle 3.0
*/
public static function get_scorm_attempt_count($scormid, $userid, $ignoremissingcompletion = false) {
global $USER, $DB;
$params = self::validate_parameters(self::get_scorm_attempt_count_parameters(),
array('scormid' => $scormid, 'userid' => $userid,
'ignoremissingcompletion' => $ignoremissingcompletion));
$attempts = array();
$warnings = array();
$scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('scorm', $scorm->id);
$context = context_module::instance($cm->id);
self::validate_context($context);
$user = core_user::get_user($params['userid'], '*', MUST_EXIST);
core_user::require_active_user($user);
// Extra checks so only users with permissions can view other users attempts.
if ($USER->id != $user->id) {
require_capability('mod/scorm:viewreport', $context);
}
// If the SCORM is not open this function will throw exceptions.
scorm_require_available($scorm);
$attemptscount = scorm_get_attempt_count($user->id, $scorm, false, $params['ignoremissingcompletion']);
$result = array();
$result['attemptscount'] = $attemptscount;
$result['warnings'] = $warnings;
return $result;
}
/**
* Describes the get_scorm_attempt_count return value.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function get_scorm_attempt_count_returns() {
return new external_single_structure(
array(
'attemptscount' => new external_value(PARAM_INT, 'Attempts count'),
'warnings' => new external_warnings(),
)
);
}
/**
* Describes the parameters for get_scorm_scoes.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_scorm_scoes_parameters() {
return new external_function_parameters(
array(
'scormid' => new external_value(PARAM_INT, 'scorm instance id'),
'organization' => new external_value(PARAM_RAW, 'organization id', VALUE_DEFAULT, '')
)
);
}
/**
* Returns a list containing all the scoes data related to the given scorm id
*
* @param int $scormid the scorm id
* @param string $organization the organization id
* @return array warnings and the scoes data
* @since Moodle 3.0
*/
public static function get_scorm_scoes($scormid, $organization = '') {
global $DB;
$params = self::validate_parameters(self::get_scorm_scoes_parameters(),
array('scormid' => $scormid, 'organization' => $organization));
$scoes = array();
$warnings = array();
$scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('scorm', $scorm->id);
$context = context_module::instance($cm->id);
self::validate_context($context);
// Check settings / permissions to view the SCORM.
scorm_require_available($scorm, true, $context);
if (!$scoes = scorm_get_scoes($scorm->id, $params['organization'])) {
// Function scorm_get_scoes return false, not an empty array.
$scoes = array();
} else {
$scoreturnstructure = self::get_scorm_scoes_returns();
foreach ($scoes as $sco) {
$extradata = array();
foreach ($sco as $element => $value) {
// Check if the element is extra data (not a basic SCO element).
if (!isset($scoreturnstructure->keys['scoes']->content->keys[$element])) {
$extradata[] = array(
'element' => $element,
'value' => $value
);
}
}
$sco->extradata = $extradata;
}
}
$result = array();
$result['scoes'] = $scoes;
$result['warnings'] = $warnings;
return $result;
}
/**
* Describes the get_scorm_scoes return value.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function get_scorm_scoes_returns() {
return new external_single_structure(
array(
'scoes' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'sco id'),
'scorm' => new external_value(PARAM_INT, 'scorm id'),
'manifest' => new external_value(PARAM_NOTAGS, 'manifest id'),
'organization' => new external_value(PARAM_NOTAGS, 'organization id'),
'parent' => new external_value(PARAM_NOTAGS, 'parent'),
'identifier' => new external_value(PARAM_NOTAGS, 'identifier'),
'launch' => new external_value(PARAM_NOTAGS, 'launch file'),
'scormtype' => new external_value(PARAM_ALPHA, 'scorm type (asset, sco)'),
'title' => new external_value(PARAM_NOTAGS, 'sco title'),
'sortorder' => new external_value(PARAM_INT, 'sort order'),
'extradata' => new external_multiple_structure(
new external_single_structure(
array(
'element' => new external_value(PARAM_RAW, 'element name'),
'value' => new external_value(PARAM_RAW, 'element value')
)
), 'Additional SCO data', VALUE_OPTIONAL
)
), 'SCORM SCO data'
)
),
'warnings' => new external_warnings(),
)
);
}
/**
* Describes the parameters for get_scorm_user_data.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_scorm_user_data_parameters() {
return new external_function_parameters(
array(
'scormid' => new external_value(PARAM_INT, 'scorm instance id'),
'attempt' => new external_value(PARAM_INT, 'attempt number')
)
);
}
/**
* Retrieves user tracking and SCO data and default SCORM values
*
* @param int $scormid the scorm id
* @param int $attempt the attempt number
* @return array warnings and the scoes data
* @throws moodle_exception
* @since Moodle 3.0
*/
public static function get_scorm_user_data($scormid, $attempt) {
global $CFG, $DB;
$params = self::validate_parameters(self::get_scorm_user_data_parameters(),
array('scormid' => $scormid, 'attempt' => $attempt));
$data = array();
$warnings = array();
$scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('scorm', $scorm->id);
$context = context_module::instance($cm->id);
self::validate_context($context);
scorm_require_available($scorm, true, $context);
$scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR));
if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) {
$scorm->version = 'scorm_12';
}
require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php');
if ($scoes = scorm_get_scoes($scorm->id)) {
$def = new stdClass();
$user = new stdClass();
foreach ($scoes as $sco) {
$def->{$sco->id} = new stdClass();
$user->{$sco->id} = new stdClass();
// We force mode normal, this can be override by the client at any time.
$def->{$sco->id} = get_scorm_default($user->{$sco->id}, $scorm, $sco->id, $params['attempt'], 'normal');
$userdata = array();
$defaultdata = array();
foreach ((array) $user->{$sco->id} as $key => $val) {
$userdata[] = array(
'element' => $key,
'value' => $val
);
}
foreach ($def->{$sco->id} as $key => $val) {
$defaultdata[] = array(
'element' => $key,
'value' => $val
);
}
$data[] = array(
'scoid' => $sco->id,
'userdata' => $userdata,
'defaultdata' => $defaultdata,
);
}
}
$result = array();
$result['data'] = $data;
$result['warnings'] = $warnings;
return $result;
}
/**
* Describes the get_scorm_user_data return value.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function get_scorm_user_data_returns() {
return new external_single_structure(
array(
'data' => new external_multiple_structure(
new external_single_structure(
array(
'scoid' => new external_value(PARAM_INT, 'sco id'),
'userdata' => new external_multiple_structure(
new external_single_structure(
array(
'element' => new external_value(PARAM_RAW, 'element name'),
'value' => new external_value(PARAM_RAW, 'element value')
)
)
),
'defaultdata' => new external_multiple_structure(
new external_single_structure(
array(
'element' => new external_value(PARAM_RAW, 'element name'),
'value' => new external_value(PARAM_RAW, 'element value')
)
)
),
), 'SCO data'
)
),
'warnings' => new external_warnings(),
)
);
}
/**
* Describes the parameters for insert_scorm_tracks.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function insert_scorm_tracks_parameters() {
return new external_function_parameters(
array(
'scoid' => new external_value(PARAM_INT, 'SCO id'),
'attempt' => new external_value(PARAM_INT, 'attempt number'),
'tracks' => new external_multiple_structure(
new external_single_structure(
array(
'element' => new external_value(PARAM_RAW, 'element name'),
'value' => new external_value(PARAM_RAW, 'element value')
)
)
),
)
);
}
/**
* Saves a SCORM tracking record.
* It will overwrite any existing tracking data for this attempt.
* Validation should be performed before running the function to ensure the user will not lose any existing attempt data.
*
* @param int $scoid the SCO id
* @param string $attempt the attempt number
* @param array $tracks the track records to be stored
* @return array warnings and the scoes data
* @throws moodle_exception
* @since Moodle 3.0
*/
public static function insert_scorm_tracks($scoid, $attempt, $tracks) {
global $USER, $DB;
$params = self::validate_parameters(self::insert_scorm_tracks_parameters(),
array('scoid' => $scoid, 'attempt' => $attempt, 'tracks' => $tracks));
$trackids = array();
$warnings = array();
$sco = scorm_get_sco($params['scoid'], SCO_ONLY);
if (!$sco) {
throw new moodle_exception('cannotfindsco', 'scorm');
}
$scorm = $DB->get_record('scorm', array('id' => $sco->scorm), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('scorm', $scorm->id);
$context = context_module::instance($cm->id);
self::validate_context($context);
// Check settings / permissions to view the SCORM.
require_capability('mod/scorm:savetrack', $context);
// 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, $attemptobject, $element, $value,
$scorm->forcecompleted);
if ($trackid) {
$trackids[] = $trackid;
} else {
$warnings[] = array(
'item' => 'scorm',
'itemid' => $scorm->id,
'warningcode' => 1,
'message' => 'Element: ' . $element . ' was not saved'
);
}
}
$result = array();
$result['trackids'] = $trackids;
$result['warnings'] = $warnings;
return $result;
}
/**
* Describes the insert_scorm_tracks return value.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function insert_scorm_tracks_returns() {
return new external_single_structure(
array(
'trackids' => new external_multiple_structure(new external_value(PARAM_INT, 'track id')),
'warnings' => new external_warnings(),
)
);
}
/**
* Describes the parameters for get_scorm_sco_tracks.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_scorm_sco_tracks_parameters() {
return new external_function_parameters(
array(
'scoid' => new external_value(PARAM_INT, 'sco id'),
'userid' => new external_value(PARAM_INT, 'user id'),
'attempt' => new external_value(PARAM_INT, 'attempt number (0 for last attempt)', VALUE_DEFAULT, 0)
)
);
}
/**
* Retrieves SCO tracking data for the given user id and attempt number
*
* @param int $scoid the sco id
* @param int $userid the user id
* @param int $attempt the attempt number
* @return array warnings and the scoes data
* @since Moodle 3.0
*/
public static function get_scorm_sco_tracks($scoid, $userid, $attempt = 0) {
global $USER, $DB;
$params = self::validate_parameters(self::get_scorm_sco_tracks_parameters(),
array('scoid' => $scoid, 'userid' => $userid, 'attempt' => $attempt));
$tracks = array();
$warnings = array();
$sco = scorm_get_sco($params['scoid'], SCO_ONLY);
if (!$sco) {
throw new moodle_exception('cannotfindsco', 'scorm');
}
$scorm = $DB->get_record('scorm', array('id' => $sco->scorm), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('scorm', $scorm->id);
$context = context_module::instance($cm->id);
self::validate_context($context);
$user = core_user::get_user($params['userid'], '*', MUST_EXIST);
core_user::require_active_user($user);
// Extra checks so only users with permissions can view other users attempts.
if ($USER->id != $user->id) {
require_capability('mod/scorm:viewreport', $context);
}
scorm_require_available($scorm, true, $context);
if (empty($params['attempt'])) {
$params['attempt'] = scorm_get_last_attempt($scorm->id, $user->id);
}
$attempted = false;
if ($scormtracks = scorm_get_tracks($sco->id, $params['userid'], $params['attempt'])) {
// Check if attempted.
if ($scormtracks->status != '') {
$attempted = true;
foreach ($scormtracks as $element => $value) {
$tracks[] = array(
'element' => $element,
'value' => $value,
);
}
}
}
if (!$attempted) {
$warnings[] = array(
'item' => 'attempt',
'itemid' => $params['attempt'],
'warningcode' => 'notattempted',
'message' => get_string('notattempted', 'scorm')
);
}
$result = array();
$result['data']['attempt'] = $params['attempt'];
$result['data']['tracks'] = $tracks;
$result['warnings'] = $warnings;
return $result;
}
/**
* Describes the get_scorm_sco_tracks return value.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function get_scorm_sco_tracks_returns() {
return new external_single_structure(
array(
'data' => new external_single_structure(
array(
'attempt' => new external_value(PARAM_INT, 'Attempt number'),
'tracks' => new external_multiple_structure(
new external_single_structure(
array(
'element' => new external_value(PARAM_RAW, 'Element name'),
'value' => new external_value(PARAM_RAW, 'Element value')
), 'Tracks data'
)
),
), 'SCO data'
),
'warnings' => new external_warnings(),
)
);
}
/**
* Describes the parameters for get_scorms_by_courses.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_scorms_by_courses_parameters() {
return new external_function_parameters (
array(
'courseids' => new external_multiple_structure(
new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array()
),
)
);
}
/**
* Returns a list of scorms in a provided list of courses,
* if no list is provided all scorms that the user can view will be returned.
*
* @param array $courseids the course ids
* @return array the scorm details
* @since Moodle 3.0
*/
public static function get_scorms_by_courses($courseids = array()) {
global $CFG;
$returnedscorms = array();
$warnings = array();
$params = self::validate_parameters(self::get_scorms_by_courses_parameters(), array('courseids' => $courseids));
$courses = array();
if (empty($params['courseids'])) {
$courses = enrol_get_my_courses();
$params['courseids'] = array_keys($courses);
}
// Ensure there are courseids to loop through.
if (!empty($params['courseids'])) {
list($courses, $warnings) = util::validate_courses($params['courseids'], $courses);
// Get the scorms in this course, this function checks users visibility permissions.
// We can avoid then additional validate_context calls.
$scorms = get_all_instances_in_courses("scorm", $courses);
$fs = get_file_storage();
foreach ($scorms as $scorm) {
$context = context_module::instance($scorm->coursemodule);
// Entry to return.
$module = helper_for_get_mods_by_courses::standard_coursemodule_element_values($scorm, 'mod_scorm');
// Check if the SCORM open and return warnings if so.
list($open, $openwarnings) = scorm_get_availability_status($scorm, true, $context);
if (!$open) {
foreach ($openwarnings as $warningkey => $warningdata) {
$warnings[] = array(
'item' => 'scorm',
'itemid' => $scorm->id,
'warningcode' => $warningkey,
'message' => get_string($warningkey, 'scorm', $warningdata)
);
}
} else {
$module['packagesize'] = 0;
// SCORM size.
if ($scorm->scormtype === SCORM_TYPE_LOCAL or $scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
if ($packagefile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, '/', $scorm->reference)) {
$module['packagesize'] = $packagefile->get_filesize();
// Download URL.
$module['packageurl'] = moodle_url::make_webservice_pluginfile_url(
$context->id, 'mod_scorm', 'package', 0, '/', $scorm->reference)->out(false);
}
}
$module['protectpackagedownloads'] = get_config('scorm', 'protectpackagedownloads');
$viewablefields = array('version', 'maxgrade', 'grademethod', 'whatgrade', 'maxattempt', 'forcecompleted',
'forcenewattempt', 'lastattemptlock', 'displayattemptstatus', 'displaycoursestructure',
'sha1hash', 'md5hash', 'revision', 'launch', 'skipview', 'hidebrowse', 'hidetoc', 'nav',
'navpositionleft', 'navpositiontop', 'auto', 'popup', 'width', 'height', 'timeopen',
'timeclose', 'scormtype', 'reference');
// Check additional permissions for returning optional private settings.
if (has_capability('moodle/course:manageactivities', $context)) {
$additionalfields = array('updatefreq', 'options', 'completionstatusrequired', 'completionscorerequired',
'completionstatusallscos', 'autocommit', 'timemodified');
$viewablefields = array_merge($viewablefields, $additionalfields);
}
foreach ($viewablefields as $field) {
$module[$field] = $scorm->{$field};
}
}
$returnedscorms[] = $module;
}
}
$settings = [
[
'name' => 'scormstandard',
'value' => get_config('scorm', 'scormstandard'),
]
];
$result = [
'scorms' => $returnedscorms,
'options' => $settings,
'warnings' => $warnings
];
return $result;
}
/**
* Describes the get_scorms_by_courses return value.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function get_scorms_by_courses_returns() {
return new external_single_structure(
array(
'scorms' => new external_multiple_structure(
new external_single_structure(array_merge(
helper_for_get_mods_by_courses::standard_coursemodule_elements_returns(),
[
'packagesize' => new external_value(PARAM_INT, 'SCORM zip package size', VALUE_OPTIONAL),
'packageurl' => new external_value(PARAM_URL, 'SCORM zip package URL', VALUE_OPTIONAL),
'version' => new external_value(PARAM_NOTAGS, 'SCORM version (SCORM_12, SCORM_13, SCORM_AICC)',
VALUE_OPTIONAL),
'maxgrade' => new external_value(PARAM_INT, 'Max grade', VALUE_OPTIONAL),
'grademethod' => new external_value(PARAM_INT, 'Grade method', VALUE_OPTIONAL),
'whatgrade' => new external_value(PARAM_INT, 'What grade', VALUE_OPTIONAL),
'maxattempt' => new external_value(PARAM_INT, 'Maximum number of attemtps', VALUE_OPTIONAL),
'forcecompleted' => new external_value(PARAM_BOOL, 'Status current attempt is forced to "completed"',
VALUE_OPTIONAL),
'forcenewattempt' => new external_value(PARAM_INT, 'Controls re-entry behaviour',
VALUE_OPTIONAL),
'lastattemptlock' => new external_value(PARAM_BOOL, 'Prevents to launch new attempts once finished',
VALUE_OPTIONAL),
'displayattemptstatus' => new external_value(PARAM_INT, 'How to display attempt status',
VALUE_OPTIONAL),
'displaycoursestructure' => new external_value(PARAM_BOOL, 'Display contents structure',
VALUE_OPTIONAL),
'sha1hash' => new external_value(PARAM_NOTAGS, 'Package content or ext path hash', VALUE_OPTIONAL),
'md5hash' => new external_value(PARAM_NOTAGS, 'MD5 Hash of package file', VALUE_OPTIONAL),
'revision' => new external_value(PARAM_INT, 'Revison number', VALUE_OPTIONAL),
'launch' => new external_value(PARAM_INT, 'First content to launch', VALUE_OPTIONAL),
'skipview' => new external_value(PARAM_INT, 'How to skip the content structure page', VALUE_OPTIONAL),
'hidebrowse' => new external_value(PARAM_BOOL, 'Disable preview mode?', VALUE_OPTIONAL),
'hidetoc' => new external_value(PARAM_INT, 'How to display the SCORM structure in player',
VALUE_OPTIONAL),
'nav' => new external_value(PARAM_INT, 'Show navigation buttons', VALUE_OPTIONAL),
'navpositionleft' => new external_value(PARAM_INT, 'Navigation position left', VALUE_OPTIONAL),
'navpositiontop' => new external_value(PARAM_INT, 'Navigation position top', VALUE_OPTIONAL),
'auto' => new external_value(PARAM_BOOL, 'Auto continue?', VALUE_OPTIONAL),
'popup' => new external_value(PARAM_INT, 'Display in current or new window', VALUE_OPTIONAL),
'width' => new external_value(PARAM_INT, 'Frame width', VALUE_OPTIONAL),
'height' => new external_value(PARAM_INT, 'Frame height', VALUE_OPTIONAL),
'timeopen' => new external_value(PARAM_INT, 'Available from', VALUE_OPTIONAL),
'timeclose' => new external_value(PARAM_INT, 'Available to', VALUE_OPTIONAL),
'scormtype' => new external_value(PARAM_ALPHA, 'SCORM type', VALUE_OPTIONAL),
'reference' => new external_value(PARAM_NOTAGS, 'Reference to the package', VALUE_OPTIONAL),
'protectpackagedownloads' => new external_value(PARAM_BOOL, 'Protect package downloads?',
VALUE_OPTIONAL),
'updatefreq' => new external_value(PARAM_INT, 'Auto-update frequency for remote packages',
VALUE_OPTIONAL),
'options' => new external_value(PARAM_RAW, 'Additional options', VALUE_OPTIONAL),
'completionstatusrequired' => new external_value(PARAM_INT, 'Status passed/completed required?',
VALUE_OPTIONAL),
'completionscorerequired' => new external_value(PARAM_INT, 'Minimum score required', VALUE_OPTIONAL),
'completionstatusallscos' => new external_value(PARAM_INT, 'Require all scos to return completion status', VALUE_OPTIONAL),
'autocommit' => new external_value(PARAM_BOOL, 'Save track data automatically?', VALUE_OPTIONAL),
'timemodified' => new external_value(PARAM_INT, 'Time of last modification', VALUE_OPTIONAL),
]
), 'SCORM')
),
'options' => new external_multiple_structure(
new external_single_structure(
[
'name' => new external_value(PARAM_RAW, 'Options name'),
'value' => new external_value(PARAM_RAW, 'Option value')
]
), 'Global SCORM options', VALUE_OPTIONAL
),
'warnings' => new external_warnings(),
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function launch_sco_parameters() {
return new external_function_parameters(
array(
'scormid' => new external_value(PARAM_INT, 'SCORM instance id'),
'scoid' => new external_value(PARAM_INT, 'SCO id (empty for launching the first SCO)', VALUE_DEFAULT, 0)
)
);
}
/**
* Trigger the course module viewed event.
*
* @param int $scormid the SCORM instance id
* @param int $scoid the SCO id
* @return array of warnings and status result
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function launch_sco($scormid, $scoid = 0) {
global $DB, $CFG;
require_once($CFG->libdir . '/completionlib.php');
$params = self::validate_parameters(self::launch_sco_parameters(),
array(
'scormid' => $scormid,
'scoid' => $scoid
));
$warnings = array();
// Request and permission validation.
$scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($scorm, 'scorm');
$context = context_module::instance($cm->id);
self::validate_context($context);
// If the SCORM is not open this function will throw exceptions.
scorm_require_available($scorm);
if (!empty($params['scoid']) and !($sco = scorm_get_sco($params['scoid'], SCO_ONLY))) {
throw new moodle_exception('cannotfindsco', 'scorm');
}
// Mark module viewed.
$completion = new completion_info($course);
$completion->set_module_viewed($cm);
list($sco, $scolaunchurl) = scorm_get_sco_and_launch_url($scorm, $params['scoid'], $context);
// Trigger the SCO launched event.
scorm_launch_sco($scorm, $sco, $cm, $context, $scolaunchurl);
$result = array();
$result['status'] = true;
$result['warnings'] = $warnings;
return $result;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function launch_sco_returns() {
return new external_single_structure(
array(
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
)
);
}
/**
* Describes the parameters for get_scorm_access_information.
*
* @return external_external_function_parameters
* @since Moodle 3.7
*/
public static function get_scorm_access_information_parameters() {
return new external_function_parameters (
array(
'scormid' => new external_value(PARAM_INT, 'scorm instance id.')
)
);
}
/**
* Return access information for a given scorm.
*
* @param int $scormid scorm instance id
* @return array of warnings and the access information
* @since Moodle 3.7
* @throws moodle_exception
*/
public static function get_scorm_access_information($scormid) {
global $DB;
$params = self::validate_parameters(self::get_scorm_access_information_parameters(), array('scormid' => $scormid));
// Request and permission validation.
$scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($scorm, 'scorm');
$context = context_module::instance($cm->id);
self::validate_context($context);
$result = array();
// Return all the available capabilities.
$capabilities = load_capability_def('mod_scorm');
foreach ($capabilities as $capname => $capdata) {
// Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
$field = 'can' . str_replace('mod/scorm:', '', $capname);
$result[$field] = has_capability($capname, $context);
}
$result['warnings'] = array();
return $result;
}
/**
* Describes the get_scorm_access_information return value.
*
* @return external_single_structure
* @since Moodle 3.7
*/
public static function get_scorm_access_information_returns() {
$structure = array(
'warnings' => new external_warnings()
);
$capabilities = load_capability_def('mod_scorm');
foreach ($capabilities as $capname => $capdata) {
// Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
$field = 'can' . str_replace('mod/scorm:', '', $capname);
$structure[$field] = new external_value(PARAM_BOOL, 'Whether the user has the capability ' . $capname . ' allowed.',
VALUE_OPTIONAL);
}
return new external_single_structure($structure);
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_scorm\output;
use renderable;
use renderer_base;
use templatable;
use moodle_url;
use url_select;
/**
* Render HTML elements for tertiary nav for scorm.
*
* @package mod_scorm
* @copyright 2021 Sujith Haridasan <sujith@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class actionbar implements renderable, templatable {
/** @var int */
private $id;
/** @var string */
private $download;
/** @var int */
private $attemptsmode;
/**
* actionbar constructor.
*
* @param int $id The course module id.
* @param bool $download Show download button or not.
* @param int $attemptsmode attempts mode for scorm.
*/
public function __construct(int $id, bool $download = false, int $attemptsmode = 0) {
$this->id = $id;
$this->download = $download;
$this->attemptsmode = $attemptsmode;
}
/**
* Provide data for the template
*
* @param renderer_base $output renderer_base object.
* @return array data for the template
*/
public function export_for_template(renderer_base $output): array {
global $PAGE;
$basicreportlink = new moodle_url('/mod/scorm/report.php', ['id' => $this->id, 'mode' => 'basic']);
$graphreportlink = new moodle_url('/mod/scorm/report.php', ['id' => $this->id, 'mode' => 'graphs']);
$interactionreportlink = new moodle_url('/mod/scorm/report.php', ['id' => $this->id, 'mode' => 'interactions']);
$objectivesreportlink = new moodle_url('/mod/scorm/report.php', ['id' => $this->id, 'mode' => 'objectives']);
$reportmenu = [
$basicreportlink->out(false) => get_string('pluginname', 'scormreport_basic'),
$graphreportlink->out(false) => get_string('pluginname', 'scormreport_graphs'),
$interactionreportlink->out(false) => get_string('pluginname', 'scormreport_interactions'),
$objectivesreportlink->out(false) => get_string('pluginname', 'scormreport_objectives'),
];
$sesskey = sesskey();
if ($this->download) {
$mode = $PAGE->url->get_param('mode');
if ($mode === 'basic') {
$options = [
'id' => $this->id, 'mode' => $mode,
'attemptsmode' => $this->attemptsmode, 'sesskey' => $sesskey
];
} else if ($mode === 'interactions') {
$options = [
'id' => $this->id, 'mode' => $mode, 'qtext' => '0',
'resp' => '1', 'right' => '0', 'result' => '0',
'attemptsmode' => $this->attemptsmode, 'sesskey' => $sesskey
];
} else if ($mode === 'objectives') {
$options = [
'id' => $this->id, 'mode' => $mode,
'attemptsmode' => $this->attemptsmode, 'objectivescore' => '0',
'sesskey' => $sesskey
];
}
$options['download'] = 'ODS';
$downloadodslink = new moodle_url($PAGE->url, $options);
$options['download'] = 'Excel';
$downloadexcellink = new moodle_url($PAGE->url, $options);
$options['download'] = 'CSV';
$downloadtextlink = new moodle_url($PAGE->url, $options);
}
$url = new moodle_url('/mod/scorm/report.php', $PAGE->url->remove_params('attemptsmode'));
$urlselect = new url_select($reportmenu, $url->out(false), null, 'selectscormreports');
$heading = $reportmenu[$url->out(false)] ?? null;
$data = [
'heading' => $heading,
'scormreports' => $urlselect->export_for_template($output),
'candownload' => $this->download,
'downloadods' => ($this->download) ? $downloadodslink->out(false) : '',
'downloadexcel' => ($this->download) ? $downloadexcellink->out(false) : '',
'downloadtext' => ($this->download) ? $downloadtextlink->out(false) : ''
];
return $data;
}
}
@@ -0,0 +1,102 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_scorm\output;
use renderable;
use renderer_base;
use templatable;
use moodle_url;
use url_select;
/**
* Render HTML elements for reports page on tertiary nav.
*
* @package mod_scorm
* @copyright 2021 Sujith Haridasan <sujith@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class userreportsactionbar implements renderable, templatable {
/** @var int */
private $id;
/** @var int */
private $userid;
/** @var int */
private $attempt;
/** @var string */
private $reporttype;
/** @var string */
private $mode;
/** @var int */
private $scoid;
/**
* userreportsactionbar constructor
*
* @param int $id Course module id.
* @param int $userid User id.
* @param int $attempt Number of attempts.
* @param string $reporttype The report type can be either learning/interact.
* @param string $mode The mode view to set the back button.
* @param int|null $scoid The scorm id.
*/
public function __construct(int $id, int $userid, int $attempt, string $reporttype, string $mode, ?int $scoid = null) {
$this->id = $id;
$this->userid = $userid;
$this->attempt = $attempt;
$this->reporttype = $reporttype;
$this->mode = $mode;
$this->scoid = $scoid;
}
/**
* Provide data for the template
*
* @param renderer_base $output renderer_base object.
* @return array data for the template.
*/
public function export_for_template(renderer_base $output): array {
$data = [
'backurl' => (new moodle_url('/mod/scorm/report.php', ['id' => $this->id, 'mode' => $this->mode]))->out(false)
];
if (!$this->scoid) {
$learnobjects = new moodle_url('/mod/scorm/report/userreport.php',
['id' => $this->id, 'user' => $this->userid, 'attempt' => $this->attempt, 'mode' => $this->mode]);
$interactions = new moodle_url('/mod/scorm/report/userreportinteractions.php',
['id' => $this->id, 'user' => $this->userid, 'attempt' => $this->attempt, 'mode' => $this->mode]);
$reportmenu[$learnobjects->out(false)] = get_string('scoes', 'scorm');
$reportmenu[$interactions->out(false)] = get_string('interactions', 'scorm');
if ($this->reporttype === 'learning') {
$userreporturl = $learnobjects->out(false);
} else {
$userreporturl = $interactions->out(false);
}
$urlselect = new url_select($reportmenu, $userreporturl, [], 'userscormreport');
$data ['userreport'] = $urlselect->export_for_template($output);
$data ['heading'] = $reportmenu[$userreporturl] ?? null;
}
return $data;
}
}
@@ -0,0 +1,35 @@
<?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/>.
/**
* Subplugin info class.
*
* @package mod_scorm
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\plugininfo;
use core\plugininfo\base;
defined('MOODLE_INTERNAL') || die();
class scormreport extends base {
public function is_uninstall_allowed() {
return true;
}
}
+380
View File
@@ -0,0 +1,380 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_scorm\privacy;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/mod/scorm/locallib.php");
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\helper;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
/**
* Privacy class for requesting user data.
*
* @package mod_scorm
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\plugin\provider {
/**
* Return the fields which contain personal data.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('scorm_attempt', [
'userid' => 'privacy:metadata:userid',
'attempt' => 'privacy:metadata:attempt',
], 'privacy:metadata:scorm_attempt');
$collection->add_database_table('scorm_aicc_session', [
'userid' => 'privacy:metadata:userid',
'scormmode' => 'privacy:metadata:aicc_session:scormmode',
'scormstatus' => 'privacy:metadata:aicc_session:scormstatus',
'attempt' => 'privacy:metadata:attempt',
'lessonstatus' => 'privacy:metadata:aicc_session:lessonstatus',
'sessiontime' => 'privacy:metadata:aicc_session:sessiontime',
'timecreated' => 'privacy:metadata:aicc_session:timecreated',
'timemodified' => 'privacy:metadata:timemodified',
], 'privacy:metadata:scorm_aicc_session');
$collection->add_external_location_link('aicc', [
'data' => 'privacy:metadata:aicc:data'
], 'privacy:metadata:aicc:externalpurpose');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$sql = "SELECT ctx.id
FROM {%s} ss
JOIN {modules} m
ON m.name = 'scorm'
JOIN {course_modules} cm
ON cm.instance = ss.scormid
AND cm.module = m.id
JOIN {context} ctx
ON ctx.instanceid = cm.id
AND ctx.contextlevel = :modlevel
WHERE ss.userid = :userid";
$params = ['modlevel' => CONTEXT_MODULE, 'userid' => $userid];
$contextlist = new contextlist();
$contextlist->add_from_sql(sprintf($sql, 'scorm_attempt'), $params);
$contextlist->add_from_sql(sprintf($sql, 'scorm_aicc_session'), $params);
return $contextlist;
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!is_a($context, \context_module::class)) {
return;
}
$sql = "SELECT ss.userid
FROM {%s} ss
JOIN {modules} m
ON m.name = 'scorm'
JOIN {course_modules} cm
ON cm.instance = ss.scormid
AND cm.module = m.id
JOIN {context} ctx
ON ctx.instanceid = cm.id
AND ctx.contextlevel = :modlevel
WHERE ctx.id = :contextid";
$params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id];
$userlist->add_from_sql('userid', sprintf($sql, 'scorm_attempt'), $params);
$userlist->add_from_sql('userid', sprintf($sql, 'scorm_aicc_session'), $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
// Remove contexts different from COURSE_MODULE.
$contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
if ($context->contextlevel == CONTEXT_MODULE) {
$carry[] = $context->id;
}
return $carry;
}, []);
if (empty($contexts)) {
return;
}
$user = $contextlist->get_user();
$userid = $user->id;
// Get SCORM data.
foreach ($contexts as $contextid) {
$context = \context::instance_by_id($contextid);
$data = helper::get_context_data($context, $user);
writer::with_context($context)->export_data([], $data);
helper::export_context_files($context, $user);
}
// Get scoes_track data.
list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
$sql = "SELECT v.id,
a.attempt,
e.element,
v.value,
v.timemodified,
ctx.id as contextid
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 = a.scormid
JOIN {context} ctx
ON ctx.instanceid = cm.id
WHERE ctx.id $insql
AND a.userid = :userid";
$params = array_merge($inparams, ['userid' => $userid]);
$alldata = [];
$scoestracks = $DB->get_recordset_sql($sql, $params);
foreach ($scoestracks as $track) {
$alldata[$track->contextid][$track->attempt][] = (object)[
'element' => $track->element,
'value' => $track->value,
'timemodified' => transform::datetime($track->timemodified),
];
}
$scoestracks->close();
// The scoes_track data is organised in: {Course name}/{SCORM activity name}/{My attempts}/{Attempt X}/data.json
// where X is the attempt number.
array_walk($alldata, function($attemptsdata, $contextid) {
$context = \context::instance_by_id($contextid);
array_walk($attemptsdata, function($data, $attempt) use ($context) {
$subcontext = [
get_string('myattempts', 'scorm'),
get_string('attempt', 'scorm'). " $attempt"
];
writer::with_context($context)->export_data(
$subcontext,
(object)['scoestrack' => $data]
);
});
});
// Get aicc_session data.
$sql = "SELECT ss.id,
ss.scormmode,
ss.scormstatus,
ss.attempt,
ss.lessonstatus,
ss.sessiontime,
ss.timecreated,
ss.timemodified,
ctx.id as contextid
FROM {scorm_aicc_session} ss
JOIN {course_modules} cm
ON cm.instance = ss.scormid
JOIN {context} ctx
ON ctx.instanceid = cm.id
WHERE ctx.id $insql
AND ss.userid = :userid";
$params = array_merge($inparams, ['userid' => $userid]);
$alldata = [];
$aiccsessions = $DB->get_recordset_sql($sql, $params);
foreach ($aiccsessions as $aiccsession) {
$alldata[$aiccsession->contextid][] = (object)[
'scormmode' => $aiccsession->scormmode,
'scormstatus' => $aiccsession->scormstatus,
'lessonstatus' => $aiccsession->lessonstatus,
'attempt' => $aiccsession->attempt,
'sessiontime' => $aiccsession->sessiontime,
'timecreated' => transform::datetime($aiccsession->timecreated),
'timemodified' => transform::datetime($aiccsession->timemodified),
];
}
$aiccsessions->close();
// The aicc_session data is organised in: {Course name}/{SCORM activity name}/{My AICC sessions}/data.json
// In this case, the attempt hasn't been included in the json file because it can be null.
array_walk($alldata, function($data, $contextid) {
$context = \context::instance_by_id($contextid);
$subcontext = [
get_string('myaiccsessions', 'scorm')
];
writer::with_context($context)->export_data(
$subcontext,
(object)['sessions' => $data]
);
});
}
/**
* Delete all user data which matches the specified context.
*
* @param context $context A user context.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
// This should not happen, but just in case.
if ($context->contextlevel != CONTEXT_MODULE) {
return;
}
// Prepare SQL to gather all IDs to delete.
$sql = "SELECT ss.id
FROM {%s} ss
JOIN {modules} m
ON m.name = 'scorm'
JOIN {course_modules} cm
ON cm.instance = ss.scormid
AND cm.module = m.id
WHERE cm.id = :cmid";
$params = ['cmid' => $context->instanceid];
static::delete_data('scorm_aicc_session', $sql, $params);
$coursemodule = get_coursemodule_from_id('scorm', $context->instanceid);
scorm_delete_tracks($coursemodule->instance);
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
// Remove contexts different from COURSE_MODULE.
$contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
if ($context->contextlevel == CONTEXT_MODULE) {
$carry[] = $context->id;
}
return $carry;
}, []);
if (empty($contextids)) {
return;
}
$userid = $contextlist->get_user()->id;
// Prepare SQL to gather all completed IDs.
list($insql, $inparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$sql = "SELECT ss.id
FROM {%s} ss
JOIN {modules} m
ON m.name = 'scorm'
JOIN {course_modules} cm
ON cm.instance = ss.scormid
AND cm.module = m.id
JOIN {context} ctx
ON ctx.instanceid = cm.id
WHERE ss.userid = :userid
AND ctx.id $insql";
$params = array_merge($inparams, ['userid' => $userid]);
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);
}
}
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
if (!is_a($context, \context_module::class)) {
return;
}
// Prepare SQL to gather all completed IDs.
$userids = $userlist->get_userids();
list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$sql = "SELECT ss.id
FROM {%s} ss
JOIN {modules} m
ON m.name = 'scorm'
JOIN {course_modules} cm
ON cm.instance = ss.scormid
AND cm.module = m.id
JOIN {context} ctx
ON ctx.instanceid = cm.id
WHERE ctx.id = :contextid
AND ss.userid $insql";
$params = array_merge($inparams, ['contextid' => $context->id]);
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);
}
}
/**
* Delete data from $tablename with the IDs returned by $sql query.
*
* @param string $tablename Table name where executing the SQL query.
* @param string $sql SQL query for getting the IDs of the scoestrack entries to delete.
* @param array $params SQL params for the query.
*/
protected static function delete_data(string $tablename, string $sql, array $params) {
global $DB;
$scoestracksids = $DB->get_fieldset_sql(sprintf($sql, $tablename), $params);
if (!empty($scoestracksids)) {
list($insql, $inparams) = $DB->get_in_or_equal($scoestracksids, SQL_PARAMS_NAMED);
$DB->delete_records_select($tablename, "id $insql", $inparams);
}
}
}
+113
View File
@@ -0,0 +1,113 @@
<?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/>.
/**
* @package mod_scorm
* @author Ankit Kumar Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm;
/*******************************************************************/
// Default class for Scorm plugins
//
// Doesn't do anything on it's own -- it needs to be extended.
// This class displays scorm reports. Because it is called from
// within /mod/scorm/report.php you can assume that the page header
// and footer are taken care of.
//
// This file can refer to itself as report.php to pass variables
// to itself - all these will also be globally available.
/*******************************************************************/
defined('MOODLE_INTERNAL') || die();
class report {
/**
* displays the full report
* @param stdClass $scorm full SCORM object
* @param stdClass $cm - full course_module object
* @param stdClass $course - full course object
* @param string $download - type of download being requested
*/
public function display($scorm, $cm, $course, $download) {
// This function just displays the report.
return true;
}
/**
* allows the plugin to control who can see this plugin.
* @return boolean
*/
public function canview($contextmodule) {
return true;
}
/**
* Generates a checkbox that can be added to header tables to select/deselect all quiz attempts.
*
* @return string
*/
protected function generate_master_checkbox(): string {
global $OUTPUT;
// Build the select/deselect all control.
$selectalltext = get_string('selectall', 'scorm');
$deselectalltext = get_string('selectnone', 'scorm');
$mastercheckbox = new \core\output\checkbox_toggleall('scorm-attempts', true, [
'name' => 'scorm-selectall-attempts',
'value' => 1,
'label' => $selectalltext,
'labelclasses' => 'accesshide',
'selectall' => $selectalltext,
'deselectall' => $deselectalltext,
]);
return $OUTPUT->render($mastercheckbox);
}
/**
* Generates a checkbox for a row in the attempts table.
*
* @param string $name The checkbox's name attribute.
* @param string $value The checkbox's value.
* @return string
*/
protected function generate_row_checkbox(string $name, string $value): string {
global $OUTPUT;
$checkbox = new \core\output\checkbox_toggleall('scorm-attempts', false, [
'name' => $name,
'value' => $value,
]);
return $OUTPUT->render($checkbox);
}
/**
* Generates an action button that deletes the selected attempts.
*/
protected function generate_delete_selected_button(): string {
$deleteselectedparams = array(
'type' => 'submit',
'value' => get_string('deleteselected', 'scorm'),
'class' => 'btn btn-secondary',
'data-action' => 'toggle',
'data-togglegroup' => 'scorm-attempts',
'data-toggle' => 'action',
'disabled' => true
);
return \html_writer::empty_tag('input', $deleteselectedparams);
}
}
+55
View File
@@ -0,0 +1,55 @@
<?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/>.
/**
* Search area for mod_scorm activities.
*
* @package mod_scorm
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\search;
defined('MOODLE_INTERNAL') || die();
/**
* Search area for mod_scorm activities.
*
* @package mod_scorm
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity extends \core_search\base_activity {
/**
* Returns true if this area uses file indexing.
*
* @return bool
*/
public function uses_file_indexing() {
return true;
}
/**
* Return the fileareas that should be indexed.
*
* @return array
*/
public function get_search_fileareas() {
return ['intro', 'content'];
}
}
+53
View File
@@ -0,0 +1,53 @@
<?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/>.
/**
* A scheduled task for scorm cron.
*
* @package mod_scorm
* @copyright 2017 Abhishek kumar <ganitgenius@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_scorm\task;
defined('MOODLE_INTERNAL') || die();
/**
* A cron_task class to be used by Tasks API.
*
* @copyright 2017 Abhishek kumar <ganitgenius@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cron_task extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('crontask', 'mod_scorm');
}
/**
* Run scorm cron.
*/
public function execute() {
global $CFG;
require_once($CFG->dirroot . '/mod/scorm/lib.php');
scorm_cron_scheduled_task();
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_scorm\task;
/**
* Adhoc task for recalculating grades.
*
* @package mod_scorm
* @copyright 2022 Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class update_grades extends \core\task\adhoc_task {
/**
* Execute task.
*/
public function execute() {
global $CFG, $DB;
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$data = $this->get_custom_data();
$scorm = $DB->get_record('scorm', ['id' => $data->scormid]);
scorm_update_grades($scorm, $data->userid);
}
}
+106
View File
@@ -0,0 +1,106 @@
<?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/>.
require_once('../../config.php');
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$id = optional_param('id', '', PARAM_INT); // Course Module ID, or
$a = optional_param('a', '', PARAM_INT); // scorm ID
$scoid = required_param('scoid', PARAM_INT); // sco ID
$attempt = required_param('attempt', PARAM_INT); // attempt number.
if (!empty($id)) {
if (! $cm = get_coursemodule_from_id('scorm', $id)) {
throw new \moodle_exception('invalidcoursemodule');
}
if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
throw new \moodle_exception('coursemisconf');
}
if (! $scorm = $DB->get_record("scorm", array("id" => $cm->instance))) {
throw new \moodle_exception('invalidcoursemodule');
}
} else if (!empty($a)) {
if (! $scorm = $DB->get_record("scorm", array("id" => $a))) {
throw new \moodle_exception('invalidcoursemodule');
}
if (! $course = $DB->get_record("course", array("id" => $scorm->course))) {
throw new \moodle_exception('coursemisconf');
}
if (! $cm = get_coursemodule_from_instance("scorm", $scorm->id, $course->id)) {
throw new \moodle_exception('invalidcoursemodule');
}
} else {
throw new \moodle_exception('missingparameter');
}
$PAGE->set_url('/mod/scorm/datamodel.php', array('scoid' => $scoid, 'attempt' => $attempt, 'id' => $cm->id));
require_login($course, false, $cm);
if (confirm_sesskey() && (!empty($scoid))) {
$result = true;
$request = null;
if (has_capability('mod/scorm:savetrack', context_module::instance($cm->id))) {
// Preload all current tracking data.
$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, $attemptobject, $element, $value,
$scorm->forcecompleted, $trackdata) && $result;
}
if (substr($element, 0, 15) == 'adl.nav.request') {
// SCORM 2004 Sequencing Request.
require_once($CFG->dirroot.'/mod/scorm/datamodels/scorm_13lib.php');
$search = array('@continue@', '@previous@', '@\{target=(\S+)\}choice@', '@exit@',
'@exitAll@', '@abandon@', '@abandonAll@');
$replace = array('continue_', 'previous_', '\1', 'exit_', 'exitall_', 'abandon_', 'abandonall');
$action = preg_replace($search, $replace, $value);
if ($action != $value) {
// Evaluating navigation request.
$valid = scorm_seq_overall ($scoid, $USER->id, $action, $attempt);
$valid = 'true';
// Set valid request.
$search = array('@continue@', '@previous@', '@\{target=(\S+)\}choice@');
$replace = array('true', 'true', 'true');
$matched = preg_replace($search, $replace, $value);
if ($matched == 'true') {
$request = 'adl.nav.request_valid["'.$action.'"] = "'.$valid.'";';
}
}
}
}
}
if ($result) {
echo "true\n0";
} else {
echo "false\n101";
}
if ($request != null) {
echo "\n".$request;
}
}
+561
View File
@@ -0,0 +1,561 @@
// 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 1.2 API Implementation
//
function AICCapi(def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, viewmode, currentorg, sesskey, cmid, autocommit) {
var prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "&currentorg=" + currentorg + "&sesskey=" + sesskey;
var datamodelurl = cfgwwwroot + "/mod/scorm/datamodel.php";
var datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid;
// Standard Data Type Definition
CMIString256 = '^.{0,255}$';
CMIString4096 = '^.{0,4096}$';
CMITime = '^([0-2]{1}[0-9]{1}):([0-5]{1}[0-9]{1}):([0-5]{1}[0-9]{1})(\.[0-9]{1,2})?$';
CMITimespan = '^([0-9]{2,4}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,2})?$';
CMIInteger = '^\\d+$';
CMISInteger = '^-?([0-9]+)$';
CMIDecimal = '^-?([0-9]{0,3})(\.[0-9]{1,2})?$';
CMIIdentifier = '^\\w{1,255}$';
CMIFeedback = CMIString256; // This must be redefined
CMIIndex = '[._](\\d+).';
// Vocabulary Data Type Definition
CMIStatus = '^passed$|^completed$|^failed$|^incomplete$|^browsed$';
CMIStatus2 = '^passed$|^completed$|^failed$|^incomplete$|^browsed$|^not attempted$';
CMIExit = '^time-out$|^suspend$|^logout$|^$';
CMIType = '^true-false$|^choice$|^fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$';
CMIResult = '^correct$|^wrong$|^unanticipated$|^neutral$|^([0-9]{0,3})?(\.[0-9]{1,2})?$';
NAVEvent = '^previous$|^continue$';
// Children lists
cmi_children = 'core, suspend_data, launch_data, comments, objectives, student_data, student_preference, interactions';
core_children = 'student_id, student_name, lesson_location, credit, lesson_status, entry, score, total_time, lesson_mode, exit, session_time';
score_children = 'raw, min, max';
comments_children = 'content, location, time';
objectives_children = 'id, score, status';
student_data_children = 'attempt_number, tries, mastery_score, max_time_allowed, time_limit_action';
student_preference_children = 'audio, language, speed, text';
interactions_children = 'id, objectives, time, type, correct_responses, weighting, student_response, result, latency';
// Data ranges
score_range = '0#100';
audio_range = '-1#100';
speed_range = '-100#100';
weighting_range = '-100#100';
text_range = '-1#1';
// The AICC data model
var datamodel = {};
for(scoid in def){
datamodel[scoid] = {
'cmi._children':{'defaultvalue':cmi_children, 'mod':'r', 'writeerror':'402'},
'cmi._version':{'defaultvalue':'3.4', 'mod':'r', 'writeerror':'402'},
'cmi.core._children':{'defaultvalue':core_children, 'mod':'r', 'writeerror':'402'},
'cmi.core.student_id':{'defaultvalue':def[scoid]['cmi.core.student_id'], 'mod':'r', 'writeerror':'403'},
'cmi.core.student_name':{'defaultvalue':def[scoid]['cmi.core.student_name'], 'mod':'r', 'writeerror':'403'},
'cmi.core.lesson_location':{'defaultvalue':def[scoid]['cmi.core.lesson_location'], 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
'cmi.core.credit':{'defaultvalue':def[scoid]['cmi.core.credit'], 'mod':'r', 'writeerror':'403'},
'cmi.core.lesson_status':{'defaultvalue':def[scoid]['cmi.core.lesson_status'], 'format':CMIStatus, 'mod':'rw', 'writeerror':'405'},
'cmi.core.exit':{'defaultvalue':def[scoid]['cmi.core.exit'], 'format':CMIExit, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.core.entry':{'defaultvalue':def[scoid]['cmi.core.entry'], 'mod':'r', 'writeerror':'403'},
'cmi.core.score._children':{'defaultvalue':score_children, 'mod':'r', 'writeerror':'402'},
'cmi.core.score.raw':{'defaultvalue':def[scoid]['cmi.core.score.raw'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.core.score.max':{'defaultvalue':def[scoid]['cmi.core.score.max'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.core.score.min':{'defaultvalue':def[scoid]['cmi.core.score.min'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.core.session_time':{'format':CMITimespan, 'mod':'w', 'defaultvalue':'00:00:00', 'readerror':'404', 'writeerror':'405'},
'cmi.core.total_time':{'defaultvalue':def[scoid]['cmi.core.total_time'], 'mod':'r', 'writeerror':'403'},
'cmi.core.lesson_mode':{'defaultvalue':def[scoid]['cmi.core.lesson_mode'], 'mod':'r', 'writeerror':'403'},
'cmi.suspend_data':{'defaultvalue':def[scoid]['cmi.suspend_data'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'},
'cmi.launch_data':{'defaultvalue':def[scoid]['cmi.launch_data'], 'mod':'r', 'writeerror':'403'},
'cmi.comments':{'defaultvalue':def[scoid]['cmi.comments'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'},
// deprecated evaluation attributes
'cmi.evaluation.comments._count':{'defaultvalue':'0', 'mod':'r', 'writeerror':'402'},
'cmi.evaluation.comments._children':{'defaultvalue':comments_children, 'mod':'r', 'writeerror':'402'},
'cmi.evaluation.comments.n.content':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
'cmi.evaluation.comments.n.location':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
'cmi.evaluation.comments.n.time':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMITime, 'mod':'rw', 'writeerror':'405'},
'cmi.comments_from_lms':{'mod':'r', 'writeerror':'403'},
'cmi.objectives._children':{'defaultvalue':objectives_children, 'mod':'r', 'writeerror':'402'},
'cmi.objectives._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
'cmi.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'rw', 'writeerror':'405'},
'cmi.objectives.n.score._children':{'pattern':CMIIndex, 'mod':'r', 'writeerror':'402'},
'cmi.objectives.n.score.raw':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.objectives.n.score.min':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.objectives.n.score.max':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.objectives.n.status':{'pattern':CMIIndex, 'format':CMIStatus2, 'mod':'rw', 'writeerror':'405'},
'cmi.student_data._children':{'defaultvalue':student_data_children, 'mod':'r', 'writeerror':'402'},
'cmi.student_data.attempt_number':{'defaultvalue':def[scoid]['cmi.student_data.attempt_number'], 'mod':'r', 'writeerror':'402'},
'cmi.student_data.tries.n.score.raw':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.student_data.tries.n.score.min':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.student_data.tries.n.score.max':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.student_data.tries.n.status':{'pattern':CMIIndex, 'format':CMIStatus2, 'mod':'rw', 'writeerror':'405'},
'cmi.student_data.tries.n.time':{'pattern':CMIIndex, 'format':CMITime, 'mod':'rw', 'writeerror':'405'},
'cmi.student_data.mastery_score':{'defaultvalue':def[scoid]['cmi.student_data.mastery_score'], 'mod':'r', 'writeerror':'403'},
'cmi.student_data.max_time_allowed':{'defaultvalue':def[scoid]['cmi.student_data.max_time_allowed'], 'mod':'r', 'writeerror':'403'},
'cmi.student_data.time_limit_action':{'defaultvalue':def[scoid]['cmi.student_data.time_limit_action'], 'mod':'r', 'writeerror':'403'},
'cmi.student_data.tries_during_lesson':{'defaultvalue':def[scoid]['cmi.student_data.tries_during_lesson'], 'mod':'r', 'writeerror':'402'},
'cmi.student_preference._children':{'defaultvalue':student_preference_children, 'mod':'r', 'writeerror':'402'},
'cmi.student_preference.audio':{'defaultvalue':'0', 'format':CMISInteger, 'range':audio_range, 'mod':'rw', 'writeerror':'405'},
'cmi.student_preference.language':{'defaultvalue':'', 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
'cmi.student_preference.speed':{'defaultvalue':'0', 'format':CMISInteger, 'range':speed_range, 'mod':'rw', 'writeerror':'405'},
'cmi.student_preference.text':{'defaultvalue':'0', 'format':CMISInteger, 'range':text_range, 'mod':'rw', 'writeerror':'405'},
'cmi.interactions._children':{'defaultvalue':interactions_children, 'mod':'r', 'writeerror':'402'},
'cmi.interactions._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
'cmi.interactions.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.objectives._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
'cmi.interactions.n.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.time':{'pattern':CMIIndex, 'format':CMITime, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.type':{'pattern':CMIIndex, 'format':CMIType, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.correct_responses._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
'cmi.interactions.n.correct_responses.n.pattern':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.weighting':{'pattern':CMIIndex, 'format':CMIDecimal, 'range':weighting_range, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.student_response':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.result':{'pattern':CMIIndex, 'format':CMIResult, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.latency':{'pattern':CMIIndex, 'format':CMITimespan, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'nav.event':{'defaultvalue':'', 'format':NAVEvent, 'mod':'w', 'readerror':'404', 'writeerror':'405'}
};
}
var cmi, nav;
function initdatamodel(scoid){
prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "&currentorg=" + currentorg + "&sesskey=" + sesskey;
datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid;
//
// Datamodel inizialization
//
cmi = new Object();
cmi.core = new Object();
cmi.core.score = new Object();
cmi.objectives = new Object();
cmi.student_data = new Object();
cmi.student_preference = new Object();
cmi.interactions = new Object();
// deprecated evaluation attributes
cmi.evaluation = new Object();
cmi.evaluation.comments = new Object();
// Navigation Object
nav = new Object();
for (element in datamodel[scoid]) {
if (element.match(/\.n\./) == null) {
if (typeof datamodel[scoid][element].defaultvalue != 'undefined') {
eval(element + ' = datamodel["' + scoid + '"]["' + element + '"].defaultvalue;');
} else {
eval(element + ' = "";');
}
}
}
eval(cmiobj[scoid]);
if (cmi.core.lesson_status == '') {
cmi.core.lesson_status = 'not attempted';
}
}
//
// API Methods definition
//
var Initialized = false;
function LMSInitialize (param) {
scoid = scorm_current_node ? scorm_current_node.scoid : scoid;
initdatamodel(scoid);
errorCode = "0";
if (param == "") {
if (!Initialized) {
Initialized = true;
errorCode = "0";
return "true";
} else {
errorCode = "101";
}
} else {
errorCode = "201";
}
return "false";
}
function LMSFinish (param) {
errorCode = "0";
if (param == "") {
if (Initialized) {
Initialized = false;
result = StoreData(cmi,true);
if (nav.event != '') {
if (nav.event == 'continue') {
setTimeout('mod_scorm_launch_next_sco();',500);
} else {
setTimeout('mod_scorm_launch_prev_sco();',500);
}
} else {
if (scormauto == 1) {
setTimeout('mod_scorm_launch_next_sco();',500);
}
}
// trigger TOC update
var callback = M.mod_scorm.connectPrereqCallback;
YUI().use('io-base', function(Y) {
Y.on('io:complete', callback.success, Y);
Y.io(prerequrl);
});
return "true";
} else {
errorCode = "301";
}
} else {
errorCode = "201";
}
return "false";
}
function LMSGetValue (element) {
errorCode = "0";
if (Initialized) {
if (element != "") {
expression = new RegExp(CMIIndex,'g');
elementmodel = String(element).replace(expression,'.n.');
if (typeof datamodel[scoid][elementmodel] != "undefined") {
if (datamodel[scoid][elementmodel].mod != 'w') {
element = String(element).replace(expression, "_$1.");
elementIndexes = element.split('.');
subelement = 'cmi';
i = 1;
while ((i < elementIndexes.length) && (typeof eval(subelement) != "undefined")) {
subelement += '.' + elementIndexes[i++];
}
if (subelement == element) {
errorCode = "0";
return eval(element);
} else {
errorCode = "0"; // Need to check if it is the right errorCode
}
} else {
errorCode = datamodel[scoid][elementmodel].readerror;
}
} else {
childrenstr = '._children';
countstr = '._count';
if (elementmodel.substr(elementmodel.length - childrenstr.length,elementmodel.length) == childrenstr) {
parentmodel = elementmodel.substr(0,elementmodel.length - childrenstr.length);
if (typeof datamodel[scoid][parentmodel] != "undefined") {
errorCode = "202";
} else {
errorCode = "201";
}
} else if (elementmodel.substr(elementmodel.length - countstr.length,elementmodel.length) == countstr) {
parentmodel = elementmodel.substr(0,elementmodel.length - countstr.length);
if (typeof datamodel[scoid][parentmodel] != "undefined") {
errorCode = "203";
} else {
errorCode = "201";
}
} else {
errorCode = "201";
}
}
} else {
errorCode = "201";
}
} else {
errorCode = "301";
}
return "";
}
function LMSSetValue (element,value) {
errorCode = "0";
if (Initialized) {
if (element != "") {
expression = new RegExp(CMIIndex,'g');
elementmodel = String(element).replace(expression,'.n.');
if (typeof datamodel[scoid][elementmodel] != "undefined") {
if (datamodel[scoid][elementmodel].mod != 'r') {
expression = new RegExp(datamodel[scoid][elementmodel].format);
value = value + '';
matches = value.match(expression);
if (matches != null) {
//Create dynamic data model element
if (element != elementmodel) {
elementIndexes = element.split('.');
subelement = 'cmi';
for (i = 1; i < elementIndexes.length - 1; i++) {
elementIndex = elementIndexes[i];
if (elementIndexes[i + 1].match(/^\d+$/)) {
if ((typeof eval(subelement + '.' + elementIndex)) == "undefined") {
eval(subelement + '.' + elementIndex + ' = new Object();');
eval(subelement + '.' + elementIndex + '._count = 0;');
}
if (elementIndexes[i + 1] == eval(subelement + '.' + elementIndex + '._count')) {
eval(subelement + '.' + elementIndex + '._count++;');
}
if (elementIndexes[i + 1] > eval(subelement + '.' + elementIndex + '._count')) {
errorCode = "201";
}
subelement = subelement.concat('.' + elementIndex + '_' + elementIndexes[i + 1]);
i++;
} else {
subelement = subelement.concat('.' + elementIndex);
}
if ((typeof eval(subelement)) == "undefined") {
eval(subelement + ' = new Object();');
if (subelement.substr(0,14) == 'cmi.objectives') {
eval(subelement + '.score = new Object();');
eval(subelement + '.score._children = score_children;');
eval(subelement + '.score.raw = "";');
eval(subelement + '.score.min = "";');
eval(subelement + '.score.max = "";');
}
if (subelement.substr(0,16) == 'cmi.interactions') {
eval(subelement + '.objectives = new Object();');
eval(subelement + '.objectives._count = 0;');
eval(subelement + '.correct_responses = new Object();');
eval(subelement + '.correct_responses._count = 0;');
}
}
}
element = subelement.concat('.' + elementIndexes[elementIndexes.length - 1]);
}
// Store data.
if (errorCode == "0") {
if (autocommit && !(AICCapi.timeout)) {
AICCapi.timeout = Y.later(60000, API, 'LMSCommit', [""], false);
}
if (typeof datamodel[scoid][elementmodel].range != "undefined") {
range = datamodel[scoid][elementmodel].range;
ranges = range.split('#');
value = value * 1.0;
if ((value >= ranges[0]) && (value <= ranges[1])) {
eval(element + '="' + value + '";');
errorCode = "0";
return "true";
} else {
errorCode = datamodel[scoid][elementmodel].writeerror;
}
} else {
if (element == 'cmi.comments') {
eval(element + '+="' + value + '";');
} else {
eval(element + '="' + value + '";');
}
errorCode = "0";
return "true";
}
}
} else {
errorCode = datamodel[scoid][elementmodel].writeerror;
}
} else {
errorCode = datamodel[scoid][elementmodel].writeerror;
}
} else {
errorCode = "201"
}
} else {
errorCode = "201";
}
} else {
errorCode = "301";
}
return "false";
}
function LMSCommit (param) {
if (AICCapi.timeout) {
AICCapi.timeout.cancel();
AICCapi.timeout = null;
}
errorCode = "0";
if (param == "") {
if (Initialized) {
result = StoreData(cmi,false);
return "true";
} else {
errorCode = "301";
}
} else {
errorCode = "201";
}
return "false";
}
function LMSGetLastError () {
return errorCode;
}
function LMSGetErrorString (param) {
if (param != "") {
var errorString = new Array();
errorString["0"] = "No error";
errorString["101"] = "General exception";
errorString["201"] = "Invalid argument error";
errorString["202"] = "Element cannot have children";
errorString["203"] = "Element not an array - cannot have count";
errorString["301"] = "Not initialized";
errorString["401"] = "Not implemented error";
errorString["402"] = "Invalid set value, element is a keyword";
errorString["403"] = "Element is read only";
errorString["404"] = "Element is write only";
errorString["405"] = "Incorrect data type";
return errorString[param];
} else {
return "";
}
}
function LMSGetDiagnostic (param) {
if (param == "") {
param = errorCode;
}
return param;
}
function AddTime (first, second) {
var sFirst = first.split(":");
var sSecond = second.split(":");
var cFirst = sFirst[2].split(".");
var cSecond = sSecond[2].split(".");
var change = 0;
FirstCents = 0; //Cents
if (cFirst.length > 1) {
FirstCents = parseInt(cFirst[1],10);
}
SecondCents = 0;
if (cSecond.length > 1) {
SecondCents = parseInt(cSecond[1],10);
}
var cents = FirstCents + SecondCents;
change = Math.floor(cents / 100);
cents = cents - (change * 100);
if (Math.floor(cents) < 10) {
cents = "0" + cents.toString();
}
var secs = parseInt(cFirst[0],10) + parseInt(cSecond[0],10) + change; //Seconds
change = Math.floor(secs / 60);
secs = secs - (change * 60);
if (Math.floor(secs) < 10) {
secs = "0" + secs.toString();
}
mins = parseInt(sFirst[1],10) + parseInt(sSecond[1],10) + change; //Minutes
change = Math.floor(mins / 60);
mins = mins - (change * 60);
if (mins < 10) {
mins = "0" + mins.toString();
}
hours = parseInt(sFirst[0],10) + parseInt(sSecond[0],10) + change; //Hours
if (hours < 10) {
hours = "0" + hours.toString();
}
if (cents != '0') {
return hours + ":" + mins + ":" + secs + '.' + cents;
} else {
return hours + ":" + mins + ":" + secs;
}
}
function TotalTime() {
total_time = AddTime(cmi.core.total_time, cmi.core.session_time);
return '&' + underscore('cmi.core.total_time') + '=' + escape(total_time);
}
function CollectData(data,parent) {
var datastring = '';
for (property in data) {
if (typeof data[property] == 'object') {
datastring += CollectData(data[property],parent + '.' + property);
} else {
element = parent + '.' + property;
expression = new RegExp(CMIIndex,'g');
elementmodel = String(element).replace(expression,'.n.');
if (typeof datamodel[scoid][elementmodel] != "undefined") {
if (datamodel[scoid][elementmodel].mod != 'r') {
elementstring = '&' + underscore(element) + '=' + escape(data[property]);
if (typeof datamodel[scoid][elementmodel].defaultvalue != "undefined") {
if (datamodel[scoid][elementmodel].defaultvalue != data[property]) {
datastring += elementstring;
}
} else {
datastring += elementstring;
}
}
}
}
}
return datastring;
}
function StoreData(data,storetotaltime) {
var datastring = '';
if (storetotaltime) {
if (cmi.core.lesson_mode == 'normal') {
if (cmi.core.credit == 'credit') {
if (cmi.student_data.mastery_score != '' && cmi.core.score.raw != '') {
if (cmi.core.score.raw >= cmi.student_data.mastery_score) {
cmi.core.lesson_status = 'passed';
} else {
cmi.core.lesson_status = 'failed';
}
}
}
}
if (cmi.core.lesson_mode == 'browse') {
if (datamodel[scoid]['cmi.core.lesson_status'].defaultvalue == '' && cmi.core.lesson_status == 'not attempted') {
cmi.core.lesson_status = 'browsed';
}
}
datastring = CollectData(data,'cmi');
datastring += TotalTime();
} else {
datastring = CollectData(data,'cmi');
}
var myRequest = NewHttpReq();
var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
if (result === false) {
return false;
}
results = String(result).split('\n');
errorCode = results[1];
return results[0];
}
this.LMSInitialize = LMSInitialize;
this.LMSFinish = LMSFinish;
this.LMSGetValue = LMSGetValue;
this.LMSSetValue = LMSSetValue;
this.LMSCommit = LMSCommit;
this.LMSGetLastError = LMSGetLastError;
this.LMSGetErrorString = LMSGetErrorString;
this.LMSGetDiagnostic = LMSGetDiagnostic;
}
M.scorm_api = {};
M.scorm_api.init = function(Y, def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, viewmode, currentorg, sesskey, cmid, autocommit) {
window.API = new AICCapi(def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, viewmode, currentorg, sesskey, cmid, autocommit);
}
+66
View File
@@ -0,0 +1,66 @@
<?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/>.
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$userdata = new stdClass();
$def = new stdClass();
$cmiobj = new stdClass();
if (!isset($currentorg)) {
$currentorg = '';
}
if ($scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'sortorder, id')) {
// Drop keys so that it is a simple array.
$scoes = array_values($scoes);
foreach ($scoes as $sco) {
$def->{($sco->id)} = new stdClass();
$userdata->{($sco->id)} = new stdClass();
$def->{($sco->id)} = get_scorm_default($userdata->{($sco->id)}, $scorm, $sco->id, $attempt, $mode);
// Reconstitute objectives, comments_from_learner and comments_from_lms.
$cmiobj->{($sco->id)} = '';
$currentobj = '';
$count = 0;
foreach ($userdata as $element => $value) {
if (substr($element, 0, 14) == 'cmi.objectives') {
$element = preg_replace('/\.(\d+)\./', "_\$1.", $element);
preg_match('/\_(\d+)\./', $element, $matches);
if (count($matches) > 0 && $currentobj != $matches[1]) {
$currentobj = $matches[1];
$count++;
$end = strpos($element, $matches[1]) + strlen($matches[1]);
$subelement = substr($element, 0, $end);
$cmiobj->{($sco->id)} .= ' '.$subelement." = new Object();\n";
$cmiobj->{($sco->id)} .= ' '.$subelement.".score = new Object();\n";
$cmiobj->{($sco->id)} .= ' '.$subelement.".score._children = score_children;\n";
$cmiobj->{($sco->id)} .= ' '.$subelement.".score.raw = '';\n";
$cmiobj->{($sco->id)} .= ' '.$subelement.".score.min = '';\n";
$cmiobj->{($sco->id)} .= ' '.$subelement.".score.max = '';\n";
}
$cmiobj->{($sco->id)} .= ' '.$element.' = \''.$value."';\n";
}
}
if ($count > 0) {
$cmiobj->{($sco->id)} .= ' cmi.objectives._count = '.$count.";\n";
}
}
}
$PAGE->requires->js_init_call('M.scorm_api.init', array($def, $cmiobj, $scorm->auto, $CFG->wwwroot, $scorm->id, $scoid,
$attempt, $mode, $currentorg, sesskey(), $id, $scorm->autocommit));
+575
View File
@@ -0,0 +1,575 @@
<?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/>.
/**
* functions used by AICC packages.
*
* @package mod_scorm
* @copyright 1999 onwards Roberto Pinna
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
function scorm_add_time($a, $b) {
$aes = explode(':', $a);
$bes = explode(':', $b);
$aseconds = explode('.', $aes[2]);
$bseconds = explode('.', $bes[2]);
$change = 0;
$acents = 0; // Cents.
if (count($aseconds) > 1) {
$acents = $aseconds[1];
}
$bcents = 0;
if (count($bseconds) > 1) {
$bcents = $bseconds[1];
}
$cents = $acents + $bcents;
$change = floor($cents / 100);
$cents = $cents - ($change * 100);
if (floor($cents) < 10) {
$cents = '0'. $cents;
}
$secs = $aseconds[0] + $bseconds[0] + $change; // Seconds.
$change = floor($secs / 60);
$secs = $secs - ($change * 60);
if (floor($secs) < 10) {
$secs = '0'. $secs;
}
$mins = $aes[1] + $bes[1] + $change; // Minutes.
$change = floor($mins / 60);
$mins = $mins - ($change * 60);
if ($mins < 10) {
$mins = '0' . $mins;
}
$hours = $aes[0] + $bes[0] + $change; // Hours.
if ($hours < 10) {
$hours = '0' . $hours;
}
if ($cents != '0') {
return $hours . ":" . $mins . ":" . $secs . '.' . $cents;
} else {
return $hours . ":" . $mins . ":" . $secs;
}
}
/**
* Take the header row of an AICC definition file
* and returns sequence of columns and a pointer to
* the sco identifier column.
*
* @param string $row AICC header row
* @param string $mastername AICC sco identifier column
* @return mixed
*/
function scorm_get_aicc_columns($row, $mastername='system_id') {
$tok = strtok(strtolower($row), "\",\n\r");
$result = new stdClass();
$result->columns = array();
$result->mastercol = 0;
$i = 0;
while ($tok) {
if ($tok != '') {
$result->columns[] = $tok;
if ($tok == $mastername) {
$result->mastercol = $i;
}
$i++;
}
$tok = strtok("\",\n\r");
}
return $result;
}
/**
* Given a colums array return a string containing the regular
* expression to match the columns in a text row.
*
* @param array $column The header columns
* @param string $remodule The regular expression module for a single column
* @return string
*/
function scorm_forge_cols_regexp($columns, $remodule='(".*")?,') {
$regexp = '/^';
foreach ($columns as $column) {
$regexp .= $remodule;
}
$regexp = substr($regexp, 0, -1) . '/';
return $regexp;
}
/**
* Sets up AICC packages
* Called whenever package changes
* @param object $scorm instance - fields are updated and changes saved into database
* @return bool
*/
function scorm_parse_aicc(&$scorm) {
global $DB;
if ($scorm->scormtype == SCORM_TYPE_AICCURL) {
return scorm_aicc_generate_simple_sco($scorm);
}
if (!isset($scorm->cmid)) {
$cm = get_coursemodule_from_instance('scorm', $scorm->id);
$scorm->cmid = $cm->id;
}
$context = context_module::instance($scorm->cmid);
$fs = get_file_storage();
$files = $fs->get_area_files($context->id, 'mod_scorm', 'content', 0, 'sortorder, itemid, filepath, filename', false);
$version = 'AICC';
$ids = array();
$courses = array();
$extaiccfiles = array('crs', 'des', 'au', 'cst', 'ort', 'pre', 'cmp');
foreach ($files as $file) {
$filename = $file->get_filename();
$ext = substr($filename, strrpos($filename, '.'));
$extension = strtolower(substr($ext, 1));
if (in_array($extension, $extaiccfiles)) {
$id = strtolower(basename($filename, $ext));
if (!isset($ids[$id])) {
$ids[$id] = new stdClass();
}
$ids[$id]->$extension = $file;
}
}
foreach ($ids as $courseid => $id) {
if (!isset($courses[$courseid])) {
$courses[$courseid] = new stdClass();
}
if (isset($id->crs)) {
$contents = $id->crs->get_content();
$rows = explode("\r\n", $contents);
if (is_array($rows)) {
foreach ($rows as $row) {
if (preg_match("/^(.+)=(.+)$/", $row, $matches)) {
switch (strtolower(trim($matches[1]))) {
case 'course_id':
$courses[$courseid]->id = trim($matches[2]);
break;
case 'course_title':
$courses[$courseid]->title = trim($matches[2]);
break;
case 'version':
$courses[$courseid]->version = 'AICC_'.trim($matches[2]);
break;
}
}
}
}
}
if (isset($id->des)) {
$contents = $id->des->get_content();
$rows = explode("\r\n", $contents);
$columns = scorm_get_aicc_columns($rows[0]);
$regexp = scorm_forge_cols_regexp($columns->columns);
for ($i = 1; $i < count($rows); $i++) {
if (preg_match($regexp, $rows[$i], $matches)) {
for ($j = 0; $j < count($columns->columns); $j++) {
$column = $columns->columns[$j];
if (!isset($courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1 , -1)])) {
$courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1 , -1)] = new stdClass();
}
$courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1 , -1)]->$column = substr(trim($matches[$j + 1]), 1, -1);
}
}
}
}
if (isset($id->au)) {
$contents = $id->au->get_content();
$rows = explode("\r\n", $contents);
$columns = scorm_get_aicc_columns($rows[0]);
$regexp = scorm_forge_cols_regexp($columns->columns);
for ($i = 1; $i < count($rows); $i++) {
if (preg_match($regexp, $rows[$i], $matches)) {
for ($j = 0; $j < count($columns->columns); $j++) {
$column = $columns->columns[$j];
$courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1, -1)]->$column = substr(trim($matches[$j + 1]), 1, -1);
}
}
}
}
if (isset($id->cst)) {
$contents = $id->cst->get_content();
$rows = explode("\r\n", $contents);
$columns = scorm_get_aicc_columns($rows[0], 'block');
$regexp = scorm_forge_cols_regexp($columns->columns, '(.+)?,');
for ($i = 1; $i < count($rows); $i++) {
if (preg_match($regexp, $rows[$i], $matches)) {
for ($j = 0; $j < count($columns->columns); $j++) {
if ($j != $columns->mastercol) {
$element = substr(trim($matches[$j + 1]), 1 , -1);
if (!empty($element)) {
$courses[$courseid]->elements[$element]->parent = substr(trim($matches[$columns->mastercol + 1]), 1, -1);
}
}
}
}
}
}
if (isset($id->ort)) {
$contents = $id->ort->get_content();
$rows = explode("\r\n", $contents);
$columns = scorm_get_aicc_columns($rows[0], 'course_element');
$regexp = scorm_forge_cols_regexp($columns->columns, '(.+)?,');
for ($i = 1; $i < count($rows); $i++) {
if (preg_match($regexp, $rows[$i], $matches)) {
for ($j = 0; $j < count($matches) - 1; $j++) {
if ($j != $columns->mastercol) {
$courses[$courseid]->elements[substr(trim($matches[$j + 1]), 1, -1)]->parent = substr(trim($matches[$columns->mastercol + 1]), 1, -1);
}
}
}
}
}
if (isset($id->pre)) {
$contents = $id->pre->get_content();
$rows = explode("\r\n", $contents);
$columns = scorm_get_aicc_columns($rows[0], 'structure_element');
$regexp = scorm_forge_cols_regexp($columns->columns, '(.+),');
for ($i = 1; $i < count($rows); $i++) {
if (preg_match($regexp, $rows[$i], $matches)) {
$elementid = trim($matches[$columns->mastercol + 1]);
$elementid = trim(trim($elementid, '"'), "'"); // Remove any quotes.
$prereq = trim($matches[2 - $columns->mastercol]);
$prereq = trim(trim($prereq, '"'), "'"); // Remove any quotes.
$courses[$courseid]->elements[$elementid]->prerequisites = $prereq;
}
}
}
if (isset($id->cmp)) {
$contents = $id->cmp->get_content();
$rows = explode("\r\n", $contents);
}
}
$oldscoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id));
$sortorder = 0;
$launch = 0;
if (isset($courses)) {
foreach ($courses as $course) {
$sortorder++;
$sco = new stdClass();
$sco->identifier = $course->id;
$sco->scorm = $scorm->id;
$sco->organization = '';
$sco->title = $course->title;
$sco->parent = '/';
$sco->launch = '';
$sco->scormtype = '';
$sco->sortorder = $sortorder;
if ($ss = $DB->get_record('scorm_scoes', array('scorm' => $scorm->id,
'identifier' => $sco->identifier))) {
$id = $ss->id;
$sco->id = $id;
$DB->update_record('scorm_scoes', $sco);
unset($oldscoes[$id]);
} else {
$id = $DB->insert_record('scorm_scoes', $sco);
}
if ($launch == 0) {
$launch = $id;
}
if (isset($course->elements)) {
foreach ($course->elements as $element) {
unset($sco);
$sco = new stdClass();
$sco->identifier = $element->system_id;
$sco->scorm = $scorm->id;
$sco->organization = $course->id;
$sco->title = $element->title;
if (!isset($element->parent)) {
$sco->parent = '/';
} else if (strtolower($element->parent) == 'root') {
$sco->parent = $course->id;
} else {
$sco->parent = $element->parent;
}
$sco->launch = '';
$sco->scormtype = '';
$sco->previous = 0;
$sco->next = 0;
$id = null;
// Is it an Assignable Unit (AU)?
if (isset($element->file_name)) {
$sco->launch = $element->file_name;
$sco->scormtype = 'sco';
}
if ($oldscoid = scorm_array_search('identifier', $sco->identifier, $oldscoes)) {
$sco->id = $oldscoid;
$DB->update_record('scorm_scoes', $sco);
$id = $oldscoid;
$DB->delete_records('scorm_scoes_data', array('scoid' => $oldscoid));
unset($oldscoes[$oldscoid]);
} else {
$id = $DB->insert_record('scorm_scoes', $sco);
}
if (!empty($id)) {
$scodata = new stdClass();
$scodata->scoid = $id;
if (isset($element->web_launch)) {
$scodata->name = 'parameters';
$scodata->value = $element->web_launch;
$dataid = $DB->insert_record('scorm_scoes_data', $scodata);
}
if (isset($element->prerequisites)) {
$scodata->name = 'prerequisites';
$scodata->value = $element->prerequisites;
$dataid = $DB->insert_record('scorm_scoes_data', $scodata);
}
if (isset($element->max_time_allowed)) {
$scodata->name = 'max_time_allowed';
$scodata->value = $element->max_time_allowed;
$dataid = $DB->insert_record('scorm_scoes_data', $scodata);
}
if (isset($element->time_limit_action)) {
$scodata->name = 'time_limit_action';
$scodata->value = $element->time_limit_action;
$dataid = $DB->insert_record('scorm_scoes_data', $scodata);
}
if (isset($element->mastery_score)) {
$scodata->name = 'mastery_score';
$scodata->value = $element->mastery_score;
$dataid = $DB->insert_record('scorm_scoes_data', $scodata);
}
if (isset($element->core_vendor)) {
$scodata->name = 'datafromlms';
$scodata->value = preg_replace('/<cr>/i', "\r\n", $element->core_vendor);
$dataid = $DB->insert_record('scorm_scoes_data', $scodata);
}
}
if ($launch == 0) {
$launch = $id;
}
}
}
}
}
if (!empty($oldscoes)) {
foreach ($oldscoes as $oldsco) {
scorm_delete_tracks($scorm->id, $oldsco->id);
$DB->delete_records('scorm_scoes', ['id' => $oldsco->id]);
}
}
// Find first launchable object.
$sqlselect = 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true);
// We use get_records here as we need to pass a limit in the query that works cross db.
$scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id), 'sortorder', 'id', 0, 1);
if (!empty($scoes)) {
$sco = reset($scoes); // We only care about the first record - the above query only returns one.
$scorm->launch = $sco->id;
} else {
$scorm->launch = $launch;
}
$scorm->version = 'AICC';
return true;
}
/**
* Given a scormid creates an AICC Session record to allow HACP
*
* @param int $scormid - id from scorm table
* @return string hacpsession
*/
function scorm_aicc_get_hacp_session($scormid) {
global $USER, $DB, $SESSION;
$cfgscorm = get_config('scorm');
if (empty($cfgscorm->allowaicchacp)) {
return false;
}
$now = time();
$hacpsession = $SESSION->scorm;
$hacpsession->scormid = $scormid;
$hacpsession->hacpsession = random_string(20);
$hacpsession->userid = $USER->id;
$hacpsession->timecreated = $now;
$hacpsession->timemodified = $now;
$DB->insert_record('scorm_aicc_session', $hacpsession);
return $hacpsession->hacpsession;
}
/**
* Check the hacp_session for whether it is valid.
*
* @param string $hacpsession The hacpsession value to check (optional). Normally leave this blank
* and this function will do required_param('sesskey', ...).
* @return mixed - false if invalid, otherwise returns record from scorm_aicc_session table.
*/
function scorm_aicc_confirm_hacp_session($hacpsession) {
global $DB;
$cfgscorm = get_config('scorm');
if (empty($cfgscorm->allowaicchacp)) {
return false;
}
$time = time() - ($cfgscorm->aicchacptimeout * 60);
$sql = "hacpsession = ? AND timemodified > ?";
$hacpsession = $DB->get_record_select('scorm_aicc_session', $sql, array($hacpsession, $time));
if (!empty($hacpsession)) { // Update timemodified as this is still an active session - resets the timeout.
$hacpsession->timemodified = time();
$DB->update_record('scorm_aicc_session', $hacpsession);
}
return $hacpsession;
}
/**
* generate a simple single activity AICC object
* structure to wrap around and externally linked
* AICC package URL
*
* @param object $scorm package record
*/
function scorm_aicc_generate_simple_sco($scorm) {
global $DB;
// Find the oldest one.
$scos = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'id');
if (!empty($scos)) {
$sco = array_shift($scos);
} else {
$sco = new stdClass();
}
// Get rid of old ones.
foreach ($scos as $oldsco) {
scorm_delete_tracks($scorm->id, $oldsco->id);
$DB->delete_records('scorm_scoes', ['id' => $oldsco->id]);
}
$sco->identifier = 'A1';
$sco->scorm = $scorm->id;
$sco->organization = '';
$sco->title = $scorm->name;
$sco->parent = '/';
// Add the HACP signal to the activity launcher.
if (preg_match('/\?/', $scorm->reference)) {
$sco->launch = $scorm->reference.'&CMI=HACP';
} else {
$sco->launch = $scorm->reference.'?CMI=HACP';
}
$sco->scormtype = 'sco';
if (isset($sco->id)) {
$DB->update_record('scorm_scoes', $sco);
$id = $sco->id;
} else {
$id = $DB->insert_record('scorm_scoes', $sco);
}
return $id;
}
/**
* Sets up $userdata array and default values for AICC package.
*
* @param stdClass $userdata an empty stdClass variable that should be set up with user values
* @param object $scorm package record
* @param string $scoid SCO Id
* @param string $attempt attempt number for the user
* @param string $mode scorm display mode type
* @return array The default values that should be used for AICC package
*/
function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) {
global $USER;
$aiccuserid = get_config('scorm', 'aiccuserid');
if (!empty($aiccuserid)) {
$userdata->student_id = $USER->id;
} else {
$userdata->student_id = $USER->username;
}
$userdata->student_name = $USER->lastname .', '. $USER->firstname;
if ($usertrack = scorm_get_tracks($scoid, $USER->id, $attempt)) {
foreach ($usertrack as $key => $value) {
$userdata->$key = $value;
}
} else {
$userdata->status = '';
$userdata->score_raw = '';
}
if ($scodatas = scorm_get_sco($scoid, SCO_DATA)) {
foreach ($scodatas as $key => $value) {
$userdata->$key = $value;
}
} else {
throw new \moodle_exception('cannotfindsco', 'scorm');
}
if (!$sco = scorm_get_sco($scoid)) {
throw new \moodle_exception('cannotfindsco', 'scorm');
}
$userdata->mode = 'normal';
if (!empty($mode)) {
$userdata->mode = $mode;
}
if ($userdata->mode == 'normal') {
$userdata->credit = 'credit';
} else {
$userdata->credit = 'no-credit';
}
if (isset($userdata->status)) {
if ($userdata->status == '') {
$userdata->entry = 'ab-initio';
} else {
if (isset($userdata->{'cmi.core.exit'}) && ($userdata->{'cmi.core.exit'} == 'suspend')) {
$userdata->entry = 'resume';
} else {
$userdata->entry = '';
}
}
}
$def = array();
$def['cmi.core.student_id'] = $userdata->student_id;
$def['cmi.core.student_name'] = $userdata->student_name;
$def['cmi.core.credit'] = $userdata->credit;
$def['cmi.core.entry'] = $userdata->entry;
$def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms');
$def['cmi.core.lesson_mode'] = $userdata->mode;
$def['cmi.student_data.attempt_number'] = scorm_isset($userdata, 'cmi.student_data.attempt_number');
$def['cmi.student_data.mastery_score'] = scorm_isset($userdata, 'mastery_score');
$def['cmi.student_data.max_time_allowed'] = scorm_isset($userdata, 'max_time_allowed');
$def['cmi.student_data.time_limit_action'] = scorm_isset($userdata, 'time_limit_action');
$def['cmi.student_data.tries_during_lesson'] = scorm_isset($userdata, 'cmi.student_data.tries_during_lesson');
$def['cmi.core.lesson_location'] = scorm_isset($userdata, 'cmi.core.lesson_location');
$def['cmi.core.lesson_status'] = scorm_isset($userdata, 'cmi.core.lesson_status');
$def['cmi.core.exit'] = scorm_isset($userdata, 'cmi.core.exit');
$def['cmi.core.score.raw'] = scorm_isset($userdata, 'cmi.core.score.raw');
$def['cmi.core.score.max'] = scorm_isset($userdata, 'cmi.core.score.max');
$def['cmi.core.score.min'] = scorm_isset($userdata, 'cmi.core.score.min');
$def['cmi.core.total_time'] = scorm_isset($userdata, 'cmi.core.total_time', '00:00:00');
$def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data');
$def['cmi.comments'] = scorm_isset($userdata, 'cmi.comments');
return $def;
}
+775
View File
@@ -0,0 +1,775 @@
<?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/>.
echo html_writer::start_tag('script');
?>
<!--// hopefully fool ie IE proof way of getting DOM element
function safeGetElement(doc, el) {
return doc.ids ? doc.ids[el] : doc.getElementById ? doc.getElementById(el) : doc.all[el];
}
// Find elements by class name
var aryClassElements = new Array();
function getNextElementByClassName( strClassName, obj ) {
if ( obj.className == strClassName ) {
aryClassElements[aryClassElements.length] = obj;
}
for ( var i = 0; i < obj.childNodes.length; i++ )
getNextElementByClassName( strClassName, obj.childNodes[i] );
}
function getElementsByClassName( strClassName, obj ) {
aryClassElements = new Array();
getNextElementByClassName( strClassName, obj );
if (aryClassElements.length > 0) {
return aryClassElements[0];
}
else {
return null;
}
}
// retrieve cookie data
function getCookie (cookie_name){
var results = document.cookie.match ( '(^|;) ?' + cookie_name + '=([^;]*)(;|$)' );
if ( results ) {
return (unescape(results[2]));
} else {
return null;
}
}
// retrieve the logging flag from a Cookie
function getLoggingActive () {
var loggingActive = getCookie('SCORMLoggingActive');
if (!loggingActive) {
loggingActive = 'A';
}
return loggingActive;
}
// set the logging flag in a cookie
function setLoggingActive (flag) {
new cookie("SCORMLoggingActive", flag, 365, "/").set();
}
// toggle the logging
function toggleLog () {
if (getLoggingActive() == "A") {
AppendToLog("Moodle Logging Deactivated", 0);
setLoggingActive('N');
logButton.innerHTML = '-><?php echo addslashes_js(get_string('scormloggingoff', 'scorm')); ?>';
} else {
setLoggingActive('A');
AppendToLog("Moodle Logging Activated", 0);
logButton.innerHTML = '<?php echo addslashes_js(get_string('scormloggingon', 'scorm')); ?>';
logPopUpWindow.focus();
}
}
// globals for the log accumulation
var logString = "";
var logRow = 0;
var logPopUpWindow = "N";
var debugSCORMVersion = '<?php echo $scorm->version; ?>';
<?php
$lmsprefix = (scorm_version_check($scorm->version, SCORM_12) || empty($scorm->version)) ? 'LMS' : '';
$lmsapi = (scorm_version_check($scorm->version, SCORM_12) || empty($scorm->version)) ? 'API' : 'API_1484_11';
$lmselements = array();
if (scorm_version_check($scorm->version, SCORM_12) || empty($scorm->version)) {
$lmselements = array( 'cmi.core._children',
'cmi.core.student_id',
'cmi.core.student_name',
'cmi.core.lesson_location',
'cmi.core.credit',
'cmi.core.lesson_status',
'cmi.core.entry',
'cmi.core._children',
'cmi.core.score.raw',
'cmi.core.score.max',
'cmi.core.score.min',
'cmi.core.total_time',
'cmi.core.lesson_mode',
'cmi.core.exit',
'cmi.core.session_time',
'cmi.suspend_data',
'cmi.launch_data',
'cmi.comments',
'cmi.comments_from_lms',
'cmi.objectives._count',
'cmi.objectives._children',
'cmi.objectives.n.id',
'cmi.objectives.n.score._children',
'cmi.objectives.n.score.raw',
'cmi.objectives.n.score.min',
'cmi.objectives.n.score.max',
'cmi.objectives.n.status',
'cmi.student_data._children',
'cmi.student_data.mastery_score',
'cmi.student_data.max_time_allowed',
'cmi.student_data.time_limit_action',
'cmi.student_preference._children',
'cmi.student_preference.audio',
'cmi.student_preference.language',
'cmi.student_preference.speed',
'cmi.student_preference.text',
'cmi.interactions._children',
'cmi.interactions._count',
'cmi.interactions.n.id',
'cmi.interactions.n.objectives._count',
'cmi.interactions.n.objectives.m.id',
'cmi.interactions.n.time',
'cmi.interactions.n.type',
'cmi.interactions.n.correct_responses._count',
'cmi.interactions.n.correct_responses.m.pattern',
'cmi.interactions.n.weighting',
'cmi.interactions.n.student_response',
'cmi.interactions.n.result',
'cmi.interactions.n.latency');
} else {
$lmselements = array( 'cmi._children',
'cmi._version',
'cmi.learner_id',
'cmi.learner_name',
'cmi.location',
'cmi.completion_status',
'cmi.completion_threshold',
'cmi.scaled_passing_score',
'cmi.progressive_measure',
'cmi.score._children',
'cmi.score.raw',
'cmi.score.max',
'cmi.score.min',
'cmi.score.scaled',
'cmi.total_time',
'cmi.time_limit_action',
'cmi.max_time_allowed',
'cmi.session_time',
'cmi.success_status',
'cmi.lesson_mode',
'cmi.entry',
'cmi.exit',
'cmi.credit',
'cmi.mode',
'cmi.suspend_data',
'cmi.launch_data',
'cmi.comments',
'cmi.comments_from_lms._children',
'cmi.comments_from_lms._count',
'cmi.comments_from_lms.n.comment',
'cmi.comments_from_lms.n.location',
'cmi.comments_from_lms.n.timestamp',
'cmi.comments_from_learner._children',
'cmi.comments_from_learner._count',
'cmi.comments_from_learner.n.comment',
'cmi.comments_from_learner.n.location',
'cmi.comments_from_learner.n.timestamp',
'cmi.objectives._count',
'cmi.objectives._children',
'cmi.objectives.n.id',
'cmi.objectives.n.score._children',
'cmi.objectives.n.score.raw',
'cmi.objectives.n.score.min',
'cmi.objectives.n.score.max',
'cmi.objectives.n.score.scaled',
'cmi.objectives.n.success_status',
'cmi.objectives.n.completion_status',
'cmi.objectives.n.progress_measure',
'cmi.objectives.n.description',
'cmi.student_data._children',
'cmi.student_data.mastery_score',
'cmi.student_data.max_time_allowed',
'cmi.student_data.time_limit_action',
'cmi.student_preference._children',
'cmi.student_preference.audio',
'cmi.student_preference.language',
'cmi.student_preference.speed',
'cmi.student_preference.text',
'cmi.interactions._children',
'cmi.interactions._count',
'cmi.interactions.n.id',
'cmi.interactions.n.objectives._count',
'cmi.interactions.n.objectives.m.id',
'cmi.interactions.n.time',
'cmi.interactions.n.type',
'cmi.interactions.n.correct_responses._count',
'cmi.interactions.n.correct_responses.m.pattern',
'cmi.interactions.n.weighting',
'cmi.interactions.n.learner_response',
'cmi.interactions.n.result',
'cmi.interactions.n.latency',
'cmi.interactions.n.description',
'adl.nav.request');
}
?>
// add each entry to the log, or setup the log pane first time round
// The code written into the header is based on the ADL test suite API interaction code
// and various examples of test wrappers out in the community
function UpdateLog(s) {
var s1 = '<html><head><style>\n'
+ 'body {font-family: Arial, Helvetica, Sans-Serif;font-size: xx-small;'
+ 'margin: 0px 0px 0px 0px; padding: 0px 0px 0px 0px; '
+ 'background-color: ffffff;}\n'
+ '.even {background-color: ffffff; width: 100%;}\n'
+ '.odd {background-color: e8f2fe; width: 100%;}\n'
+ '.error {background-color: ffffff; color: red; width: 100%;}\n'
+ '<\/style>'
+ '<script>\n'
+ 'var LMSVersion = \'<?php echo $scorm->version; ?>\';\n'
+ ' \n'
+ 'function checkLMSVersion() { \n'
+ ' if (this.document.body.childNodes.length > 0) { \n'
+ ' if (this.document.body.lastChild.id == LMSVersion) { \n'
+ ' return true; \n'
+ ' } \n'
+ ' }; \n'
+ ' alert(\'LMS Version: \' + this.document.body.lastChild.id + \n'
+ ' \' does not equal: \' + LMSVersion + \n'
+ ' \' so API calls will fail - did navigate to another SCORM package?\'); \n'
+ ' return false; \n'
+ '} \n'
+ ' \n'
+ 'var saveElement = ""; \n'
+ 'function setAPIValue() { \n'
+ ' document.elemForm.API_ELEMENT.value = document.elemForm.ELEMENT_LIST.value; \n'
+ ' saveElement = document.elemForm.API_ELEMENT.value; \n'
+ '} \n'
+ ' \n'
+ 'var _Debug = false; // set this to false to turn debugging off \n'
+ ' \n'
+ '// Define exception/error codes \n'
+ 'var _NoError = 0; \n'
+ 'var _GeneralException = 101; \n'
+ 'var _ServerBusy = 102; \n'
+ 'var _InvalidArgumentError = 201; \n'
+ 'var _ElementCannotHaveChildren = 202; \n'
+ 'var _ElementIsNotAnArray = 203; \n'
+ 'var _NotInitialized = 301; \n'
+ 'var _NotImplementedError = 401; \n'
+ 'var _InvalidSetValue = 402; \n'
+ 'var _ElementIsReadOnly = 403; \n'
+ 'var _ElementIsWriteOnly = 404; \n'
+ 'var _IncorrectDataType = 405; \n'
+ ' \n'
+ '// local variable definitions \n'
+ 'var apiHandle = null; \n'
+ 'var API = null; \n'
+ 'var findAPITries = 0; \n'
+ ' \n'
+ ' \n'
+ 'function doLMSInitialize() { \n'
+ ' checkLMSVersion(); \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nLMSInitialize was not successful."); \n'
+ ' return "false"; \n'
+ ' } \n'
+ ' var result = api.<?php echo $lmsprefix; ?>Initialize(""); \n'
+ ' if (result.toString() != "true") { \n'
+ ' var err = ErrorHandler(); \n'
+ ' } \n'
+ ' return result.toString(); \n'
+ '} \n'
+ ' \n'
+ 'function doLMSFinish() { \n'
+ ' checkLMSVersion(); \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nLMSFinish was not successful."); \n'
+ ' return "false"; \n'
+ ' } else { \n'
+ ' // call the LMSFinish function that should be implemented by the API \n'
+ ' var result = api.<?php echo $lmsprefix; ?>Finish(""); \n'
+ ' if (result.toString() != "true") { \n'
+ ' var err = ErrorHandler(); \n'
+ ' } \n'
+ ' } \n'
+ ' return result.toString(); \n'
+ '} \n'
+ ' \n'
+ 'function doLMSTerminate() { \n'
+ ' checkLMSVersion(); \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nTerminate was not successful."); \n'
+ ' return "false"; \n'
+ ' } else { \n'
+ ' // call the Terminate function that should be implemented by the API \n'
+ ' var result = api.Terminate(""); \n'
+ ' if (result.toString() != "true") { \n'
+ ' var err = ErrorHandler(); \n'
+ ' } \n'
+ ' } \n'
+ ' return result.toString(); \n'
+ '} \n'
+ ' \n'
+ 'function doLMSGetValue(name) { \n'
+ ' checkLMSVersion(); \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nLMSGetValue was not successful."); \n'
+ ' return ""; \n'
+ ' } else { \n'
+ ' var value = api.<?php echo $lmsprefix; ?>GetValue(name); \n'
+ ' var errCode = api.<?php echo $lmsprefix; ?>GetLastError().toString(); \n'
+ ' if (errCode != _NoError) { \n'
+ ' // an error was encountered so display the error description \n'
+ ' var errDescription = api.<?php echo $lmsprefix; ?>GetErrorString(errCode); \n'
+ ' alert("<?php echo $lmsprefix; ?>GetValue("+name+") failed. \\n"+ errDescription); \n'
+ ' return ""; \n'
+ ' } else { \n'
+ ' return value.toString(); \n'
+ ' } \n'
+ ' } \n'
+ '} \n'
+ ' \n'
+ 'function doLMSSetValue(name, value) { \n'
+ ' checkLMSVersion(); \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nLMSSetValue was not successful."); \n'
+ ' return; \n'
+ ' } else { \n'
+ ' var result = api.<?php echo $lmsprefix; ?>SetValue(name, value); \n'
+ ' if (result.toString() != "true") { \n'
+ ' var err = ErrorHandler(); \n'
+ ' } \n'
+ ' } \n'
+ ' return; \n'
+ '} \n'
+ ' \n'
+ 'function doLMSCommit() { \n'
+ ' checkLMSVersion(); \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nLMSCommit was not successful."); \n'
+ ' return "false"; \n'
+ ' } else { \n'
+ ' var result = api.<?php echo $lmsprefix; ?>Commit(""); \n'
+ ' if (result != "true") { \n'
+ ' var err = ErrorHandler(); \n'
+ ' } \n'
+ ' } \n'
+ ' return result.toString(); \n'
+ '} \n'
+ ' \n'
+ 'function doLMSGetLastError() { \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nLMSGetLastError was not successful."); \n'
+ ' //since we can\'t get the error code from the LMS, return a general error \n'
+ ' return _GeneralError; \n'
+ ' } \n'
+ ' return api.<?php echo $lmsprefix; ?>GetLastError().toString(); \n'
+ '} \n'
+ ' \n'
+ 'function doLMSGetErrorString(errorCode) { \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nLMSGetErrorString was not successful."); \n'
+ ' } \n'
+ ' return api.<?php echo $lmsprefix; ?>GetErrorString(errorCode).toString(); \n'
+ '} \n'
+ ' \n'
+ 'function doLMSGetDiagnostic(errorCode) { \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nLMSGetDiagnostic was not successful."); \n'
+ ' } \n'
+ ' return api.<?php echo $lmsprefix; ?>GetDiagnostic(errorCode).toString(); \n'
+ '} \n'
+ ' \n'
+ 'function LMSIsInitialized() { \n'
+ ' // there is no direct method for determining if the LMS API is initialized \n'
+ ' // for example an LMSIsInitialized function defined on the API so we\'ll try \n'
+ ' // a simple LMSGetValue and trap for the LMS Not Initialized Error \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nLMSIsInitialized() failed."); \n'
+ ' return false; \n'
+ ' } else { \n'
+ ' var value = api.<?php echo $lmsprefix; ?>GetValue("cmi.core.student_name"); \n'
+ ' var errCode = api.<?php echo $lmsprefix; ?>GetLastError().toString(); \n'
+ ' if (errCode == _NotInitialized) { \n'
+ ' return false; \n'
+ ' } else { \n'
+ ' return true; \n'
+ ' } \n'
+ ' } \n'
+ '} \n'
+ ' \n'
+ 'function ErrorHandler() { \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\nCannot determine LMS error code."); \n'
+ ' return; \n'
+ ' } \n'
+ ' // check for errors caused by or from the LMS \n'
+ ' var errCode = api.<?php echo $lmsprefix; ?>GetLastError().toString(); \n'
+ ' if (errCode != _NoError) { \n'
+ ' // an error was encountered so display the error description \n'
+ ' var errDescription = api.<?php echo $lmsprefix; ?>GetErrorString(errCode); \n'
+ ' if (_Debug == true) { \n'
+ ' errDescription += "\\n"; \n'
+ ' errDescription += api.<?php echo $lmsprefix; ?>GetDiagnostic(null); \n'
+ ' // by passing null to LMSGetDiagnostic, we get any available diagnostics \n'
+ ' // on the previous error. \n'
+ ' } \n'
+ ' alert(errDescription); \n'
+ ' } \n'
+ ' return errCode; \n'
+ '} \n'
+ ' \n'
+ 'function getAPIHandle() { \n'
+ ' if (apiHandle == null) { \n'
+ ' apiHandle = getAPI(); \n'
+ ' } \n'
+ ' return apiHandle; \n'
+ '} \n'
+ ' \n'
+ 'function findAPI(win) { \n'
+ ' while ((win.<?php echo $lmsapi; ?> == null) && (win.parent != null) && (win.parent != win)) { \n'
+ ' findAPITries++; \n'
+ ' // Note: 7 is an arbitrary number, but should be more than sufficient \n'
+ ' if (findAPITries > 7) { \n'
+ ' alert("Error finding API -- too deeply nested."); \n'
+ ' return null; \n'
+ ' } \n'
+ ' win = win.parent; \n'
+ ' } \n'
+ ' return win.<?php echo $lmsapi; ?>; \n'
+ '} \n'
+ ' \n'
+ 'function getAPI() { \n'
+ ' var theAPI = findAPI(window); \n'
+ ' if ((theAPI == null) && (window.opener != null) && (typeof(window.opener) != "undefined")) { \n'
+ ' theAPI = findAPI(window.opener); \n'
+ ' } \n'
+ ' if (theAPI == null) { \n'
+ ' alert("Unable to find an API adapter"); \n'
+ ' } \n'
+ ' return theAPI \n'
+ '} \n'
+ ' \n'
+ ' function tryLMSInitialize() { \n'
+ ' var result = doLMSInitialize(); \n'
+ ' var msg; \n'
+ ' if(result == "true") { \n'
+ ' msg = "<?php echo $lmsprefix; ?>Initialize Successful!"; \n'
+ ' } else { \n'
+ ' var err = doLMSGetLastError(); \n'
+ ' var errString = doLMSGetErrorString(err); \n'
+ ' msg = "<?php echo $lmsprefix; ?>Initialize Failed! Error Code: "+err; \n'
+ ' msg += " Error Description: " + errString; \n'
+ ' } \n'
+ ' document.initForm.msgtxt.value= msg; \n'
+ ' } \n'
+ ' \n'
+ ' function tryLMSCommit() { \n'
+ ' var result = doLMSCommit(); \n'
+ ' var msg; \n'
+ ' if(result == "true") { \n'
+ ' msg = "<?php echo $lmsprefix; ?>Commit was Successful!"; \n'
+ ' } else { \n'
+ ' var err = doLMSGetLastError(); \n'
+ ' var errString = doLMSGetErrorString(err); \n'
+ ' var msg = "<?php echo $lmsprefix; ?>Commit Failed! Error Code: "+err; \n'
+ ' msg += " Error Description: " + errString; \n'
+ ' } \n'
+ ' document.otherForm.msgtxt.value = msg; \n'
+ ' document.elemForm.API_ELEMENT.value = saveElement; \n'
+ ' } \n'
+ ' \n'
+ ' function tryLMSFinish() { \n'
+ ' // set now, in case the SCO is unloaded on LMSFinish \n'
+ ' doLMSSetValue("cmi.core.lesson_status", "completed"); \n'
+ ' doLMSSetValue("cmi.core.exit", ""); \n'
+ ' doLMSSetValue("cmi.core.session_time", "00:00:30"); \n'
+ ' var result = doLMSFinish(); \n'
+ ' var msg; \n'
+ ' if(result == "true") { \n'
+ ' msg = "LMSFinish Successful!"; \n'
+ ' document.otherForm.msgtxt.value = msg; \n'
+ ' } else { \n'
+ ' var err = doLMSGetLastError(); \n'
+ ' var errString = doLMSGetErrorString(err); \n'
+ ' var msg = "LMSFinish Failed! Error Code: "+err; \n'
+ ' msg += " Error Description: " + errString; \n'
+ ' document.otherForm.msgtxt.value = msg; \n'
+ ' } \n'
+ ' } \n'
+ ' \n'
+ ' function tryLMSTerminate() { \n'
+ ' var result = doLMSTerminate(); \n'
+ ' var msg; \n'
+ ' if(result == "true") { \n'
+ ' msg = "Terminate Successful!"; \n'
+ ' document.otherForm.msgtxt.value = msg; \n'
+ ' } else { \n'
+ ' var err = doLMSGetLastError(); \n'
+ ' var errString = doLMSGetErrorString(err); \n'
+ ' var msg = "Terminate Failed! Error Code: "+err; \n'
+ ' msg += " Error Description: " + errString; \n'
+ ' document.otherForm.msgtxt.value = msg; \n'
+ ' } \n'
+ ' } \n'
+ ' \n'
+ ' function tryLMSGetValue() { \n'
+ ' var value = document.elemForm.API_ELEMENT.value; \n'
+ ' var msg; \n'
+ ' var result = doLMSGetValue(value); \n'
+ ' var err = doLMSGetLastError(); \n'
+ ' var errString = doLMSGetErrorString(err); \n'
+ ' msg = "<?php echo $lmsprefix; ?>GetValue Returned: " + result; \n'
+ ' msg += "\\nError Code: " + err; \n'
+ ' msg += "\\nError Description: " + errString; \n'
+ ' document.elemForm.msgtxt.value = msg; \n'
+ ' document.elemForm.API_ELEMENT.value = saveElement; \n'
+ ' } \n'
+ ' \n'
+ ' function tryLMSSetValue() { \n'
+ ' // Get the element that is to be set \n'
+ ' var setValue = document.elemForm.SET_VAL.value; \n'
+ ' var item = document.elemForm.API_ELEMENT.value; \n'
+ ' var msg; \n'
+ ' var api = getAPIHandle(); \n'
+ ' if (api == null) { \n'
+ ' alert("Unable to locate the LMS\'s API Implementation.\\n"+ \n'
+ ' "<?php echo $lmsprefix; ?>SetValue was not successful."); \n'
+ ' return false; \n'
+ ' } \n'
+ ' // Try to set the element \n'
+ ' var result = api.<?php echo $lmsprefix; ?>SetValue( item, setValue ); \n'
+ ' var err = doLMSGetLastError(); \n'
+ ' var errString = doLMSGetErrorString(err); \n'
+ ' msg = "<?php echo $lmsprefix; ?>SetValue returned: " + result; \n'
+ ' msg += "\\nError Code: " + err; \n'
+ ' msg += "\\nError Description: " + errString; \n'
+ ' document.elemForm.msgtxt.value = msg; \n'
+ ' document.elemForm.API_ELEMENT.value = saveElement; \n'
+ ' } \n'
+ ' \n'
+ ' function tryLMSGetLastError() { \n'
+ ' var err = doLMSGetLastError(); \n'
+ ' document.otherForm.msgtxt.value = "<?php echo $lmsprefix; ?>GetLastError returned Error Code: " + err; \n'
+ ' } \n'
+ ' \n'
+ ' function tryLMSGetErrorString() { \n'
+ ' var err = doLMSGetLastError(); \n'
+ ' var errString = doLMSGetErrorString(err); \n'
+ ' document.otherForm.msgtxt.value = "<?php echo $lmsprefix; ?>GetErrorString returned: " + errString; \n'
+ ' } \n'
+ ' \n'
+ ' function tryLMSGetDiagnostic() { \n'
+ ' var err = doLMSGetLastError(); \n'
+ ' var diagnostic = doLMSGetDiagnostic(err); \n'
+ ' document.otherForm.msgtxt.value = "<?php echo $lmsprefix; ?>GetDiagnostic returned: " + diagnostic; \n'
+ ' } \n'
+ ' \n'
+ '</script>\n'
+ '<\/head><body STYLE="background-color: ffffff; color: black"'
+ 'marginwidth="0" leftmargin="0" hspace="0">'
+ '<h1>SCORM Debugging interface</h1>'
+ '<h2>SCORM Version Detected: <?php echo $scorm->version; ?></h2>'
+ '<input type="hidden" id="mod-scorm-logstate" name="mod-scorm-logstate" value="A" \/>'
+ '<form name="initForm" onsubmit="return false;">'
+ ' <table width="100%" border="0">'
+ ' <tr>'
+ ' <td>'
+ ' <input type = "button" value = "Call <?php echo $lmsprefix; ?>Initialize()"'
+ ' onclick = "tryLMSInitialize();" id="Initialize" name="Initialize" />'
+ ' </td>'
+ ' <td>'
+ ' <label>Result: </label><input type="text" name="msgtxt" id="msgtxt" size="80" readonly value="NotCalled" />'
+ ' </td>'
+ ' </tr>'
+ ' </table>'
+ '</form>'
+ '<hr />'
+ '<form name="elemForm" id="elemForm" onsubmit="return false;">'
+ ' <table width="100%" border="0">'
+ ' <tr>'
+ ' <td><b>Select Data Model Element to Get or Set</b> &nbsp;&nbsp;&nbsp;&nbsp;'
+ ' <select name = "ELEMENT_LIST" id="ELEMENT_LIST" onchange="setAPIValue()">'
+ ' <option value="NONE">--None Selected--</option>'
+ ' <option value="">******************************************</option>'
<?php
foreach ($lmselements as $element) {
echo ' + \' <option value="'.$element.'">'.$element.'</option>\\n\'';
}
?>
+ ' </select>'
+ ' <input type="text" name="API_ELEMENT" id="API_ELEMENT" size="40"><br />'
+ ' <br />'
+ ' <label><b>Select API Function to Call</b></label> &nbsp;&nbsp;&nbsp;&nbsp;'
+ ' <input type = "button" value = "<?php echo $lmsprefix; ?>GetValue()"'
+ ' onclick = "tryLMSGetValue();" id="lmsGetButton"'
+ ' name="lmsGetButton">&nbsp;&nbsp;-- OR --&nbsp;&nbsp;'
+ ' <input type="button" value="<?php echo $lmsprefix; ?>SetValue()"'
+ ' onclick="tryLMSSetValue();" id="lmsSetButton"'
+ ' name="lmsSetButton">'
+ ' <label><b>&nbsp; value to Set: </b></label>&nbsp; <input type="text" name="SET_VAL" id="SET_VAL" size="25">'
+ ' <br />'
+ ' <label>Result: </label><br />'
+ ' <textarea name="msgtxt" id="msgtxt" rows="2" cols="150" wrap="VIRTUAL" readonly>None</textarea>'
+ ' </td>'
+ ' </tr>'
+ ' </table>'
+ '</form>'
+ '<hr />'
+ '<form name="otherForm" onsubmit="return false;">'
+ ' <h3>Additional API Functions</h3>'
+ ' <table width="100%" border="0">'
+ ' <tr>'
+ ' <td><input type="button"'
+ ' value="<?php echo $lmsprefix; ?>GetLastError() "'
+ ' onclick="tryLMSGetLastError();"'
+ ' id="lastErrorButton"'
+ ' name="lastErrorButton">'
+ ' <input type="button"'
+ ' value="<?php echo $lmsprefix; ?>GetErrorString() "'
+ ' onclick="tryLMSGetErrorString();"'
+ ' id="getErrorStringButton"'
+ ' name="getErrorStringButton">'
+ ' <input type="button"'
+ ' value="<?php echo $lmsprefix; ?>GetDiagnostic() "'
+ ' onclick="tryLMSGetDiagnostic();"'
+ ' id="getDiagnosticButton"'
+ ' name="getDiagnosticButton">'
+ ' <input type="button"'
+ ' value="<?php echo $lmsprefix; ?>Commit() "'
+ ' onclick="tryLMSCommit();"'
+ ' id="commitButton"'
+ ' name="commitButton">'
+ ' <input type="button"'
+ ' value="<?php echo scorm_version_check($scorm->version, SCORM_12) ? 'LMSFinish' : 'Terminate'; ?>() "'
+ ' onclick="try'
+ ' <?php echo scorm_version_check($scorm->version, SCORM_12) ? 'LMSFinish' : 'LMSTerminate'; ?>();"'
+ ' id="finishButton"'
+ ' name="finishButton">'
+ ' </td>'
+ ' </tr>'
+ ' <tr>'
+ ' <td>'
+ ' <label>Result: </label><br />'
+ ' <textarea name="msgtxt" id="msgtxt" rows="2" cols="150" wrap="VIRTUAL" readonly>None</textarea>'
+ ' </td>'
+ ' </tr>'
+ ' </table>'
+ '</form>'
+ '<h3 id="mod-scorm-marker">SCORM API Activity Log<\/h3>';
// Is logging active?
if (getLoggingActive() != "A") {
return;
}
var popupdoc = '';
logString += s;
if (logPopUpWindow != 'N' && !logPopUpWindow.closed) {
popupdoc = logPopUpWindow.document;
popupdoc.body.innerHTML += s;
} else {
logPopUpWindow = open( '', 'scormlogpopupwindow', '' );
popupdoc = logPopUpWindow.document;
// Is logging active?
var marker = safeGetElement(popupdoc, 'mod-scorm-marker');
if (marker) {
popupdoc.body.innerHTML += s;
} else {
popupdoc.open();
popupdoc.write(s1);
popupdoc.write(logString);
popupdoc.write('<\/body><\/html>')
popupdoc.close();
popupdoc.title = 'SCORM API Activity Log';
logPopUpWindow.focus();
}
}
if (popupdoc.body && popupdoc.body.childNodes.length > 0) {
popupdoc.body.lastChild.scrollIntoView();
};
}
//add an individual log entry
function AppendToLog(s, rc) {
var sStyle = '';
if (rc != 0) {
sStyle = 'class="error"';
} else if (logRow % 2 != 0) {
sStyle = 'class="even"';
} else {
sStyle = 'class="odd"';
}
var now = new Date();
now.setTime( now.getTime() );
s = '<div ' + sStyle + ' id="<?php echo $scorm->version; ?>">' + now.toGMTString() + ': ' + s + '<\/div>';
UpdateLog(s);
// switch colours for a new section of work
if (s.match(/Commit|Loaded|Initialize|Terminate|Finish|Moodle SCORM|Moodle Logging/)) {
logRow++;
}
}
// format a log entry
function LogAPICall(func, nam, val, rc) {
// drop call to GetLastError for the time being - it produces too much chatter
if (func.match(/GetLastError/)) {
return;
}
var s = func + '("' + nam + '"';
if (val != null && ! (func.match(/GetValue|GetLastError/))) {
s += ', "' + val + '"';
}
s += ')';
if (func.match(/GetValue/)) {
s += ' - ' + val;
}
s += ' => ' + String(rc) + " scoid = " + scorm_current_node.scoid;
AppendToLog(s, rc);
<?php
if (scorm_debugging($scorm) && ($sco->scormtype == 'asset')) {
?>
hint = 'Item <?php echo $sco->identifier; ?> has been defined as an Asset: it should never call the SCORM API';
AppendToLog(hint, 101);
<?php
}
?>
}
// Add in a JS controlled link for toggling the Debug logging
if (!document.getElementById('mod-scorm-log-toggle')) {
var logButton = document.createElement('a');
logButton.id = 'mod-scorm-log-toggle';
logButton.name = 'logToggle';
logButton.href = 'javascript:toggleLog();';
if (getLoggingActive() == "A") {
logButton.innerHTML = '<?php echo addslashes_js(get_string('scormloggingon', 'scorm')); ?>';
} else {
logButton.innerHTML = '<?php echo addslashes_js(get_string('scormloggingoff', 'scorm')); ?>';
}
var content = safeGetElement(document, 'scormpage');
content.insertBefore(logButton, content.firstChild);
}
-->
<?php
echo html_writer::end_tag('script');
+678
View File
@@ -0,0 +1,678 @@
// 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 1.2 API Implementation
//
function SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey,
scoid, attempt, viewmode, cmid, currentorg, autocommit, masteryoverride, hidetoc) {
var prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "&currentorg=" + currentorg + "&sesskey=" + sesskey;
var datamodelurl = cfgwwwroot + "/mod/scorm/datamodel.php";
var datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid;
// Standard Data Type Definition
CMIString256 = cmistring256;
CMIString4096 = cmistring4096;
CMITime = '^([0-2]{1}[0-9]{1}):([0-5]{1}[0-9]{1}):([0-5]{1}[0-9]{1})(\.[0-9]{1,2})?$';
CMITimespan = '^([0-9]{2,4}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,2})?$';
CMIInteger = '^\\d+$';
CMISInteger = '^-?([0-9]+)$';
CMIDecimal = '^-?([0-9]{0,3})(\.[0-9]*)?$';
CMIIdentifier = '^[\\u0021-\\u007E]{0,255}$';
CMIFeedback = CMIString256; // This must be redefined
CMIIndex = '[._](\\d+).';
// Vocabulary Data Type Definition
CMIStatus = '^passed$|^completed$|^failed$|^incomplete$|^browsed$';
CMIStatus2 = '^passed$|^completed$|^failed$|^incomplete$|^browsed$|^not attempted$';
CMIExit = '^time-out$|^suspend$|^logout$|^$';
CMIType = '^true-false$|^choice$|^fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$';
CMIResult = '^correct$|^wrong$|^unanticipated$|^neutral$|^([0-9]{0,3})?(\.[0-9]*)?$';
NAVEvent = '^previous$|^continue$';
// Children lists
cmi_children = 'core,suspend_data,launch_data,comments,objectives,student_data,student_preference,interactions';
core_children = 'student_id,student_name,lesson_location,credit,lesson_status,entry,score,total_time,lesson_mode,exit,session_time';
score_children = 'raw,min,max';
comments_children = 'content,location,time';
objectives_children = 'id,score,status';
correct_responses_children = 'pattern';
student_data_children = 'mastery_score,max_time_allowed,time_limit_action';
student_preference_children = 'audio,language,speed,text';
interactions_children = 'id,objectives,time,type,correct_responses,weighting,student_response,result,latency';
// Data ranges
score_range = '0#100';
audio_range = '-1#100';
speed_range = '-100#100';
weighting_range = '-100#100';
text_range = '-1#1';
// The SCORM 1.2 data model
// Set up data model for each sco
var datamodel = {};
for(scoid in def){
datamodel[scoid] = {
'cmi._children':{'defaultvalue':cmi_children, 'mod':'r', 'writeerror':'402'},
'cmi._version':{'defaultvalue':'3.4', 'mod':'r', 'writeerror':'402'},
'cmi.core._children':{'defaultvalue':core_children, 'mod':'r', 'writeerror':'402'},
'cmi.core.student_id':{'defaultvalue':def[scoid]['cmi.core.student_id'], 'mod':'r', 'writeerror':'403'},
'cmi.core.student_name':{'defaultvalue':def[scoid]['cmi.core.student_name'], 'mod':'r', 'writeerror':'403'},
'cmi.core.lesson_location':{'defaultvalue':def[scoid]['cmi.core.lesson_location'], 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
'cmi.core.credit':{'defaultvalue':def[scoid]['cmi.core.credit'], 'mod':'r', 'writeerror':'403'},
'cmi.core.lesson_status':{'defaultvalue':def[scoid]['cmi.core.lesson_status'], 'format':CMIStatus, 'mod':'rw', 'writeerror':'405'},
'cmi.core.entry':{'defaultvalue':def[scoid]['cmi.core.entry'], 'mod':'r', 'writeerror':'403'},
'cmi.core.score._children':{'defaultvalue':score_children, 'mod':'r', 'writeerror':'402'},
'cmi.core.score.raw':{'defaultvalue':def[scoid]['cmi.core.score.raw'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.core.score.max':{'defaultvalue':def[scoid]['cmi.core.score.max'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.core.score.min':{'defaultvalue':def[scoid]['cmi.core.score.min'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.core.total_time':{'defaultvalue':def[scoid]['cmi.core.total_time'], 'mod':'r', 'writeerror':'403'},
'cmi.core.lesson_mode':{'defaultvalue':def[scoid]['cmi.core.lesson_mode'], 'mod':'r', 'writeerror':'403'},
'cmi.core.exit':{'defaultvalue':def[scoid]['cmi.core.exit'], 'format':CMIExit, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.core.session_time':{'format':CMITimespan, 'mod':'w', 'defaultvalue':'00:00:00', 'readerror':'404', 'writeerror':'405'},
'cmi.suspend_data':{'defaultvalue':def[scoid]['cmi.suspend_data'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'},
'cmi.launch_data':{'defaultvalue':def[scoid]['cmi.launch_data'], 'mod':'r', 'writeerror':'403'},
'cmi.comments':{'defaultvalue':def[scoid]['cmi.comments'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'},
// deprecated evaluation attributes
'cmi.evaluation.comments._count':{'defaultvalue':'0', 'mod':'r', 'writeerror':'402'},
'cmi.evaluation.comments._children':{'defaultvalue':comments_children, 'mod':'r', 'writeerror':'402'},
'cmi.evaluation.comments.n.content':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
'cmi.evaluation.comments.n.location':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
'cmi.evaluation.comments.n.time':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMITime, 'mod':'rw', 'writeerror':'405'},
'cmi.comments_from_lms':{'mod':'r', 'writeerror':'403'},
'cmi.objectives._children':{'defaultvalue':objectives_children, 'mod':'r', 'writeerror':'402'},
'cmi.objectives._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
'cmi.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'rw', 'writeerror':'405'},
'cmi.objectives.n.score._children':{'pattern':CMIIndex, 'mod':'r', 'writeerror':'402'},
'cmi.objectives.n.score.raw':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.objectives.n.score.min':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.objectives.n.score.max':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
'cmi.objectives.n.status':{'pattern':CMIIndex, 'format':CMIStatus2, 'mod':'rw', 'writeerror':'405'},
'cmi.student_data._children':{'defaultvalue':student_data_children, 'mod':'r', 'writeerror':'402'},
'cmi.student_data.mastery_score':{'defaultvalue':def[scoid]['cmi.student_data.mastery_score'], 'mod':'r', 'writeerror':'403'},
'cmi.student_data.max_time_allowed':{'defaultvalue':def[scoid]['cmi.student_data.max_time_allowed'], 'mod':'r', 'writeerror':'403'},
'cmi.student_data.time_limit_action':{'defaultvalue':def[scoid]['cmi.student_data.time_limit_action'], 'mod':'r', 'writeerror':'403'},
'cmi.student_preference._children':{'defaultvalue':student_preference_children, 'mod':'r', 'writeerror':'402'},
'cmi.student_preference.audio':{'defaultvalue':def[scoid]['cmi.student_preference.audio'], 'format':CMISInteger, 'range':audio_range, 'mod':'rw', 'writeerror':'405'},
'cmi.student_preference.language':{'defaultvalue':def[scoid]['cmi.student_preference.language'], 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
'cmi.student_preference.speed':{'defaultvalue':def[scoid]['cmi.student_preference.speed'], 'format':CMISInteger, 'range':speed_range, 'mod':'rw', 'writeerror':'405'},
'cmi.student_preference.text':{'defaultvalue':def[scoid]['cmi.student_preference.text'], 'format':CMISInteger, 'range':text_range, 'mod':'rw', 'writeerror':'405'},
'cmi.interactions._children':{'defaultvalue':interactions_children, 'mod':'r', 'writeerror':'402'},
'cmi.interactions._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
'cmi.interactions.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.objectives._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
'cmi.interactions.n.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.time':{'pattern':CMIIndex, 'format':CMITime, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.type':{'pattern':CMIIndex, 'format':CMIType, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.correct_responses._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
'cmi.interactions.n.correct_responses.n.pattern':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.weighting':{'pattern':CMIIndex, 'format':CMIDecimal, 'range':weighting_range, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.student_response':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.result':{'pattern':CMIIndex, 'format':CMIResult, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'cmi.interactions.n.latency':{'pattern':CMIIndex, 'format':CMITimespan, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
'nav.event':{'defaultvalue':'', 'format':NAVEvent, 'mod':'w', 'readerror':'404', 'writeerror':'405'}
};
}
var cmi, nav;
function initdatamodel(scoid){
prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "&currentorg=" + currentorg + "&sesskey=" + sesskey;
datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid;
//
// Datamodel inizialization
//
cmi = new Object();
cmi.core = new Object();
cmi.core.score = new Object();
cmi.objectives = new Object();
cmi.student_data = new Object();
cmi.student_preference = new Object();
cmi.interactions = new Object();
// deprecated evaluation attributes
cmi.evaluation = new Object();
cmi.evaluation.comments = new Object();
// Navigation Object
nav = new Object();
for (element in datamodel[scoid]) {
if (element.match(/\.n\./) == null) {
if (typeof datamodel[scoid][element].defaultvalue != 'undefined') {
eval(element + ' = datamodel["' + scoid + '"]["' + element + '"].defaultvalue;');
} else {
eval(element + ' = "";');
}
}
}
eval(cmiobj[scoid]);
eval(cmiint[scoid]);
if (cmi.core.lesson_status == '') {
cmi.core.lesson_status = 'not attempted';
}
}
//
// API Methods definition
//
var Initialized = false;
function LMSInitialize (param) {
scoid = (scorm_current_node && scorm_current_node.scoid) ? scorm_current_node.scoid : scoid;
initdatamodel(scoid);
errorCode = "0";
if (param == "") {
if (!Initialized) {
Initialized = true;
errorCode = "0";
if (scormdebugging) {
LogAPICall("LMSInitialize", param, "", errorCode);
}
return "true";
} else {
errorCode = "101";
}
} else {
errorCode = "201";
}
if (scormdebugging) {
LogAPICall("LMSInitialize", param, "", errorCode);
}
return "false";
}
function LMSFinish (param) {
errorCode = "0";
if (param == "") {
if (Initialized) {
Initialized = false;
result = StoreData(cmi,true);
if (nav.event != '') {
if (nav.event == 'continue') {
setTimeout('mod_scorm_launch_next_sco();',500);
} else {
setTimeout('mod_scorm_launch_prev_sco();',500);
}
} else {
if (scormauto == 1) {
setTimeout('mod_scorm_launch_next_sco();',500);
}
}
if (scormdebugging) {
LogAPICall("LMSFinish", "AJAXResult", result, 0);
}
result = ('true' == result) ? 'true' : 'false';
errorCode = (result == 'true') ? '0' : '101';
if (scormdebugging) {
LogAPICall("LMSFinish", "result", result, 0);
LogAPICall("LMSFinish", param, "", 0);
}
// trigger TOC update
var callback = M.mod_scorm.connectPrereqCallback;
YUI().use('io-base', function(Y) {
Y.on('io:complete', callback.success, Y);
Y.io(prerequrl);
});
return result;
} else {
errorCode = "301";
}
} else {
errorCode = "201";
}
if (scormdebugging) {
LogAPICall("LMSFinish", param, "", errorCode);
}
return "false";
}
function LMSGetValue (element) {
errorCode = "0";
if (Initialized) {
if (element != "") {
expression = new RegExp(CMIIndex,'g');
elementmodel = String(element).replace(expression,'.n.');
if (typeof datamodel[scoid][elementmodel] != "undefined") {
if (datamodel[scoid][elementmodel].mod != 'w') {
element = String(element).replace(expression, "_$1.");
elementIndexes = element.split('.');
subelement = 'cmi';
i = 1;
while ((i < elementIndexes.length) && (typeof eval(subelement) != "undefined")) {
subelement += '.' + elementIndexes[i++];
}
if (subelement == element) {
errorCode = "0";
if (scormdebugging) {
LogAPICall("LMSGetValue", element, eval(element), 0);
}
return eval(element);
} else {
errorCode = "0"; // Need to check if it is the right errorCode
}
} else {
errorCode = datamodel[scoid][elementmodel].readerror;
}
} else {
childrenstr = '._children';
countstr = '._count';
if (elementmodel.substr(elementmodel.length - childrenstr.length,elementmodel.length) == childrenstr) {
parentmodel = elementmodel.substr(0,elementmodel.length - childrenstr.length);
if (typeof datamodel[scoid][parentmodel] != "undefined") {
errorCode = "202";
} else {
errorCode = "201";
}
} else if (elementmodel.substr(elementmodel.length - countstr.length,elementmodel.length) == countstr) {
parentmodel = elementmodel.substr(0,elementmodel.length - countstr.length);
if (typeof datamodel[scoid][parentmodel] != "undefined") {
errorCode = "203";
} else {
errorCode = "201";
}
} else {
errorCode = "201";
}
}
} else {
errorCode = "201";
}
} else {
errorCode = "301";
}
if (scormdebugging) {
LogAPICall("LMSGetValue", element, "", errorCode);
}
return "";
}
function LMSSetValue (element,value) {
errorCode = "0";
if (Initialized) {
if (element != "") {
expression = new RegExp(CMIIndex,'g');
elementmodel = String(element).replace(expression,'.n.');
if (typeof datamodel[scoid][elementmodel] != "undefined") {
if (datamodel[scoid][elementmodel].mod != 'r') {
expression = new RegExp(datamodel[scoid][elementmodel].format);
value = value + '';
matches = value.match(expression);
if (matches != null) {
//Create dynamic data model element
if (element != elementmodel) {
elementIndexes = element.split('.');
subelement = 'cmi';
for (i = 1; i < elementIndexes.length - 1; i++) {
elementIndex = elementIndexes[i];
if (elementIndexes[i + 1].match(/^\d+$/)) {
if ((typeof eval(subelement + '.' + elementIndex)) == "undefined") {
eval(subelement + '.' + elementIndex + ' = new Object();');
eval(subelement + '.' + elementIndex + '._count = 0;');
}
if (elementIndexes[i + 1] == eval(subelement + '.' + elementIndex + '._count')) {
eval(subelement + '.' + elementIndex + '._count++;');
}
if (elementIndexes[i + 1] > eval(subelement + '.' + elementIndex + '._count')) {
errorCode = "201";
}
subelement = subelement.concat('.' + elementIndex + '_' + elementIndexes[i + 1]);
i++;
} else {
subelement = subelement.concat('.' + elementIndex);
}
if ((typeof eval(subelement)) == "undefined") {
eval(subelement + ' = new Object();');
if (subelement.substr(0,14) == 'cmi.objectives') {
eval(subelement + '.score = new Object();');
eval(subelement + '.score._children = score_children;');
eval(subelement + '.score.raw = "";');
eval(subelement + '.score.min = "";');
eval(subelement + '.score.max = "";');
}
if (subelement.substr(0,16) == 'cmi.interactions') {
eval(subelement + '.objectives = new Object();');
eval(subelement + '.objectives._count = 0;');
eval(subelement + '.correct_responses = new Object();');
eval(subelement + '.correct_responses._count = 0;');
}
}
}
element = subelement.concat('.' + elementIndexes[elementIndexes.length - 1]);
}
//Store data
if (errorCode == "0") {
if (autocommit && !(SCORMapi1_2.timeout)) {
SCORMapi1_2.timeout = Y.later(60000, API, 'LMSCommit', [""], false);
}
if (typeof datamodel[scoid][elementmodel].range != "undefined") {
range = datamodel[scoid][elementmodel].range;
ranges = range.split('#');
value = value * 1.0;
if ((value >= ranges[0]) && (value <= ranges[1])) {
eval(element + '=value;');
errorCode = "0";
if (scormdebugging) {
LogAPICall("LMSSetValue", element, value, errorCode);
}
return "true";
} else {
errorCode = datamodel[scoid][elementmodel].writeerror;
}
} else {
if (element == 'cmi.comments') {
cmi.comments = cmi.comments + value;
} else {
eval(element + '=value;');
}
errorCode = "0";
if (scormdebugging) {
LogAPICall("LMSSetValue", element, value, errorCode);
}
return "true";
}
}
} else {
errorCode = datamodel[scoid][elementmodel].writeerror;
}
} else {
errorCode = datamodel[scoid][elementmodel].writeerror;
}
} else {
errorCode = "201"
}
} else {
errorCode = "201";
}
} else {
errorCode = "301";
}
if (scormdebugging) {
LogAPICall("LMSSetValue", element, value, errorCode);
}
return "false";
}
function LMSCommit (param) {
if (SCORMapi1_2.timeout) {
SCORMapi1_2.timeout.cancel();
SCORMapi1_2.timeout = null;
}
errorCode = "0";
if (param == "") {
if (Initialized) {
result = StoreData(cmi,false);
// Trigger TOC update only if TOC is displayed.
// Checks against setting Display course structure in player:
// 0 = To the side, 1 = Hidden, 2 = In a drop down menu, 3 = Disabled
if (hidetoc !== '3') {
Y.log('Refreshing toc');
var callback = M.mod_scorm.connectPrereqCallback;
YUI().use('io-base', function(Y) {
Y.on('io:complete', callback.success, Y);
Y.io(prerequrl);
});
}
if (scormdebugging) {
LogAPICall("Commit", param, "", 0);
}
if (scormdebugging) {
LogAPICall("LMSCommit", "AJAXResult", result, 0);
}
result = ('true' == result) ? 'true' : 'false';
errorCode = (result == 'true') ? '0' : '101';
if (scormdebugging) {
LogAPICall("LMSCommit", "result", result, 0);
LogAPICall("LMSCommit", "errorCode", errorCode, 0);
}
return result;
} else {
errorCode = "301";
}
} else {
errorCode = "201";
}
if (scormdebugging) {
LogAPICall("LMSCommit", param, "", 0);
}
return "false";
}
function LMSGetLastError () {
if (scormdebugging) {
LogAPICall("LMSGetLastError", "", "", errorCode);
}
return errorCode;
}
function LMSGetErrorString (param) {
if (param != "") {
var errorString = new Array();
errorString["0"] = "No error";
errorString["101"] = "General exception";
errorString["201"] = "Invalid argument error";
errorString["202"] = "Element cannot have children";
errorString["203"] = "Element not an array - cannot have count";
errorString["301"] = "Not initialized";
errorString["401"] = "Not implemented error";
errorString["402"] = "Invalid set value, element is a keyword";
errorString["403"] = "Element is read only";
errorString["404"] = "Element is write only";
errorString["405"] = "Incorrect data type";
if (scormdebugging) {
LogAPICall("LMSGetErrorString", param, errorString[param], 0);
}
return errorString[param];
} else {
if (scormdebugging) {
LogAPICall("LMSGetErrorString", param, "No error string found!", 0);
}
return "";
}
}
function LMSGetDiagnostic (param) {
if (param == "") {
param = errorCode;
}
if (scormdebugging) {
LogAPICall("LMSGetDiagnostic", param, param, 0);
}
return param;
}
function AddTime (first, second) {
var sFirst = first.split(":");
var sSecond = second.split(":");
var cFirst = sFirst[2].split(".");
var cSecond = sSecond[2].split(".");
var change = 0;
FirstCents = 0; //Cents
if (cFirst.length > 1) {
FirstCents = parseInt(cFirst[1],10);
}
SecondCents = 0;
if (cSecond.length > 1) {
SecondCents = parseInt(cSecond[1],10);
}
var cents = FirstCents + SecondCents;
change = Math.floor(cents / 100);
cents = cents - (change * 100);
if (Math.floor(cents) < 10) {
cents = "0" + cents.toString();
}
var secs = parseInt(cFirst[0],10) + parseInt(cSecond[0],10) + change; //Seconds
change = Math.floor(secs / 60);
secs = secs - (change * 60);
if (Math.floor(secs) < 10) {
secs = "0" + secs.toString();
}
mins = parseInt(sFirst[1],10) + parseInt(sSecond[1],10) + change; //Minutes
change = Math.floor(mins / 60);
mins = mins - (change * 60);
if (mins < 10) {
mins = "0" + mins.toString();
}
hours = parseInt(sFirst[0],10) + parseInt(sSecond[0],10) + change; //Hours
if (hours < 10) {
hours = "0" + hours.toString();
}
if (cents != '0') {
return hours + ":" + mins + ":" + secs + '.' + cents;
} else {
return hours + ":" + mins + ":" + secs;
}
}
function TotalTime() {
total_time = AddTime(cmi.core.total_time, cmi.core.session_time);
return '&' + underscore('cmi.core.total_time') + '=' + encodeURIComponent(total_time);
}
function CollectData(data,parent) {
var datastring = '';
for (property in data) {
if (typeof data[property] == 'object') {
datastring += CollectData(data[property],parent + '.' + property);
} else {
element = parent + '.' + property;
expression = new RegExp(CMIIndex,'g');
// get the generic name for this element (e.g. convert 'cmi.interactions.1.id' to 'cmi.interactions.n.id')
elementmodel = String(element).replace(expression,'.n.');
// ignore the session time element
if (element != "cmi.core.session_time") {
// check if this specific element is not defined in the datamodel,
// but the generic element name is
if (typeof datamodel[scoid][element] == "undefined" && typeof datamodel[scoid][elementmodel] != "undefined") {
// add this specific element to the data model (by cloning
// the generic element) so we can track changes to it
datamodel[scoid][element] = CloneObj(datamodel[scoid][elementmodel]);
}
// check if the current element exists in the datamodel
if (typeof datamodel[scoid][element] != "undefined") {
// make sure this is not a read only element
if (datamodel[scoid][element].mod != 'r') {
elementstring = '&' + underscore(element) + '=' + encodeURIComponent(data[property]);
// check if the element has a default value
if (typeof datamodel[scoid][element].defaultvalue != "undefined") {
// check if the default value is different from the current value
if (datamodel[scoid][element].defaultvalue != data[property] ||
typeof datamodel[scoid][element].defaultvalue != typeof data[property]) {
// append the URI fragment to the string we plan to commit
datastring += elementstring;
// update the element default to reflect the current committed value
datamodel[scoid][element].defaultvalue = data[property];
}
} else {
// append the URI fragment to the string we plan to commit
datastring += elementstring;
// no default value for the element, so set it now
datamodel[scoid][element].defaultvalue = data[property];
}
}
}
}
}
}
return datastring;
}
function CloneObj(obj){
if(obj == null || typeof(obj) != 'object') {
return obj;
}
var temp = new obj.constructor(); // changed (twice)
for(var key in obj) {
temp[key] = CloneObj(obj[key]);
}
return temp;
}
function StoreData(data,storetotaltime) {
var datastring = '';
if (storetotaltime) {
if (cmi.core.lesson_status == 'not attempted') {
cmi.core.lesson_status = 'completed';
}
if (cmi.core.lesson_mode == 'normal') {
if (cmi.core.credit == 'credit') {
if (masteryoverride && cmi.student_data.mastery_score !== '' && cmi.core.score.raw !== '') {
if (parseFloat(cmi.core.score.raw) >= parseFloat(cmi.student_data.mastery_score)) {
cmi.core.lesson_status = 'passed';
} else {
cmi.core.lesson_status = 'failed';
}
}
}
}
if (cmi.core.lesson_mode == 'browse') {
if (datamodel[scoid]['cmi.core.lesson_status'].defaultvalue == '' && cmi.core.lesson_status == 'not attempted') {
cmi.core.lesson_status = 'browsed';
}
}
datastring = CollectData(data,'cmi');
datastring += TotalTime();
} else {
datastring = CollectData(data,'cmi');
}
var myRequest = NewHttpReq();
//alert('going to:' + "<?php p($CFG->wwwroot) ?>/mod/scorm/datamodel.php" + "id=<?php p($id) ?>&a=<?php p($a) ?>&sesskey=<?php echo sesskey() ?>"+datastring);
var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
if (result === false) {
return false;
}
results = String(result).split('\n');
errorCode = results[1];
return results[0];
}
this.LMSInitialize = LMSInitialize;
this.LMSFinish = LMSFinish;
this.LMSGetValue = LMSGetValue;
this.LMSSetValue = LMSSetValue;
this.LMSCommit = LMSCommit;
this.LMSGetLastError = LMSGetLastError;
this.LMSGetErrorString = LMSGetErrorString;
this.LMSGetDiagnostic = LMSGetDiagnostic;
}
M.scorm_api = {};
M.scorm_api.init = function(Y, def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebugging, scormauto, scormid, cfgwwwroot,
sesskey, scoid, attempt, viewmode, cmid, currentorg, autocommit, masteryoverride, hidetoc) {
window.API = new SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebugging, scormauto, scormid, cfgwwwroot,
sesskey, scoid, attempt, viewmode, cmid, currentorg, autocommit, masteryoverride, hidetoc);
}
+66
View File
@@ -0,0 +1,66 @@
<?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/>.
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
// Set some vars to use as default values.
$userdata = new stdClass();
$def = new stdClass();
$cmiobj = new stdClass();
$cmiint = new stdClass();
if (!isset($currentorg)) {
$currentorg = '';
}
if ($scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'sortorder, id')) {
// Drop keys so that it is a simple array.
$scoes = array_values($scoes);
foreach ($scoes as $sco) {
$def->{($sco->id)} = new stdClass();
$userdata->{($sco->id)} = new stdClass();
$def->{($sco->id)} = get_scorm_default($userdata->{($sco->id)}, $scorm, $sco->id, $attempt, $mode);
// Reconstitute objectives.
$cmiobj->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)},
'cmi.objectives', array('score'));
$cmiint->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)},
'cmi.interactions', array('objectives', 'correct_responses'));
}
}
// If SCORM 1.2 standard mode is disabled allow higher datamodel limits.
if (intval(get_config("scorm", "scormstandard"))) {
$cmistring256 = '^[\\u0000-\\uFFFF]{0,255}$';
$cmistring4096 = '^[\\u0000-\\uFFFF]{0,4096}$';
} else {
$cmistring256 = '^[\\u0000-\\uFFFF]{0,64000}$';
$cmistring4096 = $cmistring256;
}
$scorm->autocommit = ($scorm->autocommit === "1") ? true : false;
$scorm->masteryoverride = ($scorm->masteryoverride === "1") ? true : false;
$PAGE->requires->js_init_call('M.scorm_api.init', array($def, $cmiobj, $cmiint, $cmistring256, $cmistring4096,
scorm_debugging($scorm), $scorm->auto, $scorm->id, $CFG->wwwroot,
sesskey(), $scoid, $attempt, $mode, $id, $currentorg, $scorm->autocommit,
$scorm->masteryoverride, $scorm->hidetoc));
// Pull in the debugging utilities.
if (scorm_debugging($scorm)) {
require_once($CFG->dirroot.'/mod/scorm/datamodels/debug.js.php');
echo html_writer::script('AppendToLog("Moodle SCORM 1.2 API Loaded, Activity: '.
$scorm->name.', SCO: '.$sco->identifier.'", 0);');
}
+106
View File
@@ -0,0 +1,106 @@
<?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/>.
/**
* Sets up $userdata array and default values for SCORM 1.2 .
*
* @param stdClass $userdata an empty stdClass variable that should be set up with user values
* @param object $scorm package record
* @param string $scoid SCO Id
* @param string $attempt attempt number for the user
* @param string $mode scorm display mode type
* @return array The default values that should be used for SCORM 1.2 package
*/
function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) {
global $USER;
$userdata->student_id = $USER->username;
if (empty(get_config('scorm', 'scormstandard'))) {
$userdata->student_name = fullname($USER);
} else {
$userdata->student_name = $USER->lastname .', '. $USER->firstname;
}
if ($usertrack = scorm_get_tracks($scoid, $USER->id, $attempt)) {
foreach ($usertrack as $key => $value) {
$userdata->$key = $value;
}
} else {
$userdata->status = '';
$userdata->score_raw = '';
}
if ($scodatas = scorm_get_sco($scoid, SCO_DATA)) {
foreach ($scodatas as $key => $value) {
$userdata->$key = $value;
}
} else {
throw new \moodle_exception('cannotfindsco', 'scorm');
}
if (!$sco = scorm_get_sco($scoid)) {
throw new \moodle_exception('cannotfindsco', 'scorm');
}
if (isset($userdata->status)) {
if ($userdata->status == '') {
$userdata->entry = 'ab-initio';
} else {
if (isset($userdata->{'cmi.core.exit'}) && ($userdata->{'cmi.core.exit'} == 'suspend')) {
$userdata->entry = 'resume';
} else {
$userdata->entry = '';
}
}
}
$userdata->mode = 'normal';
if (!empty($mode)) {
$userdata->mode = $mode;
}
if ($userdata->mode == 'normal') {
$userdata->credit = 'credit';
} else {
$userdata->credit = 'no-credit';
}
$def = array();
$def['cmi.core.student_id'] = $userdata->student_id;
$def['cmi.core.student_name'] = $userdata->student_name;
$def['cmi.core.credit'] = $userdata->credit;
$def['cmi.core.entry'] = $userdata->entry;
$def['cmi.core.lesson_mode'] = $userdata->mode;
$def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms');
$def['cmi.student_data.mastery_score'] = scorm_isset($userdata, 'masteryscore');
$def['cmi.student_data.max_time_allowed'] = scorm_isset($userdata, 'maxtimeallowed');
$def['cmi.student_data.time_limit_action'] = scorm_isset($userdata, 'timelimitaction');
$def['cmi.core.total_time'] = scorm_isset($userdata, 'cmi.core.total_time', '00:00:00');
// Now handle standard userdata items.
$def['cmi.core.lesson_location'] = scorm_isset($userdata, 'cmi.core.lesson_location');
$def['cmi.core.lesson_status'] = scorm_isset($userdata, 'cmi.core.lesson_status');
$def['cmi.core.score.raw'] = scorm_isset($userdata, 'cmi.core.score.raw');
$def['cmi.core.score.max'] = scorm_isset($userdata, 'cmi.core.score.max');
$def['cmi.core.score.min'] = scorm_isset($userdata, 'cmi.core.score.min');
$def['cmi.core.exit'] = scorm_isset($userdata, 'cmi.core.exit');
$def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data');
$def['cmi.comments'] = scorm_isset($userdata, 'cmi.comments');
$def['cmi.student_preference.language'] = scorm_isset($userdata, 'cmi.student_preference.language');
$def['cmi.student_preference.audio'] = scorm_isset($userdata, 'cmi.student_preference.audio', '0');
$def['cmi.student_preference.speed'] = scorm_isset($userdata, 'cmi.student_preference.speed', '0');
$def['cmi.student_preference.text'] = scorm_isset($userdata, 'cmi.student_preference.text', '0');
return $def;
}
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
<?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/>.
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$userdata = new stdClass();
$def = new stdClass();
$cmiobj = new stdClass();
$cmiint = new stdClass();
$cmicommentsuser = new stdClass();
$cmicommentslms = new stdClass();
if (!isset($currentorg)) {
$currentorg = '';
}
if ($scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'sortorder, id')) {
// Drop keys so that it is a simple array.
$scoes = array_values($scoes);
foreach ($scoes as $sco) {
$def->{($sco->id)} = new stdClass();
$userdata->{($sco->id)} = new stdClass();
$def->{($sco->id)} = get_scorm_default($userdata->{($sco->id)}, $scorm, $sco->id, $attempt, $mode);
// Reconstitute objectives, comments_from_learner and comments_from_lms.
$cmiobj->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)},
'cmi.objectives', array('score'));
$cmiint->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)},
'cmi.interactions', array('objectives', 'correct_responses'));
$cmicommentsuser->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)},
'cmi.comments_from_learner', array());
$cmicommentslms->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)},
'cmi.comments_from_lms', array());
}
}
$scorm->autocommit = ($scorm->autocommit === "1") ? true : false;
$PAGE->requires->js_init_call('M.scorm_api.init', array($def, $cmiobj, $cmiint, $cmicommentsuser, $cmicommentslms,
scorm_debugging($scorm), $scorm->auto, $scorm->id, $CFG->wwwroot,
sesskey(), $scoid, $attempt, $mode, $id, $currentorg, $scorm->autocommit));
// Pull in the debugging utilities.
if (scorm_debugging($scorm)) {
require_once($CFG->dirroot.'/mod/scorm/datamodels/debug.js.php');
echo html_writer::script('AppendToLog("Moodle SCORM 1.3 API Loaded, Activity: '.
$scorm->name.', SCO: '.$sco->identifier.'", 0);');
}
File diff suppressed because it is too large Load Diff
+956
View File
@@ -0,0 +1,956 @@
<?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/>.
/**
* functions used by SCORM 1.2/2004 packages.
*
* @package mod_scorm
* @copyright 1999 onwards Roberto Pinna
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
function scorm_get_resources($blocks) {
$resources = array();
foreach ($blocks as $block) {
if ($block['name'] == 'RESOURCES' && isset($block['children'])) {
foreach ($block['children'] as $resource) {
if ($resource['name'] == 'RESOURCE') {
$resources[addslashes_js($resource['attrs']['IDENTIFIER'])] = $resource['attrs'];
}
}
}
}
return $resources;
}
function scorm_get_manifest($blocks, $scoes) {
global $OUTPUT;
static $parents = array();
static $resources;
static $manifest;
static $organization;
$manifestresourcesnotfound = array();
if (count($blocks) > 0) {
foreach ($blocks as $block) {
switch ($block['name']) {
case 'METADATA':
if (isset($block['children'])) {
foreach ($block['children'] as $metadata) {
if ($metadata['name'] == 'SCHEMAVERSION') {
if (empty($scoes->version)) {
$isversionset = (preg_match("/^(1\.2)$|^(CAM )?(1\.3)$/", $metadata['tagData'], $matches));
if (isset($metadata['tagData']) && $isversionset) {
$scoes->version = 'SCORM_'.$matches[count($matches) - 1];
} else {
$isversionset = (preg_match("/^2004 (3rd|4th) Edition$/", $metadata['tagData'], $matches));
if (isset($metadata['tagData']) && $isversionset) {
$scoes->version = 'SCORM_1.3';
} else {
$scoes->version = 'SCORM_1.2';
}
}
}
}
}
}
break;
case 'MANIFEST':
$manifest = $block['attrs']['IDENTIFIER'];
$organization = '';
$resources = array();
$resources = scorm_get_resources($block['children']);
$scoes = scorm_get_manifest($block['children'], $scoes);
if (empty($scoes->elements) || count($scoes->elements) <= 0) {
foreach ($resources as $item => $resource) {
if (!empty($resource['HREF'])) {
$sco = new stdClass();
$sco->identifier = $item;
$sco->title = $item;
$sco->parent = '/';
$sco->launch = $resource['HREF'];
$sco->scormtype = $resource['ADLCP:SCORMTYPE'];
$scoes->elements[$manifest][$organization][$item] = $sco;
}
}
}
break;
case 'ORGANIZATIONS':
if (!isset($scoes->defaultorg) && isset($block['attrs']['DEFAULT'])) {
$scoes->defaultorg = $block['attrs']['DEFAULT'];
}
if (!empty($block['children'])) {
$scoes = scorm_get_manifest($block['children'], $scoes);
}
break;
case 'ORGANIZATION':
$identifier = $block['attrs']['IDENTIFIER'];
$organization = '';
$scoes->elements[$manifest][$organization][$identifier] = new stdClass();
$scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
$scoes->elements[$manifest][$organization][$identifier]->parent = '/';
$scoes->elements[$manifest][$organization][$identifier]->launch = '';
$scoes->elements[$manifest][$organization][$identifier]->scormtype = '';
$parents = array();
$parent = new stdClass();
$parent->identifier = $identifier;
$parent->organization = $organization;
array_push($parents, $parent);
$organization = $identifier;
if (!empty($block['children'])) {
$scoes = scorm_get_manifest($block['children'], $scoes);
}
array_pop($parents);
break;
case 'ITEM':
$parent = array_pop($parents);
array_push($parents, $parent);
$identifier = $block['attrs']['IDENTIFIER'];
$scoes->elements[$manifest][$organization][$identifier] = new stdClass();
$scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
$scoes->elements[$manifest][$organization][$identifier]->parent = $parent->identifier;
if (!isset($block['attrs']['ISVISIBLE'])) {
$block['attrs']['ISVISIBLE'] = 'true';
}
$scoes->elements[$manifest][$organization][$identifier]->isvisible = $block['attrs']['ISVISIBLE'];
if (!isset($block['attrs']['PARAMETERS'])) {
$block['attrs']['PARAMETERS'] = '';
}
$scoes->elements[$manifest][$organization][$identifier]->parameters = $block['attrs']['PARAMETERS'];
if (!isset($block['attrs']['IDENTIFIERREF'])) {
$scoes->elements[$manifest][$organization][$identifier]->launch = '';
$scoes->elements[$manifest][$organization][$identifier]->scormtype = 'asset';
} else {
$idref = addslashes_js($block['attrs']['IDENTIFIERREF']);
$base = '';
if (isset($resources[$idref]['XML:BASE'])) {
$base = $resources[$idref]['XML:BASE'];
}
if (!isset($resources[$idref])) {
$manifestresourcesnotfound[] = $idref;
$scoes->elements[$manifest][$organization][$identifier]->launch = '';
} else {
$scoes->elements[$manifest][$organization][$identifier]->launch = $base.$resources[$idref]['HREF'];
if (empty($resources[$idref]['ADLCP:SCORMTYPE'])) {
$resources[$idref]['ADLCP:SCORMTYPE'] = 'asset';
}
$scoes->elements[$manifest][$organization][$identifier]->scormtype = $resources[$idref]['ADLCP:SCORMTYPE'];
}
}
$parent = new stdClass();
$parent->identifier = $identifier;
$parent->organization = $organization;
array_push($parents, $parent);
if (!empty($block['children'])) {
$scoes = scorm_get_manifest($block['children'], $scoes);
}
array_pop($parents);
break;
case 'TITLE':
$parent = array_pop($parents);
array_push($parents, $parent);
if (!isset($block['tagData'])) {
$block['tagData'] = '';
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->title = $block['tagData'];
break;
case 'ADLCP:PREREQUISITES':
if ($block['attrs']['TYPE'] == 'aicc_script') {
$parent = array_pop($parents);
array_push($parents, $parent);
if (!isset($block['tagData'])) {
$block['tagData'] = '';
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->prerequisites = $block['tagData'];
}
break;
case 'ADLCP:MAXTIMEALLOWED':
$parent = array_pop($parents);
array_push($parents, $parent);
if (!isset($block['tagData'])) {
$block['tagData'] = '';
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->maxtimeallowed = $block['tagData'];
break;
case 'ADLCP:TIMELIMITACTION':
$parent = array_pop($parents);
array_push($parents, $parent);
if (!isset($block['tagData'])) {
$block['tagData'] = '';
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->timelimitaction = $block['tagData'];
break;
case 'ADLCP:DATAFROMLMS':
$parent = array_pop($parents);
array_push($parents, $parent);
if (!isset($block['tagData'])) {
$block['tagData'] = '';
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->datafromlms = $block['tagData'];
break;
case 'ADLCP:MASTERYSCORE':
$parent = array_pop($parents);
array_push($parents, $parent);
if (!isset($block['tagData'])) {
$block['tagData'] = '';
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->masteryscore = $block['tagData'];
break;
case 'ADLCP:COMPLETIONTHRESHOLD':
$parent = array_pop($parents);
array_push($parents, $parent);
if (!isset($block['attrs']['MINPROGRESSMEASURE'])) {
$block['attrs']['MINPROGRESSMEASURE'] = '1.0';
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->threshold = $block['attrs']['MINPROGRESSMEASURE'];
break;
case 'ADLNAV:PRESENTATION':
$parent = array_pop($parents);
array_push($parents, $parent);
if (!empty($block['children'])) {
foreach ($block['children'] as $adlnav) {
if ($adlnav['name'] == 'ADLNAV:NAVIGATIONINTERFACE') {
foreach ($adlnav['children'] as $adlnavinterface) {
if ($adlnavinterface['name'] == 'ADLNAV:HIDELMSUI') {
if ($adlnavinterface['tagData'] == 'continue') {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->hidecontinue = 1;
}
if ($adlnavinterface['tagData'] == 'previous') {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideprevious = 1;
}
if ($adlnavinterface['tagData'] == 'exit') {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideexit = 1;
}
if ($adlnavinterface['tagData'] == 'exitAll') {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideexitall = 1;
}
if ($adlnavinterface['tagData'] == 'abandon') {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideabandon = 1;
}
if ($adlnavinterface['tagData'] == 'abandonAll') {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideabandonall = 1;
}
if ($adlnavinterface['tagData'] == 'suspendAll') {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->hidesuspendall = 1;
}
}
}
}
}
}
break;
case 'IMSSS:SEQUENCING':
$parent = array_pop($parents);
array_push($parents, $parent);
if (!empty($block['children'])) {
foreach ($block['children'] as $sequencing) {
if ($sequencing['name'] == 'IMSSS:CONTROLMODE') {
if (isset($sequencing['attrs']['CHOICE'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->choice =
$sequencing['attrs']['CHOICE'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['CHOICEEXIT'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->choiceexit =
$sequencing['attrs']['CHOICEEXIT'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['FLOW'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->flow =
$sequencing['attrs']['FLOW'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['FORWARDONLY'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->forwardonly =
$sequencing['attrs']['FORWARDONLY'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['USECURRENTATTEMPTOBJECTINFO'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->usecurrentattemptobjectinfo =
$sequencing['attrs']['USECURRENTATTEMPTOBJECTINFO'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['USECURRENTATTEMPTPROGRESSINFO'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->usecurrentattemptprogressinfo =
$sequencing['attrs']['USECURRENTATTEMPTPROGRESSINFO'] == 'true' ? 1 : 0;
}
}
if ($sequencing['name'] == 'IMSSS:DELIVERYCONTROLS') {
if (isset($sequencing['attrs']['TRACKED'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->tracked =
$sequencing['attrs']['TRACKED'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['COMPLETIONSETBYCONTENT'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->completionsetbycontent =
$sequencing['attrs']['COMPLETIONSETBYCONTENT'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['OBJECTIVESETBYCONTENT'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectivesetbycontent =
$sequencing['attrs']['OBJECTIVESETBYCONTENT'] == 'true' ? 1 : 0;
}
}
if ($sequencing['name'] == 'ADLSEQ:CONSTRAINEDCHOICECONSIDERATIONS') {
if (isset($sequencing['attrs']['CONSTRAINCHOICE'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->constrainChoice =
$sequencing['attrs']['CONSTRAINCHOICE'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['PREVENTACTIVATION'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->preventactivation =
$sequencing['attrs']['PREVENTACTIVATION'] == 'true' ? 1 : 0;
}
}
if ($sequencing['name'] == 'IMSSS:OBJECTIVES') {
$objectives = array();
foreach ($sequencing['children'] as $objective) {
$objectivedata = new stdClass();
$objectivedata->primaryobj = 0;
switch ($objective['name']) {
case 'IMSSS:PRIMARYOBJECTIVE':
$objectivedata->primaryobj = 1;
case 'IMSSS:OBJECTIVE':
$objectivedata->satisfiedbymeasure = 0;
if (isset($objective['attrs']['SATISFIEDBYMEASURE'])) {
$objectivedata->satisfiedbymeasure =
$objective['attrs']['SATISFIEDBYMEASURE'] == 'true' ? 1 : 0;
}
$objectivedata->objectiveid = '';
if (isset($objective['attrs']['OBJECTIVEID'])) {
$objectivedata->objectiveid = $objective['attrs']['OBJECTIVEID'];
}
$objectivedata->minnormalizedmeasure = 1.0;
if (!empty($objective['children'])) {
$mapinfos = array();
foreach ($objective['children'] as $objectiveparam) {
if ($objectiveparam['name'] == 'IMSSS:MINNORMALIZEDMEASURE') {
if (isset($objectiveparam['tagData'])) {
$objectivedata->minnormalizedmeasure = $objectiveparam['tagData'];
} else {
$objectivedata->minnormalizedmeasure = 0;
}
}
if ($objectiveparam['name'] == 'IMSSS:MAPINFO') {
$mapinfo = new stdClass();
$mapinfo->targetobjectiveid = '';
if (isset($objectiveparam['attrs']['TARGETOBJECTIVEID'])) {
$mapinfo->targetobjectiveid =
$objectiveparam['attrs']['TARGETOBJECTIVEID'];
}
$mapinfo->readsatisfiedstatus = 1;
if (isset($objectiveparam['attrs']['READSATISFIEDSTATUS'])) {
$mapinfo->readsatisfiedstatus =
$objectiveparam['attrs']['READSATISFIEDSTATUS'] == 'true' ? 1 : 0;
}
$mapinfo->writesatisfiedstatus = 0;
if (isset($objectiveparam['attrs']['WRITESATISFIEDSTATUS'])) {
$mapinfo->writesatisfiedstatus =
$objectiveparam['attrs']['WRITESATISFIEDSTATUS'] == 'true' ? 1 : 0;
}
$mapinfo->readnormalizemeasure = 1;
if (isset($objectiveparam['attrs']['READNORMALIZEDMEASURE'])) {
$mapinfo->readnormalizemeasure =
$objectiveparam['attrs']['READNORMALIZEDMEASURE'] == 'true' ? 1 : 0;
}
$mapinfo->writenormalizemeasure = 0;
if (isset($objectiveparam['attrs']['WRITENORMALIZEDMEASURE'])) {
$mapinfo->writenormalizemeasure =
$objectiveparam['attrs']['WRITENORMALIZEDMEASURE'] == 'true' ? 1 : 0;
}
array_push($mapinfos, $mapinfo);
}
}
if (!empty($mapinfos)) {
$objectivesdata->mapinfos = $mapinfos;
}
}
break;
}
array_push($objectives, $objectivedata);
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectives = $objectives;
}
if ($sequencing['name'] == 'IMSSS:LIMITCONDITIONS') {
if (isset($sequencing['attrs']['ATTEMPTLIMIT'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->attemptLimit =
$sequencing['attrs']['ATTEMPTLIMIT'];
}
if (isset($sequencing['attrs']['ATTEMPTABSOLUTEDURATIONLIMIT'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->attemptAbsoluteDurationLimit =
$sequencing['attrs']['ATTEMPTABSOLUTEDURATIONLIMIT'];
}
}
if ($sequencing['name'] == 'IMSSS:ROLLUPRULES') {
if (isset($sequencing['attrs']['ROLLUPOBJECTIVESATISFIED'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->rollupobjectivesatisfied =
$sequencing['attrs']['ROLLUPOBJECTIVESATISFIED'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['ROLLUPPROGRESSCOMPLETION'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->rollupprogresscompletion =
$sequencing['attrs']['ROLLUPPROGRESSCOMPLETION'] == 'true' ? 1 : 0;
}
if (isset($sequencing['attrs']['OBJECTIVEMEASUREWEIGHT'])) {
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectivemeasureweight =
$sequencing['attrs']['OBJECTIVEMEASUREWEIGHT'];
}
if (!empty($sequencing['children'])) {
$rolluprules = array();
foreach ($sequencing['children'] as $sequencingrolluprule) {
if ($sequencingrolluprule['name'] == 'IMSSS:ROLLUPRULE' ) {
$rolluprule = new stdClass();
$rolluprule->childactivityset = 'all';
if (isset($sequencingrolluprule['attrs']['CHILDACTIVITYSET'])) {
$rolluprule->childactivityset = $sequencingrolluprule['attrs']['CHILDACTIVITYSET'];
}
$rolluprule->minimumcount = 0;
if (isset($sequencingrolluprule['attrs']['MINIMUMCOUNT'])) {
$rolluprule->minimumcount = $sequencingrolluprule['attrs']['MINIMUMCOUNT'];
}
$rolluprule->minimumpercent = 0.0000;
if (isset($sequencingrolluprule['attrs']['MINIMUMPERCENT'])) {
$rolluprule->minimumpercent = $sequencingrolluprule['attrs']['MINIMUMPERCENT'];
}
if (!empty($sequencingrolluprule['children'])) {
foreach ($sequencingrolluprule['children'] as $rolluproleconditions) {
if ($rolluproleconditions['name'] == 'IMSSS:ROLLUPCONDITIONS') {
$conditions = array();
$rolluprule->conditioncombination = 'all';
if (isset($rolluproleconditions['attrs']['CONDITIONCOMBINATION'])) {
$rolluprule->CONDITIONCOMBINATION = $rolluproleconditions['attrs']['CONDITIONCOMBINATION'];
}
foreach ($rolluproleconditions['children'] as $rolluprulecondition) {
if ($rolluprulecondition['name'] == 'IMSSS:ROLLUPCONDITION') {
$condition = new stdClass();
if (isset($rolluprulecondition['attrs']['CONDITION'])) {
$condition->cond = $rolluprulecondition['attrs']['CONDITION'];
}
$condition->operator = 'noOp';
if (isset($rolluprulecondition['attrs']['OPERATOR'])) {
$condition->operator = $rolluprulecondition['attrs']['OPERATOR'];
}
array_push($conditions, $condition);
}
}
$rolluprule->conditions = $conditions;
}
if ($rolluproleconditions['name'] == 'IMSSS:ROLLUPACTION') {
$rolluprule->rollupruleaction = $rolluproleconditions['attrs']['ACTION'];
}
}
}
array_push($rolluprules, $rolluprule);
}
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->rolluprules = $rolluprules;
}
}
if ($sequencing['name'] == 'IMSSS:SEQUENCINGRULES') {
if (!empty($sequencing['children'])) {
$sequencingrules = array();
foreach ($sequencing['children'] as $conditionrules) {
$conditiontype = -1;
switch($conditionrules['name']) {
case 'IMSSS:PRECONDITIONRULE':
$conditiontype = 0;
break;
case 'IMSSS:POSTCONDITIONRULE':
$conditiontype = 1;
break;
case 'IMSSS:EXITCONDITIONRULE':
$conditiontype = 2;
break;
}
if (!empty($conditionrules['children'])) {
$sequencingrule = new stdClass();
foreach ($conditionrules['children'] as $conditionrule) {
if ($conditionrule['name'] == 'IMSSS:RULECONDITIONS') {
$ruleconditions = array();
$sequencingrule->conditioncombination = 'all';
if (isset($conditionrule['attrs']['CONDITIONCOMBINATION'])) {
$sequencingrule->conditioncombination = $conditionrule['attrs']['CONDITIONCOMBINATION'];
}
foreach ($conditionrule['children'] as $rulecondition) {
if ($rulecondition['name'] == 'IMSSS:RULECONDITION') {
$condition = new stdClass();
if (isset($rulecondition['attrs']['CONDITION'])) {
$condition->cond = $rulecondition['attrs']['CONDITION'];
}
$condition->operator = 'noOp';
if (isset($rulecondition['attrs']['OPERATOR'])) {
$condition->operator = $rulecondition['attrs']['OPERATOR'];
}
$condition->measurethreshold = 0.0000;
if (isset($rulecondition['attrs']['MEASURETHRESHOLD'])) {
$condition->measurethreshold = $rulecondition['attrs']['MEASURETHRESHOLD'];
}
$condition->referencedobjective = '';
if (isset($rulecondition['attrs']['REFERENCEDOBJECTIVE'])) {
$condition->referencedobjective = $rulecondition['attrs']['REFERENCEDOBJECTIVE'];
}
array_push($ruleconditions, $condition);
}
}
$sequencingrule->ruleconditions = $ruleconditions;
}
if ($conditionrule['name'] == 'IMSSS:RULEACTION') {
$sequencingrule->action = $conditionrule['attrs']['ACTION'];
}
$sequencingrule->type = $conditiontype;
}
array_push($sequencingrules, $sequencingrule);
}
}
$scoes->elements[$manifest][$parent->organization][$parent->identifier]->sequencingrules = $sequencingrules;
}
}
}
}
break;
}
}
}
if (!empty($manifestresourcesnotfound)) {
// Throw warning to user to let them know manifest contains references to resources that don't appear to exist.
if (!defined('DEBUGGING_PRINTED')) {
// Prevent redirect and display warning.
define('DEBUGGING_PRINTED', 1);
}
echo $OUTPUT->notification(get_string('invalidmanifestresource', 'scorm').' '. implode(', ', $manifestresourcesnotfound));
}
return $scoes;
}
/**
* Sets up SCORM 1.2/2004 packages using the manifest file.
* Called whenever SCORM changes
* @param object $scorm instance - fields are updated and changes saved into database
* @param stored_file|string $manifest - path to manifest file or stored_file.
* @return bool
*/
function scorm_parse_scorm(&$scorm, $manifest) {
global $CFG, $DB;
// Load manifest into string.
if ($manifest instanceof stored_file) {
$xmltext = $manifest->get_content();
} else {
require_once("$CFG->libdir/filelib.php");
$xmltext = download_file_content($manifest);
}
$defaultorgid = 0;
$firstinorg = 0;
$pattern = '/&(?!\w{2,6};)/';
$replacement = '&amp;';
$xmltext = preg_replace($pattern, $replacement, $xmltext);
$objxml = new xml2Array();
$manifests = $objxml->parse($xmltext);
$scoes = new stdClass();
$scoes->version = '';
$scoes = scorm_get_manifest($manifests, $scoes);
$newscoes = array();
$sortorder = 0;
if (!empty($scoes->elements) && is_iterable($scoes->elements)) {
$olditems = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id));
foreach ($scoes->elements as $manifest => $organizations) {
foreach ($organizations as $organization => $items) {
foreach ($items as $identifier => $item) {
$sortorder++;
// This new db mngt will support all SCORM future extensions.
$newitem = new stdClass();
$newitem->scorm = $scorm->id;
$newitem->manifest = $manifest;
$newitem->organization = $organization;
$newitem->sortorder = $sortorder;
$standarddatas = array('parent', 'identifier', 'launch', 'scormtype', 'title');
foreach ($standarddatas as $standarddata) {
if (isset($item->$standarddata)) {
$newitem->$standarddata = $item->$standarddata;
} else {
$newitem->$standarddata = '';
}
}
if (!empty($defaultorgid) && !empty($scoes->defaultorg) && empty($firstinorg) &&
$newitem->parent == $scoes->defaultorg) {
$firstinorg = $sortorder;
}
if (!empty($olditems) && ($olditemid = scorm_array_search('identifier', $newitem->identifier, $olditems))) {
$newitem->id = $olditemid;
// Update the Sco sortorder but keep id so that user tracks are kept against the same ids.
$DB->update_record('scorm_scoes', $newitem);
$id = $olditemid;
// Remove all old data so we don't duplicate it.
$DB->delete_records('scorm_scoes_data', array('scoid' => $olditemid));
$DB->delete_records('scorm_seq_objective', array('scoid' => $olditemid));
$DB->delete_records('scorm_seq_mapinfo', array('scoid' => $olditemid));
$DB->delete_records('scorm_seq_ruleconds', array('scoid' => $olditemid));
$DB->delete_records('scorm_seq_rulecond', array('scoid' => $olditemid));
$DB->delete_records('scorm_seq_rolluprule', array('scoid' => $olditemid));
$DB->delete_records('scorm_seq_rolluprulecond', array('scoid' => $olditemid));
// Now remove this SCO from the olditems object as we have dealt with it.
unset($olditems[$olditemid]);
} else {
// Insert the new SCO, and retain the link between the old and new for later adjustment.
$id = $DB->insert_record('scorm_scoes', $newitem);
}
// Save this sco in memory so we can use it later.
$newscoes[$id] = $newitem;
if ($optionaldatas = scorm_optionals_data($item, $standarddatas)) {
$data = new stdClass();
$data->scoid = $id;
foreach ($optionaldatas as $optionaldata) {
if (isset($item->$optionaldata)) {
$data->name = $optionaldata;
$data->value = $item->$optionaldata;
$dataid = $DB->insert_record('scorm_scoes_data', $data);
}
}
}
if (isset($item->sequencingrules)) {
foreach ($item->sequencingrules as $sequencingrule) {
$rule = new stdClass();
$rule->scoid = $id;
$rule->ruletype = $sequencingrule->type;
$rule->conditioncombination = $sequencingrule->conditioncombination;
$rule->action = $sequencingrule->action;
$ruleid = $DB->insert_record('scorm_seq_ruleconds', $rule);
if (isset($sequencingrule->ruleconditions)) {
foreach ($sequencingrule->ruleconditions as $rulecondition) {
$rulecond = new stdClass();
$rulecond->scoid = $id;
$rulecond->ruleconditionsid = $ruleid;
$rulecond->referencedobjective = $rulecondition->referencedobjective;
$rulecond->measurethreshold = $rulecondition->measurethreshold;
$rulecond->operator = $rulecondition->operator;
$rulecond->cond = $rulecondition->cond;
$rulecondid = $DB->insert_record('scorm_seq_rulecond', $rulecond);
}
}
}
}
if (isset($item->rolluprules)) {
foreach ($item->rolluprules as $rolluprule) {
$rollup = new stdClass();
$rollup->scoid = $id;
$rollup->childactivityset = $rolluprule->childactivityset;
$rollup->minimumcount = $rolluprule->minimumcount;
$rollup->minimumpercent = $rolluprule->minimumpercent;
$rollup->rollupruleaction = $rolluprule->rollupruleaction;
$rollup->conditioncombination = $rolluprule->conditioncombination;
$rollupruleid = $DB->insert_record('scorm_seq_rolluprule', $rollup);
if (isset($rollup->conditions)) {
foreach ($rollup->conditions as $condition) {
$cond = new stdClass();
$cond->scoid = $rollup->scoid;
$cond->rollupruleid = $rollupruleid;
$cond->operator = $condition->operator;
$cond->cond = $condition->cond;
$conditionid = $DB->insert_record('scorm_seq_rolluprulecond', $cond);
}
}
}
}
if (isset($item->objectives)) {
foreach ($item->objectives as $objective) {
$obj = new stdClass();
$obj->scoid = $id;
$obj->primaryobj = $objective->primaryobj;
$obj->satisfiedbumeasure = $objective->satisfiedbymeasure;
$obj->objectiveid = $objective->objectiveid;
$obj->minnormalizedmeasure = trim($objective->minnormalizedmeasure);
$objectiveid = $DB->insert_record('scorm_seq_objective', $obj);
if (isset($objective->mapinfos)) {
foreach ($objective->mapinfos as $objmapinfo) {
$mapinfo = new stdClass();
$mapinfo->scoid = $id;
$mapinfo->objectiveid = $objectiveid;
$mapinfo->targetobjectiveid = $objmapinfo->targetobjectiveid;
$mapinfo->readsatisfiedstatus = $objmapinfo->readsatisfiedstatus;
$mapinfo->writesatisfiedstatus = $objmapinfo->writesatisfiedstatus;
$mapinfo->readnormalizedmeasure = $objmapinfo->readnormalizedmeasure;
$mapinfo->writenormalizedmeasure = $objmapinfo->writenormalizedmeasure;
$mapinfoid = $DB->insert_record('scorm_seq_mapinfo', $mapinfo);
}
}
}
}
if (empty($defaultorgid) && ((empty($scoes->defaultorg)) || ($scoes->defaultorg == $identifier))) {
$defaultorgid = $id;
}
}
}
}
if (!empty($olditems)) {
foreach ($olditems as $olditem) {
$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)) {
$scoes->version = 'SCORM_1.2';
}
$DB->set_field('scorm', 'version', $scoes->version, array('id' => $scorm->id));
$scorm->version = $scoes->version;
}
$scorm->launch = 0;
// Check launch sco is valid.
if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && !empty($newscoes[$defaultorgid]->launch)) {
// Launch param is valid - do nothing.
$scorm->launch = $defaultorgid;
} else if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && empty($newscoes[$defaultorgid]->launch)) {
// The launch is probably the default org so we need to find the first launchable item inside this org.
$sqlselect = 'scorm = ? AND sortorder >= ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true);
// We use get_records here as we need to pass a limit in the query that works cross db.
$scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id, $firstinorg), 'sortorder', 'id', 0, 1);
if (!empty($scoes)) {
$sco = reset($scoes); // We only care about the first record - the above query only returns one.
$scorm->launch = $sco->id;
}
}
if (empty($scorm->launch)) {
// No valid Launch is specified - find the first launchable sco instead.
$sqlselect = 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true);
// We use get_records here as we need to pass a limit in the query that works cross db.
$scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id), 'sortorder', 'id', 0, 1);
if (!empty($scoes)) {
$sco = reset($scoes); // We only care about the first record - the above query only returns one.
$scorm->launch = $sco->id;
}
}
return true;
}
function scorm_optionals_data($item, $standarddata) {
$result = array();
$sequencingdata = array('sequencingrules', 'rolluprules', 'objectives');
foreach ($item as $element => $value) {
if (! in_array($element, $standarddata)) {
if (! in_array($element, $sequencingdata)) {
$result[] = $element;
}
}
}
return $result;
}
function scorm_is_leaf($sco) {
global $DB;
if ($DB->record_exists('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier))) {
return false;
}
return true;
}
function scorm_get_parent($sco) {
global $DB;
if ($sco->parent != '/') {
if ($parent = $DB->get_record('scorm_scoes', array('scorm' => $sco->scorm, 'identifier' => $sco->parent))) {
return scorm_get_sco($parent->id);
}
}
return null;
}
function scorm_get_children($sco) {
global $DB;
$children = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier), 'sortorder, id');
if (!empty($children)) {
return $children;
}
return null;
}
function scorm_get_available_children($sco) {
global $DB;
$res = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier), 'sortorder, id');
if (!$res || $res == null) {
return false;
} else {
foreach ($res as $sco) {
$result[] = $sco;
}
return $result;
}
}
function scorm_get_available_descendent($descend, $sco) {
if ($sco == null) {
return $descend;
} else {
$avchildren = scorm_get_available_children($sco);
foreach ($avchildren as $avchild) {
array_push($descend, $avchild);
}
foreach ($avchildren as $avchild) {
scorm_get_available_descendent($descend, $avchild);
}
}
}
function scorm_get_siblings($sco) {
global $DB;
if ($siblings = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->parent), 'sortorder, id')) {
unset($siblings[$sco->id]);
if (!empty($siblings)) {
return $siblings;
}
}
return null;
}
// Get an array that contains all the parent scos for this sco.
function scorm_get_ancestors($sco) {
$ancestors = array();
$continue = true;
while ($continue) {
$ancestor = scorm_get_parent($sco);
if (!empty($ancestor) && $ancestor->id !== $sco->id) {
$sco = $ancestor;
$ancestors[] = $ancestor;
if ($sco->parent == '/') {
$continue = false;
}
} else {
$continue = false;
}
}
return $ancestors;
}
function scorm_get_preorder(&$preorder = array(), $sco = null) {
if ($sco != null) {
array_push($preorder, $sco);
if ($children = scorm_get_children($sco)) {
foreach ($children as $child) {
scorm_get_preorder($preorder, $child);
}
}
}
return $preorder;
}
function scorm_find_common_ancestor($ancestors, $sco) {
$pos = scorm_array_search('identifier', $sco->parent, $ancestors);
if ($sco->parent != '/') {
if ($pos === false) {
return scorm_find_common_ancestor($ancestors, scorm_get_parent($sco));
}
}
return $pos;
}
/* Usage
Grab some XML data, either from a file, URL, etc. however you want. Assume storage in $strYourXML;
$objXML = new xml2Array();
$arroutput = $objXML->parse($strYourXML);
print_r($arroutput); //print it out, or do whatever!
*/
class xml2Array {
public $arroutput = array();
public $resparser;
public $strxmldata;
/**
* Convert a utf-8 string to html entities
*
* @param string $str The UTF-8 string
* @return string
*/
public function utf8_to_entities($str) {
global $CFG;
$entities = '';
$values = array();
$lookingfor = 1;
return $str;
}
/**
* Parse an XML text string and create an array tree that rapresent the XML structure
*
* @param string $strinputxml The XML string
* @return array
*/
public function parse($strinputxml) {
$this->resparser = xml_parser_create ('UTF-8');
xml_set_object($this->resparser, $this);
xml_set_element_handler($this->resparser, "tagopen", "tagclosed");
xml_set_character_data_handler($this->resparser, "tagdata");
$this->strxmldata = xml_parse($this->resparser, $strinputxml );
if (!$this->strxmldata) {
die(sprintf("XML error: %s at line %d",
xml_error_string(xml_get_error_code($this->resparser)),
xml_get_current_line_number($this->resparser)));
}
xml_parser_free($this->resparser);
return $this->arroutput;
}
public function tagopen($parser, $name, $attrs) {
$tag = array("name" => $name, "attrs" => $attrs);
array_push($this->arroutput, $tag);
}
public function tagdata($parser, $tagdata) {
if (trim($tagdata)) {
if (isset($this->arroutput[count($this->arroutput) - 1]['tagData'])) {
$this->arroutput[count($this->arroutput) - 1]['tagData'] .= $this->utf8_to_entities($tagdata);
} else {
$this->arroutput[count($this->arroutput) - 1]['tagData'] = $this->utf8_to_entities($tagdata);
}
}
}
public function tagclosed($parser, $name) {
$this->arroutput[count($this->arroutput) - 2]['children'][] = $this->arroutput[count($this->arroutput) - 1];
array_pop($this->arroutput);
}
}
@@ -0,0 +1,63 @@
<?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/>.
require_once('../../../config.php');
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$id = optional_param('id', '', PARAM_INT); // Course Module ID, or
$a = optional_param('a', '', PARAM_INT); // scorm ID.
$scoid = required_param('scoid', PARAM_INT); // Sco ID.
$attempt = required_param('attempt', PARAM_INT); // Attempt number.
$function = required_param('function', PARAM_RAW); // Function to call.
$request = optional_param('request', '', PARAM_RAW); // Scorm ID.
if (!empty($id)) {
$cm = get_coursemodule_from_id('scorm', $id, 0, false, MUST_EXIST);
$course = $DB->get_record("course", array("id" => $cm->course), '*', MUST_EXIST);
$scorm = $DB->get_record("scorm", array("id" => $cm->instance), '*', MUST_EXIST);
} else if (!empty($a)) {
$scorm = $DB->get_record("scorm", array("id" => $a), '*', MUST_EXIST);
$course = $DB->get_record("course", array("id" => $scorm->course), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance("scorm", $scorm->id, $course->id, false, MUST_EXIST);
} else {
throw new \moodle_exception('missingparameter');
}
$PAGE->set_url('/mod/scorm/datamodels/sequencinghandler.php',
array('scoid' => $scoid, 'attempt' => $attempt, 'id' => $cm->id, 'function' => $function, 'request' => $request));
require_login($course, false, $cm);
if (!empty($scoid) && !empty($function)) {
require_once($CFG->dirroot.'/mod/scorm/datamodels/scorm_13lib.php');
if (has_capability('mod/scorm:savetrack', context_module::instance($cm->id))) {
$result = null;
switch ($function) {
case 'scorm_seq_flow' :
if ($request == 'forward' || $request == 'backward') {
$seq = scorm_seq_navigation ($scoid, $USER->id, $request.'_', $attempt);
$sco = scorm_get_sco($scoid);
$seq = scorm_seq_flow($sco, $request, $seq, true, $USER->id);
if (!empty($seq->nextactivity)) {
scorm_seq_end_attempt($sco, $USER->id, $seq);
}
}
echo json_encode($seq);
break;
}
}
}
File diff suppressed because it is too large Load Diff
+101
View File
@@ -0,0 +1,101 @@
<?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/>.
/**
* Capability definitions for the quiz module.
*
* @package mod_scorm
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'mod/scorm:addinstance' => array(
'riskbitmask' => RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/course:manageactivities'
),
'mod/scorm:viewreport' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
'mod/scorm:skipview' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW
)
),
'mod/scorm:savetrack' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
'mod/scorm:viewscores' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
'mod/scorm:deleteresponses' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
'mod/scorm:deleteownresponses' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array()
)
);
+33
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',
]
];
+246
View File
@@ -0,0 +1,246 @@
<?xml version="1.0" encoding="UTF-8" ?>
<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"
>
<TABLES>
<TABLE NAME="scorm" COMMENT="each table is one SCORM module and its configuration">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="scormtype" TYPE="char" LENGTH="50" NOTNULL="true" DEFAULT="local" SEQUENCE="false" COMMENT="local, external or repository"/>
<FIELD NAME="reference" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="intro" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="version" TYPE="char" LENGTH="9" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="maxgrade" TYPE="float" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="grademethod" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="whatgrade" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="maxattempt" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="forcecompleted" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="forcenewattempt" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="lastattemptlock" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="masteryoverride" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="displayattemptstatus" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="displaycoursestructure" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="updatefreq" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Define when the package must be automatically update"/>
<FIELD NAME="sha1hash" TYPE="char" LENGTH="40" NOTNULL="false" SEQUENCE="false" COMMENT="package content or ext path hash"/>
<FIELD NAME="md5hash" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false" COMMENT="MD5 Hash of package file"/>
<FIELD NAME="revision" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="revison number"/>
<FIELD NAME="launch" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="skipview" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="hidebrowse" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="hidetoc" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="nav" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="navpositionleft" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="-100" SEQUENCE="false"/>
<FIELD NAME="navpositiontop" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="-100" SEQUENCE="false"/>
<FIELD NAME="auto" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="popup" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="options" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="width" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="100" SEQUENCE="false"/>
<FIELD NAME="height" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="600" SEQUENCE="false"/>
<FIELD NAME="timeopen" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timeclose" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionstatusrequired" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="completionscorerequired" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="completionstatusallscos" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="autocommit" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="course" UNIQUE="false" FIELDS="course"/>
</INDEXES>
</TABLE>
<TABLE NAME="scorm_scoes" COMMENT="each SCO part of the SCORM module">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="scorm" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="manifest" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="organization" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="parent" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="identifier" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="launch" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="scormtype" TYPE="char" LENGTH="5" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="title" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="order of scoes"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scorm" TYPE="foreign" FIELDS="scorm" REFTABLE="scorm" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_scoes_data" COMMENT="Contains variable data get from packages">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="value" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scorm_scoes_data_scoid" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id" COMMENT="The relative sco"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_seq_objective" COMMENT="SCORM2004 objective description">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="primaryobj" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="objectiveid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="satisfiedbymeasure" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="minnormalizedmeasure" TYPE="float" LENGTH="11" NOTNULL="true" DEFAULT="0.0000" SEQUENCE="false" DECIMALS="4"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scorm_objective_uniq" TYPE="unique" FIELDS="scoid, id"/>
<KEY NAME="scorm_objective_scoid" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id" COMMENT="The relative sco"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_seq_mapinfo" COMMENT="SCORM2004 objective mapinfo description">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="objectiveid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="targetobjectiveid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="readsatisfiedstatus" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="readnormalizedmeasure" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="writesatisfiedstatus" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="writenormalizedmeasure" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scorm_mapinfo_uniq" TYPE="unique" FIELDS="scoid, id, objectiveid"/>
<KEY NAME="scorm_mapinfo_scoid" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id" COMMENT="The relative sco"/>
<KEY NAME="scorm_mapinfo_objectiveid" TYPE="foreign" FIELDS="objectiveid" REFTABLE="scorm_seq_objective" REFFIELDS="id" COMMENT="The relative objective"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_seq_ruleconds" COMMENT="SCORM2004 rule conditions">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="conditioncombination" TYPE="char" LENGTH="3" NOTNULL="true" DEFAULT="all" SEQUENCE="false"/>
<FIELD NAME="ruletype" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="action" TYPE="char" LENGTH="25" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scorm_ruleconds_un" TYPE="unique" FIELDS="scoid, id"/>
<KEY NAME="scorm_ruleconds_scoid" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id" COMMENT="The relative sco"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_seq_rulecond" COMMENT="SCORM2004 rule condition">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="ruleconditionsid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="refrencedobjective" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="measurethreshold" TYPE="float" LENGTH="11" NOTNULL="true" DEFAULT="0.0000" SEQUENCE="false" DECIMALS="4"/>
<FIELD NAME="operator" TYPE="char" LENGTH="5" NOTNULL="true" DEFAULT="noOp" SEQUENCE="false"/>
<FIELD NAME="cond" TYPE="char" LENGTH="30" NOTNULL="true" DEFAULT="always" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scorm_rulecond_uniq" TYPE="unique" FIELDS="id, scoid, ruleconditionsid"/>
<KEY NAME="scorm_rulecond_scoid" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id" COMMENT="The relative sco"/>
<KEY NAME="scorm_rulecond_ruleconditionsid" TYPE="foreign" FIELDS="ruleconditionsid" REFTABLE="scorm_seq_ruleconds" REFFIELDS="id" COMMENT="The relative rulecondition"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_seq_rolluprule" COMMENT="SCORM2004 sequencing rule">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="childactivityset" TYPE="char" LENGTH="15" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="minimumcount" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="minimumpercent" TYPE="float" LENGTH="11" NOTNULL="true" DEFAULT="0.0000" SEQUENCE="false" DECIMALS="4"/>
<FIELD NAME="conditioncombination" TYPE="char" LENGTH="3" NOTNULL="true" DEFAULT="all" SEQUENCE="false"/>
<FIELD NAME="action" TYPE="char" LENGTH="15" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scorm_rolluprule_uniq" TYPE="unique" FIELDS="scoid, id"/>
<KEY NAME="scorm_rolluprule_scoid" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id" COMMENT="The relative sco"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_seq_rolluprulecond" COMMENT="SCORM2004 sequencing rule">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="rollupruleid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="operator" TYPE="char" LENGTH="5" NOTNULL="true" DEFAULT="noOp" SEQUENCE="false"/>
<FIELD NAME="cond" TYPE="char" LENGTH="25" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scorm_rulluprulecond_uniq" TYPE="unique" FIELDS="scoid, rollupruleid, id"/>
<KEY NAME="scorm_rolluprulecond_scoid" TYPE="foreign" FIELDS="scoid" REFTABLE="scorm_scoes" REFFIELDS="id" COMMENT="The relative sco"/>
<KEY NAME="scorm_rolluprulecond_rolluprule" TYPE="foreign" FIELDS="rollupruleid" REFTABLE="scorm_seq_rolluprule" REFFIELDS="id" COMMENT="The relative rolluprule"/>
</KEYS>
</TABLE>
<TABLE NAME="scorm_aicc_session" COMMENT="Used by AICC HACP to store session information">
<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" COMMENT="id from user table"/>
<FIELD NAME="scormid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="id from scorm table"/>
<FIELD NAME="hacpsession" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="sessionid used to authenticate AICC HACP communication"/>
<FIELD NAME="scoid" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="id from scorm_scoes table"/>
<FIELD NAME="scormmode" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="scormstatus" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="attempt" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="lessonstatus" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="sessiontime" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="time this session was created"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="time this session was last used"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="scormid" TYPE="foreign" FIELDS="scormid" REFTABLE="scorm" REFFIELDS="id"/>
<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>
+33
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/>.
/**
* Definition of log events
*
* @package mod_scorm
* @category log
* @copyright 2010 Petr Skoda (http://skodak.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$logs = array(
array('module' => 'scorm', 'action' => 'view', 'mtable' => 'scorm', 'field' => 'name'),
array('module' => 'scorm', 'action' => 'review', 'mtable' => 'scorm', 'field' => 'name'),
array('module' => 'scorm', 'action' => 'update', 'mtable' => 'scorm', 'field' => 'name'),
array('module' => 'scorm', 'action' => 'add', 'mtable' => 'scorm', 'field' => 'name'),
);
+112
View File
@@ -0,0 +1,112 @@
<?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 external functions and service definitions.
*
* @package mod_scorm
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
$functions = array(
'mod_scorm_view_scorm' => array(
'classname' => 'mod_scorm_external',
'methodname' => 'view_scorm',
'description' => 'Trigger the course module viewed event.',
'type' => 'write',
'capabilities' => '',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_scorm_get_scorm_attempt_count' => array(
'classname' => 'mod_scorm_external',
'methodname' => 'get_scorm_attempt_count',
'description' => 'Return the number of attempts done by a user in the given SCORM.',
'type' => 'read',
'capabilities' => '',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_scorm_get_scorm_scoes' => array(
'classname' => 'mod_scorm_external',
'methodname' => 'get_scorm_scoes',
'description' => 'Returns a list containing all the scoes data related to the given scorm id',
'type' => 'read',
'capabilities' => '',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_scorm_get_scorm_user_data' => array(
'classname' => 'mod_scorm_external',
'methodname' => 'get_scorm_user_data',
'description' => 'Retrieves user tracking and SCO data and default SCORM values',
'type' => 'read',
'capabilities' => '',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_scorm_insert_scorm_tracks' => array(
'classname' => 'mod_scorm_external',
'methodname' => 'insert_scorm_tracks',
'description' => 'Saves a scorm tracking record.
It will overwrite any existing tracking data for this attempt.
Validation should be performed before running the function to ensure the user will not lose any existing
attempt data.',
'type' => 'write',
'capabilities' => 'mod/scorm:savetrack',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_scorm_get_scorm_sco_tracks' => array(
'classname' => 'mod_scorm_external',
'methodname' => 'get_scorm_sco_tracks',
'description' => 'Retrieves SCO tracking data for the given user id and attempt number',
'type' => 'read',
'capabilities' => '',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_scorm_get_scorms_by_courses' => array(
'classname' => 'mod_scorm_external',
'methodname' => 'get_scorms_by_courses',
'description' => 'Returns a list of scorm instances in a provided set of courses, if
no courses are provided then all the scorm instances the user has access to will be returned.',
'type' => 'read',
'capabilities' => '',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_scorm_launch_sco' => array(
'classname' => 'mod_scorm_external',
'methodname' => 'launch_sco',
'description' => 'Trigger the SCO launched event.',
'type' => 'write',
'capabilities' => '',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_scorm_get_scorm_access_information' => array(
'classname' => 'mod_scorm_external',
'methodname' => 'get_scorm_access_information',
'description' => 'Return capabilities information for a given scorm.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
);
+5
View File
@@ -0,0 +1,5 @@
{
"plugintypes": {
"scormreport": "mod\/scorm\/report"
}
}
+38
View File
@@ -0,0 +1,38 @@
<?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/>.
/**
* Definition of Forum scheduled tasks.
*
* @package mod_scorm
* @category task
* @copyright 2017 Abhishek kumar <ganitgenius@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = array(
array(
'classname' => 'mod_scorm\task\cron_task',
'blocking' => 0,
'minute' => '*/5',
'hour' => '*',
'day' => '*',
'month' => '*',
'dayofweek' => '*'
)
);
+197
View File
@@ -0,0 +1,197 @@
<?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/>.
/**
* Upgrade script for the scorm module.
*
* @package mod_scorm
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @global moodle_database $DB
* @param int $oldversion
* @return bool
*/
function xmldb_scorm_upgrade($oldversion) {
global $DB, $OUTPUT;
$dbman = $DB->get_manager();
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// 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');
}
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2023100901) {
// MDL-79967 - fix up any possible activity completion states since the upgrade to 2023042403.
// Get timestamp of when this site updated to version 2023042403.
$upgraded = $DB->get_field_sql("SELECT min(timemodified)
FROM {upgrade_log}
WHERE plugin = 'mod_scorm' AND version = '2023042403'");
if (empty($upgraded)) {
// The code causing this regression landed upstream 28 Jul 2023, if a site has done a fresh install since then,
// the upgrade step won't exist - set it to 20th July so we don't end up dealing with too many attempts.
$upgraded = 1689811200; // 20 July 2023 12AM.
}
// Don't bother triggering this next step if the upgrade completed within the last hour.
if (time() - HOURSECS > $upgraded) {
// Get all attempts that have occurred since the upgrade.
$sql = "SELECT DISTINCT s.id, sa.userid
FROM {scorm} s
JOIN {scorm_attempt} sa ON sa.scormid = s.id
JOIN {scorm_scoes_value} sv on sv.attemptid = sa.id
WHERE sv.timemodified > ?";
$scorms = $DB->get_recordset_sql($sql, [$upgraded]);
foreach ($scorms as $scorm) {
// Run an ad-hoc task to update the grades.
$task = new \mod_scorm\task\update_grades();
$task->set_custom_data([
'scormid' => $scorm->id,
'userid' => $scorm->userid,
]);
\core\task\manager::queue_adhoc_task($task, true);
}
$scorms->close();
}
// Scorm savepoint reached.
upgrade_mod_savepoint(true, 2023100901, 'scorm');
}
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+31
View File
@@ -0,0 +1,31 @@
<?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/>.
/**
* List of deprecated mod_scorm functions.
*
* @package mod_scorm
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @deprecated since Moodle 3.11
*/
function scorm_get_completion_state() {
$completionclass = \mod_scorm\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}
+48
View File
@@ -0,0 +1,48 @@
<?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/>.
/**
* Redirect the user based on their capabilities to either a scorm activity or to scorm reports
*
* @package mod_scorm
* @category grade
* @copyright 2010 onwards Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("../../config.php");
$id = required_param('id', PARAM_INT); // Course module ID.
if (! $cm = get_coursemodule_from_id('scorm', $id)) {
throw new \moodle_exception('invalidcoursemodule');
}
if (! $scorm = $DB->get_record('scorm', array('id' => $cm->instance))) {
throw new \moodle_exception('invalidcoursemodule');
}
if (! $course = $DB->get_record('course', array('id' => $scorm->course))) {
throw new \moodle_exception('coursemisconf');
}
require_login($course, false, $cm);
if (has_capability('mod/scorm:viewreport', context_module::instance($cm->id))) {
redirect('report.php?id='.$cm->id);
} else {
redirect('view.php?id='.$cm->id);
}
+119
View File
@@ -0,0 +1,119 @@
<?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/>.
require_once("../../config.php");
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$id = required_param('id', PARAM_INT); // Course id.
$PAGE->set_url('/mod/scorm/index.php', array('id' => $id));
if (!empty($id)) {
if (!$course = $DB->get_record('course', array('id' => $id))) {
throw new \moodle_exception('invalidcourseid');
}
} else {
throw new \moodle_exception('missingparameter');
}
require_course_login($course);
$PAGE->set_pagelayout('incourse');
// Trigger instances list viewed event.
$event = \mod_scorm\event\course_module_instance_list_viewed::create(array('context' => context_course::instance($course->id)));
$event->add_record_snapshot('course', $course);
$event->trigger();
$strscorm = get_string("modulename", "scorm");
$strscorms = get_string("modulenameplural", "scorm");
$strname = get_string("name");
$strsummary = get_string("summary");
$strreport = get_string("report", 'scorm');
$strlastmodified = get_string("lastmodified");
$PAGE->set_title($strscorms);
$PAGE->set_heading($course->fullname);
$PAGE->navbar->add($strscorms);
echo $OUTPUT->header();
$usesections = course_format_uses_sections($course->format);
if ($usesections) {
$sortorder = "cw.section ASC";
} else {
$sortorder = "m.timemodified DESC";
}
if (! $scorms = get_all_instances_in_course("scorm", $course)) {
notice(get_string('thereareno', 'moodle', $strscorms), "../../course/view.php?id=$course->id");
exit;
}
$table = new html_table();
if ($usesections) {
$strsectionname = get_string('sectionname', 'format_'.$course->format);
$table->head = array ($strsectionname, $strname, $strsummary, $strreport);
$table->align = array ("center", "left", "left", "left");
} else {
$table->head = array ($strlastmodified, $strname, $strsummary, $strreport);
$table->align = array ("left", "left", "left", "left");
}
foreach ($scorms as $scorm) {
$context = context_module::instance($scorm->coursemodule);
$tt = "";
if ($usesections) {
if ($scorm->section) {
$tt = get_section_name($course, $scorm->section);
}
} else {
$tt = userdate($scorm->timemodified);
}
$report = '&nbsp;';
$reportshow = '&nbsp;';
if (has_capability('mod/scorm:viewreport', $context)) {
$trackedusers = scorm_get_count_users($scorm->id, $scorm->groupingid);
if ($trackedusers > 0) {
$reportshow = html_writer::link('report.php?id='.$scorm->coursemodule,
get_string('viewallreports', 'scorm', $trackedusers));
} else {
$reportshow = get_string('noreports', 'scorm');
}
} else if (has_capability('mod/scorm:viewscores', $context)) {
require_once('locallib.php');
$report = scorm_grade_user($scorm, $USER->id);
$reportshow = get_string('score', 'scorm').": ".$report;
}
$options = (object)array('noclean' => true);
if (!$scorm->visible) {
// Show dimmed if the mod is hidden.
$table->data[] = array ($tt, html_writer::link('view.php?id='.$scorm->coursemodule,
format_string($scorm->name),
array('class' => 'dimmed')),
format_module_intro('scorm', $scorm, $scorm->coursemodule), $reportshow);
} else {
// Show normal if the mod is visible.
$table->data[] = array ($tt, html_writer::link('view.php?id='.$scorm->coursemodule, format_string($scorm->name)),
format_module_intro('scorm', $scorm, $scorm->coursemodule), $reportshow);
}
}
echo html_writer::empty_tag('br');
echo html_writer::table($table);
echo $OUTPUT->footer();
+1
View File
@@ -0,0 +1 @@
completionscorerequired_help,mod_scorm
+460
View File
@@ -0,0 +1,460 @@
<?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/>.
/**
* Strings for component 'scorm', language 'en'
*
* @package mod_scorm
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['toc'] = 'TOC';
$string['navigation'] = 'Navigation';
$string['aicchacptimeout'] = 'AICC HACP timeout';
$string['aicchacptimeout_desc'] = 'Length of time in minutes that an external AICC HACP session can remain open';
$string['aicchacpkeepsessiondata'] = 'AICC HACP session data';
$string['aicchacpkeepsessiondata_desc'] = 'Length of time in days to keep the external AICC HACP session data (a high setting will fill up the table with old data but may be useful when debugging)';
$string['aiccuserid'] = 'AICC pass numeric user id';
$string['aiccuserid_desc'] = 'The AICC standard for usernames is very restrictive compared with Moodle, and allows for alphanumeric characters, dash and underscore only. Periods, spaces and the @ symbol are not permitted. If enabled, user ID numbers are passed to the AICC package instead of usernames.';
$string['activation'] = 'Activation';
$string['activityloading'] = 'You will be automatically redirected to the activity in';
$string['activityoverview'] = 'You have SCORM packages that need attention';
$string['activitypleasewait'] = 'Activity loading, please wait ...';
$string['adminsettings'] = 'Admin settings';
$string['advanced'] = 'Parameters';
$string['aliasonly'] = 'When selecting an imsmanifest.xml file from a repository you must use an alias/shortcut for this file.';
$string['allowapidebug'] = 'Activate API debug and tracing (set the capture mask with apidebugmask)';
$string['allowtypeexternal'] = 'Enable external package type';
$string['allowtypeexternalaicc'] = 'Enable direct AICC URL';
$string['allowtypeexternalaicc_desc'] = 'If enabled this allows a direct url to a simple AICC package';
$string['allowtypelocalsync'] = 'Enable downloaded package type';
$string['allowtypeaicchacp'] = 'Enable external AICC HACP';
$string['allowtypeaicchacp_desc'] = 'If enabled this allows AICC HACP external communication without requiring user login for post requests from the external AICC package';
$string['apidebugmask'] = 'API debug capture mask - use a simple regex on &lt;username&gt;:&lt;activityname&gt; e.g. admin:.* will debug for admin user only';
$string['areacontent'] = 'Content files';
$string['areapackage'] = 'Package file';
$string['asset'] = 'Asset';
$string['assetlaunched'] = 'Asset - Viewed';
$string['attempt'] = 'Attempt';
$string['attempts'] = 'Attempts';
$string['attemptstatusall'] = 'Dashboard and entry page';
$string['attemptstatusmy'] = 'Dashboard only';
$string['attemptstatusentry'] = 'Entry page only';
$string['attemptsx'] = '{$a} attempts';
$string['attemptsmanagement'] = 'Attempts management';
$string['attempt1'] = '1 attempt';
$string['attr_error'] = 'Bad value for attribute ({$a->attr}) in tag {$a->tag}.';
$string['autocommit'] = 'Auto-commit';
$string['autocommit_help'] = 'If enabled, SCORM data is automaticaly saved to the database. Useful for SCORM objects which do not save their data regularly.';
$string['autocommitdesc'] = 'Automatically save SCORM data if the SCORM package does not save it.';
$string['autocontinue'] = 'Auto-continue';
$string['autocontinue_help'] = 'If enabled, subsequent learning objects are launched automatically, otherwise the Continue button must be used.';
$string['autocontinuedesc'] = 'If enabled, subsequent learning objects are launched automatically, otherwise the Continue button must be used.';
$string['averageattempt'] = 'Average attempts';
$string['badmanifest'] = 'Some manifest errors: see errors log';
$string['badimsmanifestlocation'] = 'An imsmanifest.xml file was found but it was not in the root of your zip file, please re-package your SCORM';
$string['badarchive'] = 'You must provide a valid zip file';
$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';
$string['cannotaccess'] = 'You cannot call this script in that way';
$string['cannotfindsco'] = 'Could not find SCO';
$string['closebeforeopen'] = 'You have specified a close date before the open date.';
$string['collapsetocwinsize'] = 'Collapse TOC when window size below';
$string['collapsetocwinsizedesc'] = 'This setting lets you specify the window size below which the TOC should automatically collapse.';
$string['compatibilitysettings'] = 'Compatibility settings';
$string['completed'] = 'Completed';
$string['completiondetail:completionstatuspassed'] = 'Pass the activity';
$string['completiondetail:completionstatuscompleted'] = 'Complete the activity';
$string['completiondetail:completionstatuscompletedorpassed'] = 'Complete or pass the activity';
$string['completiondetail:completionscore'] = 'Receive a score of {$a} or more';
$string['completiondetail:allscos'] = 'Do all parts of this activity';
$string['completionscorerequired'] = 'Require minimum score';
$string['completionscorerequireddesc'] = 'Minimum score of {$a} is required for completion';
$string['completionstatus_passed'] = 'Passed';
$string['completionstatus_completed'] = 'Completed';
$string['completionstatusallscos'] = 'All SCOs must return completion status';
$string['completionstatusallscos_help'] = 'Some SCORM packages contain multiple components or "scos" - when this is enabled all scos within the package must return the relevant lesson_status for this activity to be flagged complete.';
$string['completionstatusrequired'] = 'Require status';
$string['completionstatusrequireddesc'] = 'Student must achieve at least one of the following statuses: {$a}';
$string['completionstatusrequired_help'] = 'Checking one or more statuses will require a user to achieve at least one of the checked statuses in order to be marked complete in this SCORM activity, as well as any other Activity Completion requirements.';
$string['confirmloosetracks'] = 'WARNING: The package seems to be changed or modified. If the package structure is changed, some users tracks may be lost during update process.';
$string['contents'] = 'Contents';
$string['coursepacket'] = 'Course package';
$string['coursestruct'] = 'Content structure';
$string['crontask'] = 'Background processing for SCORM';
$string['currentwindow'] = 'Current window';
$string['datadir'] = 'Filesystem error: Can\'t create course data directory';
$string['defaultdisplaysettings'] = 'Default display settings';
$string['defaultgradesettings'] = 'Default grade settings';
$string['defaultothersettings'] = 'Other default settings';
$string['deleteattemptcheck'] = 'Are you absolutely sure you want to completely delete these attempts?';
$string['deleteallattempts'] = 'Delete all SCORM attempts';
$string['deleteselected'] = 'Delete selected attempts';
$string['deleteuserattemptcheck'] = 'Are you absolutely sure you want to completely delete all your attempts?';
$string['details'] = 'Track details';
$string['directories'] = 'Show the directory links';
$string['disabled'] = 'Disabled';
$string['display'] = 'Display package';
$string['displayattemptstatus'] = 'Display attempt status';
$string['displayattemptstatus_help'] = 'This preference allows a summary of the users attempts to show in the course overview block in Dashboard and/or the SCORM entry page.';
$string['displayattemptstatusdesc'] = 'Whether a summary of the user\'s attempts is shown in the course overview block in Dashboard and/or the SCORM entry page.';
$string['displaycoursestructure'] = 'Display content structure on entry page';
$string['displaycoursestructure_help'] = 'If enabled, the table of contents is displayed on the SCORM outline page.';
$string['displaycoursestructuredesc'] = 'If enabled, the table of contents is displayed on the SCORM outline page.';
$string['displaydesc'] = 'Whether to display the SCORM package in a new window.';
$string['displaysettings'] = 'Display settings';
$string['dnduploadscorm'] = 'Add a SCORM package';
$string['domxml'] = 'DOMXML external library';
$string['element'] = 'Element';
$string['enter'] = 'Enter';
$string['entercourse'] = 'Enter course';
$string['errorlogs'] = 'Errors log';
$string['eventattemptdeleted'] = 'Attempt deleted';
$string['eventinteractionsviewed'] = 'Interactions viewed';
$string['eventreportviewed'] = 'Report viewed';
$string['eventscolaunched'] = 'Sco launched';
$string['eventscorerawsubmitted'] = 'Submitted SCORM raw score';
$string['eventstatussubmitted'] = 'Submitted SCORM status';
$string['eventtracksviewed'] = 'Tracks viewed';
$string['eventuserreportviewed'] = 'User report viewed';
$string['everyday'] = 'Every day';
$string['everytime'] = 'Every time it\'s used';
$string['exceededmaxattempts'] = 'You have reached the maximum number of attempts.';
$string['exit'] = 'Exit course';
$string['exitactivity'] = 'Exit activity';
$string['expired'] = 'Sorry, this activity closed on {$a} and is no longer available';
$string['external'] = 'Update external packages timing';
$string['failed'] = 'Failed';
$string['finishscorm'] = 'If you have finished viewing this resource, {$a}';
$string['finishscormlinkname'] = 'click here to return to the course page';
$string['firstaccess'] = 'First access';
$string['firstattempt'] = 'First attempt';
$string['floating'] = 'Floating';
$string['forcecompleted'] = 'Force completed';
$string['forcecompleted_help'] = 'If enabled, the status of the current attempt is forced to "completed". (Only applicable to SCORM 1.2 packages.)';
$string['forcecompleteddesc'] = 'This preference sets the default value for the force completed setting';
$string['forcenewattempts'] = 'Force new attempt';
$string['forcenewattempts_help'] = 'There are 3 options:
* No - If a previous attempt is completed, passed or failed, the student will be provided with the option to enter in review mode or start a new attempt.
* When previous attempt completed, passed or failed - This relies on the SCORM package setting the status of \'completed\', \'passed\' or \'failed\'.
* Always - Each re-entry to the SCORM activity will generate a new attempt and the student will not be returned to the same point they reached in their previous attempt.';
$string['forceattemptalways'] = 'Always';
$string['forceattemptoncomplete'] = 'When previous attempt completed, passed or failed';
$string['forcejavascript'] = 'Force users to enable JavaScript';
$string['forcejavascript_desc'] = 'If enabled (recommended) this prevents access to SCORM objects when JavaScript is not supported/enabled in a users browser. If disabled the user may view the SCORM but API communication will fail and no grade information will be saved.';
$string['forcejavascriptmessage'] = 'JavaScript is required to view this object, please enable JavaScript in your browser and try again.';
$string['found'] = 'Manifest found';
$string['frameheight'] = 'The height of the stage frame or window.';
$string['framewidth'] = 'The width of the stage frame or window.';
$string['fromleft'] = 'From left';
$string['fromtop'] = 'From top';
$string['fullscreen'] = 'Fill the whole screen';
$string['general'] = 'General data';
$string['gradeaverage'] = 'Average grade';
$string['gradeforattempt'] = 'Grade for attempt';
$string['gradehighest'] = 'Highest grade';
$string['grademethod'] = 'Grading method';
$string['grademethod_help'] = 'The grading method defines how the grade for a single attempt of the activity is determined.
There are 4 grading methods:
* Learning objects - The number of completed/passed learning objects
* Highest grade - The highest score obtained in all passed learning objects
* Average grade - The mean of all the scores
* Sum grade - The sum of all the scores';
$string['grademethoddesc'] = 'The grading method defines how the grade for a single attempt of the activity is determined.';
$string['gradereported'] = 'Grade reported';
$string['gradesettings'] = 'Grade settings';
$string['gradescoes'] = 'Learning objects';
$string['gradesum'] = 'Sum grade';
$string['height'] = 'Height';
$string['hidden'] = 'Hidden';
$string['hidebrowse'] = 'Disable preview mode';
$string['hidebrowse_help'] = 'Preview mode allows a student to browse an activity before attempting it. If preview mode is disabled, the preview button is hidden.';
$string['hidebrowsedesc'] = 'Preview mode allows a student to browse an activity before attempting it.';
$string['hideexit'] = 'Hide exit link';
$string['hidereview'] = 'Hide review button';
$string['hidetoc'] = 'Display content structure in player';
$string['hidetoc_help'] = 'How the table of contents is displayed in the SCORM player';
$string['hidetocdesc'] = 'This setting specifies how the table of contents is displayed in the SCORM player.';
$string['highestattempt'] = 'Highest attempt';
$string['chooseapacket'] = 'Choose or update a package';
$string['identifier'] = 'Question identifier';
$string['incomplete'] = 'Incomplete';
$string['indicator:cognitivedepth'] = 'SCORM cognitive';
$string['indicator:cognitivedepth_help'] = 'This indicator is based on the cognitive depth reached by the student in a SCORM activity.';
$string['indicator:cognitivedepthdef'] = 'SCORM cognitive';
$string['indicator:cognitivedepthdef_help'] = 'The participant has reached this percentage of the cognitive engagement offered by the SCORM activities during this analysis interval (Levels = No view, View, Submit, View feedback)';
$string['indicator:cognitivedepthdef_link'] = 'Learning_analytics_indicators#Cognitive_depth';
$string['indicator:socialbreadth'] = 'SCORM social';
$string['indicator:socialbreadth_help'] = 'This indicator is based on the social breadth reached by the student in a SCORM activity.';
$string['indicator:socialbreadthdef'] = 'SCORM social';
$string['indicator:socialbreadthdef_help'] = 'The participant has reached this percentage of the social engagement offered by the SCORM activities during this analysis interval (Levels = No participation, Participant alone)';
$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';
$string['myattempts'] = 'My attempts';
$string['myaiccsessions'] = 'My AICC sessions';
$string['repositorynotsupported'] = 'This repository does not support linking directly to an imsmanifest.xml file.';
$string['trackid'] = 'ID';
$string['trackid_help'] = 'This is the identifier set by your SCORM package for this question, the SCORM specification doesn\'t allow the full question text to be provided.';
$string['trackcorrectcount'] = 'Correct count';
$string['trackcorrectcount_help'] = 'Number of correct results for the question';
$string['trackpattern'] = 'Pattern';
$string['trackpattern_help'] = 'This is what a correct response to this question would be, it does not show the learners response.';
$string['tracklatency'] = 'Latency';
$string['tracklatency_help'] = 'The time elapsed between the time the question was made available to the learner for a response and the time of the first response.';
$string['trackresponse'] = 'Response';
$string['trackresponse_help'] = 'This is the response made by the learner for this question';
$string['trackresult'] = 'Result';
$string['trackresult_help'] = 'Shows if the learner entered a correct response.';
$string['trackscoremin'] = 'Minimum score';
$string['trackscoremin_help'] = 'Minimum value that can be assigned for the raw score';
$string['trackscoremax'] = 'Maximum score';
$string['trackscoremax_help'] = 'Maximum value that can be assigned for the raw score';
$string['trackscoreraw'] = 'Raw score';
$string['trackscoreraw_help'] = 'Number that reflects the performance of the learner relative to the range bounded by the values of min and max';
$string['tracksuspenddata'] = 'Suspend data';
$string['tracksuspenddata_help'] = 'Provides space to store and retrieve data between learner sessions';
$string['tracktime'] = 'Time';
$string['tracktime_help'] = 'Time at which the attempt was started';
$string['tracktype'] = 'Type';
$string['tracktype_help'] = 'Type of the question, for example "choice" or "shortanswer".';
$string['trackweight'] = 'Weight';
$string['trackweight_help'] = 'Weight assigned to the question when calculating score.';
$string['invalidactivity'] = 'SCORM activity is incorrect';
$string['invalidmanifestname'] = 'Only imsmanifest.xml or .zip files may be selected';
$string['invalidstatus'] = 'Invalid status';
$string['invalidurl'] = 'Invalid URL specified';
$string['invalidurlhttpcheck'] = 'Invalid URL specified. Debug message:<pre>{$a->cmsg}</pre>';
$string['invalidhacpsession'] = 'Invalid HACP session';
$string['invalidmanifestresource'] = 'WARNING: The following resources were referenced in your manifest but couldn\'t be found:';
$string['last'] = 'Last accessed on';
$string['lastaccess'] = 'Last access';
$string['lastattempt'] = 'Last completed attempt';
$string['lastattemptlock'] = 'Lock after final attempt';
$string['lastattemptlock_help'] = 'If enabled, a student is prevented from launching the SCORM player after using up all their allocated attempts.';
$string['lastattemptlockdesc'] = 'If enabled, a student is prevented from launching the SCORM player after using up all their allocated attempts.';
$string['location'] = 'Show the location bar';
$string['max'] = 'Max score';
$string['maximumattempts'] = 'Number of attempts';
$string['maximumattempts_help'] = 'This setting enables the number of attempts to be restricted. It is only applicable for SCORM 1.2 and AICC packages.';
$string['maximumattemptsdesc'] = 'This preference sets the default maximum attempts for an activity';
$string['maximumgradedesc'] = 'This preference sets the default maximum grade for an activity';
$string['menubar'] = 'Show the menu bar';
$string['min'] = 'Minimum score';
$string['minimumscoregreater'] = 'Minimum score must be greater than 0.';
$string['missing_attribute'] = 'Missing attribute {$a->attr} in tag {$a->tag}';
$string['missingparam'] = 'A required parameter is missing or wrong';
$string['missing_tag'] = 'Missing tag {$a->tag}';
$string['mode'] = 'Mode';
$string['modulename'] = 'SCORM package';
$string['modulename_help'] = 'A SCORM package is a collection of files which are packaged according to an agreed standard for learning objects. The SCORM activity module enables SCORM or AICC packages to be uploaded as a zip file and added to a course.
Content is usually displayed over several pages, with navigation between the pages. There are various options for displaying content in a pop-up window, with a table of contents, with navigation buttons etc. SCORM activities generally include questions, with grades being recorded in the gradebook.
SCORM activities may be used
* For presenting multimedia content and animations
* As an assessment tool';
$string['modulename_link'] = 'mod/scorm/view';
$string['modulenameplural'] = 'SCORM packages';
$string['nav'] = 'Show Navigation';
$string['nav_help'] = 'This setting specifies whether to show or hide the navigation buttons and their position.
There are 3 options:
* No - Navigation buttons are not shown
* Under content - Navigation buttons are shown below the SCORM package content
* Floating - Navigation buttons are shown floating, with the position from the top and from the left determined by the package.';
$string['navdesc'] = 'This setting specifies whether to show or hide navigation buttons and their position.';
$string['navpositionleft'] = 'Position of navigation buttons from left in pixels.';
$string['navpositiontop'] = 'Position of navigation buttons from top in pixels.';
$string['networkdropped'] = 'The SCORM player has determined that your Internet connection is unreliable or has been interrupted. If you continue in this SCORM activity, your progress may not be saved.<br />
You should exit the activity now, and return when you have a dependable Internet connection.';
$string['newattempt'] = 'Start a new attempt';
$string['next'] = 'Continue';
$string['noactivity'] = 'Nothing to report';
$string['noattemptsallowed'] = 'Number of attempts allowed';
$string['noattemptsmade'] = 'Number of attempts you have made';
$string['no_attributes'] = 'Tag {$a->tag} must have attributes';
$string['no_children'] = 'Tag {$a->tag} must have children';
$string['nolimit'] = 'Unlimited attempts';
$string['nomanifest'] = 'Incorrect file package - missing imsmanifest.xml or AICC structure';
$string['noprerequisites'] = 'Sorry but you don\'t have the required prerequisites to access this activity.';
$string['noreports'] = 'No report to display';
$string['normal'] = 'Normal';
$string['noscriptnoscorm'] = 'Your browser does not support JavaScript or it has JavaScript support disabled. This SCORM package may not play or save data correctly.';
$string['notattempted'] = 'Not attempted';
$string['not_corr_type'] = 'Type mismatch for tag {$a->tag}';
$string['notopenyet'] = 'Sorry, this activity is not available until {$a}';
$string['objectives'] = 'Objectives';
$string['openafterclose'] = 'You have specified an open date after the close date';
$string['optallstudents'] = 'all users';
$string['optattemptsonly'] = 'users with attempts only';
$string['optnoattemptsonly'] = 'users with no attempts only';
$string['options'] = 'Options (Prevented by some browsers)';
$string['optionsadv'] = 'Options (Advanced)';
$string['optionsadv_desc'] = 'If checked the width and height will be listed as advanced settings.';
$string['organization'] = 'Organisation';
$string['organizations'] = 'Organisations';
$string['othersettings'] = 'Additional settings';
$string['page-mod-scorm-x'] = 'Any SCORM module page';
$string['pagesize'] = 'Page size';
$string['package'] = 'Package file';
$string['package_help'] = 'The package file is a zip (or pif) file containing SCORM/AICC course definition files.';
$string['packagedir'] = 'Filesystem error: Can\'t create package directory';
$string['packagefile'] = 'No package file specified';
$string['packagehdr'] = 'Package';
$string['packageurl'] = 'URL';
$string['packageurl_help'] = 'This setting enables a URL for the SCORM package to be specified, rather than choosing a file via the file picker.';
$string['passed'] = 'Passed';
$string['php5'] = 'PHP 5 (DOMXML native library)';
$string['pluginadministration'] = 'SCORM package administration';
$string['pluginname'] = 'SCORM package';
$string['popup'] = 'New window';
$string['popuplaunched'] = 'This SCORM package has been launched in a popup window, If you have finished viewing this resource, click here to return to the course page';
$string['popupmenu'] = 'In a drop-down menu';
$string['popupopen'] = 'Open package in a new window';
$string['popupsblocked'] = 'It appears that popup windows are blocked, stopping this SCORM package from playing. Please check your browser settings before trying again.';
$string['position_error'] = 'The {$a->tag} tag can\'t be child of {$a->parent} tag';
$string['preferencesuser'] = 'Preferences for this report';
$string['preferencespage'] = 'Preferences just for this page';
$string['prev'] = 'Previous';
$string['privacy:metadata:aicc:data'] = 'Personal data passed through from the AICC/SCORM subsystem.';
$string['privacy:metadata:aicc:externalpurpose'] = 'This plugin sends data externally using the AICC HACP.';
$string['privacy:metadata:aicc_session:lessonstatus'] = 'The lesson status to be tracked';
$string['privacy:metadata:aicc_session:scormmode'] = 'The mode of the element to be tracked';
$string['privacy:metadata:aicc_session:scormstatus'] = 'The status of the element to be tracked';
$string['privacy:metadata:aicc_session:sessiontime'] = 'The session time to be tracked';
$string['privacy:metadata:aicc_session:timecreated'] = 'The time when the tracked element was created';
$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_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';
$string['protectpackagedownloads_desc'] = 'If enabled, SCORM packages can be downloaded only if the user has the course:manageactivities capability. If disabled, SCORM packages can always be downloaded (by mobile or other means).';
$string['raw'] = 'Raw score';
$string['regular'] = 'Regular manifest';
$string['report'] = 'Report';
$string['reports'] = 'Reports';
$string['reportcountallattempts'] = '{$a->nbattempts} attempts for {$a->nbusers} users, out of {$a->nbresults} results';
$string['reportcountattempts'] = '{$a->nbresults} results ({$a->nbusers} users)';
$string['response'] = 'Response';
$string['result'] = 'Result';
$string['results'] = 'Results';
$string['review'] = 'Review';
$string['reviewmode'] = 'Review mode';
$string['rightanswer'] = 'Right answer';
$string['scormstandard'] = 'SCORM standards mode';
$string['scormstandarddesc'] = 'When disabled, Moodle allows SCORM 1.2 packages to store more than the specification allows, and uses Moodle full name format settings when passing the users name to the SCORM package.';
$string['scoes'] = 'Learning objects';
$string['score'] = 'Score';
$string['scorm:addinstance'] = 'Add a new SCORM package';
$string['scormclose'] = 'Available to';
$string['scormcourse'] = 'Learning course';
$string['scorm:deleteresponses'] = 'Delete SCORM attempts';
$string['scormloggingoff'] = 'API logging is off';
$string['scormloggingon'] = 'API logging is on';
$string['scormopen'] = 'Available from';
$string['scormresponsedeleted'] = 'Deleted user attempts';
$string['scorm:deleteownresponses'] = 'Delete own attempts';
$string['scorm:savetrack'] = 'Save tracks';
$string['scorm:skipview'] = 'Skip overview';
$string['scormtype'] = 'Type';
$string['scormtype_help'] = 'This setting determines how the package is included in the course. There are up to 4 options:
* Uploaded package - Enables a SCORM package to be chosen via the file picker
* External SCORM manifest - Enables an imsmanifest.xml URL to be specified. Note: If the URL has a different domain name than your site, then "Downloaded package" is a better option, since otherwise grades are not saved.
* Downloaded package - Enables a package URL to be specified. The package will be unzipped and saved locally, and updated when the external SCORM package is updated.
* External AICC URL - this URL is the launch URL for a single AICC Activity. A psuedo package will be constructed around this.';
$string['scorm:viewreport'] = 'View reports';
$string['scorm:viewscores'] = 'View scores';
$string['scrollbars'] = 'Allow the window to be scrolled';
$string['search:activity'] = 'SCORM package - activity information';
$string['selectall'] = 'Select all';
$string['selectnone'] = 'Deselect all';
$string['show'] = 'Show';
$string['sided'] = 'To the side';
$string['skipview'] = 'Student skip content structure page';
$string['skipview_help'] = 'This setting specifies whether the content structure page should ever be skipped (not displayed). If the package contains only one learning object, the content structure page can always be skipped.';
$string['skipviewdesc'] = 'This preference sets the default for when to skip content structure for a page';
$string['slashargs'] = 'WARNING: slash arguments is disabled on this site and objects may not function as expected!';
$string['stagesize'] = 'Stage size';
$string['stagesize_help'] = 'These two settings specify the frame/window width and height for the learning objects.';
$string['started'] = 'Started on';
$string['status'] = 'Status';
$string['statusbar'] = 'Show the status bar';
$string['student_response'] = 'Response';
$string['subplugintype_scormreport'] = 'Report';
$string['subplugintype_scormreport_plural'] = 'Reports';
$string['suspended'] = 'Suspended';
$string['syntax'] = 'Syntax error';
$string['tag_error'] = 'Unknown tag ({$a->tag}) with this content: {$a->value}';
$string['time'] = 'Time';
$string['title'] = 'Title';
$string['toolbar'] = 'Show the toolbar';
$string['too_many_attributes'] = 'Tag {$a->tag} has too many attributes';
$string['too_many_children'] = 'Tag {$a->tag} has too many children';
$string['totaltime'] = 'Time';
$string['trackingloose'] = 'WARNING: The tracking data of this package will be lost!';
$string['type'] = 'Type';
$string['typeaiccurl'] = 'External AICC URL';
$string['typeexternal'] = 'External SCORM manifest';
$string['typelocal'] = 'Uploaded package';
$string['typelocalsync'] = 'Downloaded package';
$string['undercontent'] = 'Under content';
$string['unziperror'] = 'An error occurs during package unzip';
$string['updatefreq'] = 'Auto-update frequency';
$string['updatefreq_error'] = 'Auto-update frequency can only be set when the package file is hosted externally';
$string['updatefreq_help'] = 'This allows the external package to be automatically downloaded and updated';
$string['updatefreqdesc'] = 'This preference sets the default auto-update frequency of an activity';
$string['validateascorm'] = 'Validate a package';
$string['validation'] = 'Validation result';
$string['validationtype'] = 'This preference set the DOMXML library used for validating SCORM Manifest. If you don\'t know leave the selected choice.';
$string['value'] = 'Value';
$string['versionwarning'] = 'The manifest version is older than 1.3, warning at {$a->tag} tag';
$string['viewallreports'] = 'View reports for {$a} attempts';
$string['viewalluserreports'] = 'View reports for {$a} users';
$string['whatgrade'] = 'Attempts grading';
$string['whatgrade_help'] = 'If multiple attempts are allowed, this setting specifies whether the highest, average (mean), first or last completed attempt is recorded in the gradebook. The last completed attempt option does not include attempts with a \'failed\' status.
Notes on handling of multiple attempts:
* The option to start a new attempt is provided by a checkbox above the Enter button on the content structure page, so be sure you\'re providing access to that page if you want to allow more than one attempt.
* Some SCORM packages are intelligent about new attempts, many are not. What this means is that if the learner re-enters an existing attempt, if the SCORM content does not have internal logic to avoid overwriting previous attempts they can be overwritten, even though the attempt was \'completed\' or \'passed\'.
* The settings "Force completed", "Force new attempt" and "Lock after final attempt" also provide further management of multiple attempts.';
$string['whatgradedesc'] = 'Whether the highest, average (mean), first or last completed attempt is recorded in the gradebook if multiple attempts are allowed.';
$string['width'] = 'Width';
$string['window'] = 'Window';
$string['youmustselectastatus'] = 'You must select a status to require';
// Deprecated since Moodle 4.3.
$string['completionscorerequired_help'] = 'Enabling this setting will require a user to have at least the minimum score entered to be marked complete in this SCORM activity, as well as any other Activity Completion requirements.';
+1859
View File
File diff suppressed because it is too large Load Diff
+167
View File
@@ -0,0 +1,167 @@
<?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/>.
require_once('../../config.php');
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$id = optional_param('id', '', PARAM_INT); // Course Module ID, or
$a = optional_param('a', '', PARAM_INT); // scorm ID
$scoid = required_param('scoid', PARAM_INT); // sco ID.
$delayseconds = 2; // Delay time before sco launch, used to give time to browser to define API.
if (!empty($id)) {
if (! $cm = get_coursemodule_from_id('scorm', $id)) {
throw new \moodle_exception('invalidcoursemodule');
}
if (! $course = $DB->get_record('course', array('id' => $cm->course))) {
throw new \moodle_exception('coursemisconf');
}
if (! $scorm = $DB->get_record('scorm', array('id' => $cm->instance))) {
throw new \moodle_exception('invalidcoursemodule');
}
} else if (!empty($a)) {
if (! $scorm = $DB->get_record('scorm', array('id' => $a))) {
throw new \moodle_exception('coursemisconf');
}
if (! $course = $DB->get_record('course', array('id' => $scorm->course))) {
throw new \moodle_exception('coursemisconf');
}
if (! $cm = get_coursemodule_from_instance('scorm', $scorm->id, $course->id)) {
throw new \moodle_exception('invalidcoursemodule');
}
} else {
throw new \moodle_exception('missingparameter');
}
$PAGE->set_url('/mod/scorm/loadSCO.php', array('scoid' => $scoid, 'id' => $cm->id));
if (!isloggedin()) { // Prevent login page from being shown in iframe.
// Using simple html instead of exceptions here as shown inside iframe/object.
echo html_writer::start_tag('html');
echo html_writer::tag('head', '');
echo html_writer::tag('body', get_string('loggedinnot'));
echo html_writer::end_tag('html');
exit;
}
require_login($course, false, $cm, false); // Call require_login anyway to set up globals correctly.
// Check if SCORM is available.
scorm_require_available($scorm);
$context = context_module::instance($cm->id);
// Forge SCO URL.
list($sco, $scolaunchurl) = scorm_get_sco_and_launch_url($scorm, $scoid, $context);
if ($sco->scormtype == 'asset') {
$attempt = scorm_get_last_attempt($scorm->id, $USER->id);
$element = (scorm_version_check($scorm->version, SCORM_13)) ? 'cmi.completion_status' : 'cmi.core.lesson_status';
$value = 'completed';
scorm_insert_track($USER->id, $scorm->id, $sco->id, $attempt, $element, $value);
}
// Trigger the SCO launched event.
scorm_launch_sco($scorm, $sco, $cm, $context, $scolaunchurl);
header('Content-Type: text/html; charset=UTF-8');
if ($sco->scormtype == 'asset') {
// HTTP 302 Found => Moved Temporarily.
header('Location: ' . $scolaunchurl);
// Provide a short feedback in case of slow network connection.
echo html_writer::start_tag('html');
echo html_writer::tag('body', html_writer::tag('p', get_string('activitypleasewait', 'scorm')));
echo html_writer::end_tag('html');
exit;
}
// We expect a SCO: select which API are we looking for.
$lmsapi = (scorm_version_check($scorm->version, SCORM_12) || empty($scorm->version)) ? 'API' : 'API_1484_11';
echo html_writer::start_tag('html');
echo html_writer::start_tag('head');
echo html_writer::tag('title', 'LoadSCO');
?>
<script type="text/javascript">
//<![CDATA[
var myApiHandle = null;
var myFindAPITries = 0;
function myGetAPIHandle() {
myFindAPITries = 0;
if (myApiHandle == null) {
myApiHandle = myGetAPI();
}
return myApiHandle;
}
function myFindAPI(win) {
while ((win.<?php echo $lmsapi; ?> == null) && (win.parent != null) && (win.parent != win)) {
myFindAPITries++;
// Note: 7 is an arbitrary number, but should be more than sufficient
if (myFindAPITries > 7) {
return null;
}
win = win.parent;
}
return win.<?php echo $lmsapi; ?>;
}
// hun for the API - needs to be loaded before we can launch the package
function myGetAPI() {
var theAPI = myFindAPI(window);
if ((theAPI == null) && (window.opener != null) && (typeof(window.opener) != "undefined")) {
theAPI = myFindAPI(window.opener);
}
if (theAPI == null) {
return null;
}
return theAPI;
}
function doredirect() {
if (myGetAPIHandle() != null) {
location = "<?php echo $scolaunchurl ?>";
}
else {
document.body.innerHTML = "<p><?php echo get_string('activityloading', 'scorm');?>" +
"<span id='countdown'><?php echo $delayseconds ?></span> " +
"<?php echo get_string('numseconds', 'moodle', '');?>. &nbsp; " +
"<?php echo addslashes($OUTPUT->pix_icon('wait', '', 'scorm')); ?></p>";
var e = document.getElementById("countdown");
var cSeconds = parseInt(e.innerHTML);
var timer = setInterval(function() {
if( cSeconds && myGetAPIHandle() == null ) {
e.innerHTML = --cSeconds;
} else {
clearInterval(timer);
document.body.innerHTML = "<p><?php echo get_string('activitypleasewait', 'scorm');?></p>";
location = "<?php echo $scolaunchurl ?>";
}
}, 1000);
}
}
//]]>
</script>
<noscript>
<meta http-equiv="refresh" content="0;url=<?php echo $scolaunchurl ?>" />
</noscript>
<?php
echo html_writer::end_tag('head');
echo html_writer::tag('body', html_writer::tag('p', get_string('activitypleasewait', 'scorm')), array('onload' => "doredirect();"));
echo html_writer::end_tag('html');
File diff suppressed because it is too large Load Diff
+608
View File
@@ -0,0 +1,608 @@
<?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/>.
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); // It must be included from a Moodle page.
}
require_once($CFG->dirroot.'/course/moodleform_mod.php');
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
class mod_scorm_mod_form extends moodleform_mod {
public function definition() {
global $CFG, $COURSE, $OUTPUT;
$cfgscorm = get_config('scorm');
$mform = $this->_form;
if (!$CFG->slasharguments) {
$mform->addElement('static', '', '', $OUTPUT->notification(get_string('slashargs', 'scorm'), 'notifyproblem'));
}
$mform->addElement('header', 'general', get_string('general', 'form'));
// Name.
$mform->addElement('text', 'name', get_string('name'));
if (!empty($CFG->formatstringstriptags)) {
$mform->setType('name', PARAM_TEXT);
} else {
$mform->setType('name', PARAM_CLEANHTML);
}
$mform->addRule('name', null, 'required', null, 'client');
$mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
// Summary.
$this->standard_intro_elements();
// Package.
$mform->addElement('header', 'packagehdr', get_string('packagehdr', 'scorm'));
$mform->setExpanded('packagehdr', true);
// Scorm types.
$scormtypes = array(SCORM_TYPE_LOCAL => get_string('typelocal', 'scorm'));
if ($cfgscorm->allowtypeexternal) {
$scormtypes[SCORM_TYPE_EXTERNAL] = get_string('typeexternal', 'scorm');
}
if ($cfgscorm->allowtypelocalsync) {
$scormtypes[SCORM_TYPE_LOCALSYNC] = get_string('typelocalsync', 'scorm');
}
if ($cfgscorm->allowtypeexternalaicc) {
$scormtypes[SCORM_TYPE_AICCURL] = get_string('typeaiccurl', 'scorm');
}
// Reference.
if (count($scormtypes) > 1) {
$mform->addElement('select', 'scormtype', get_string('scormtype', 'scorm'), $scormtypes);
$mform->setType('scormtype', PARAM_ALPHA);
$mform->addHelpButton('scormtype', 'scormtype', 'scorm');
$mform->addElement('text', 'packageurl', get_string('packageurl', 'scorm'), array('size' => 60));
$mform->setType('packageurl', PARAM_RAW);
$mform->addHelpButton('packageurl', 'packageurl', 'scorm');
$mform->hideIf('packageurl', 'scormtype', 'eq', SCORM_TYPE_LOCAL);
} else {
$mform->addElement('hidden', 'scormtype', SCORM_TYPE_LOCAL);
$mform->setType('scormtype', PARAM_ALPHA);
}
// New local package upload.
$filemanageroptions = array();
$filemanageroptions['accepted_types'] = array('.zip', '.xml');
$filemanageroptions['maxbytes'] = 0;
$filemanageroptions['maxfiles'] = 1;
$filemanageroptions['subdirs'] = 0;
$mform->addElement('filemanager', 'packagefile', get_string('package', 'scorm'), null, $filemanageroptions);
$mform->addHelpButton('packagefile', 'package', 'scorm');
$mform->hideIf('packagefile', 'scormtype', 'noteq', SCORM_TYPE_LOCAL);
// Update packages timing.
$mform->addElement('select', 'updatefreq', get_string('updatefreq', 'scorm'), scorm_get_updatefreq_array());
$mform->setType('updatefreq', PARAM_INT);
$mform->setDefault('updatefreq', $cfgscorm->updatefreq);
$mform->addHelpButton('updatefreq', 'updatefreq', 'scorm');
// Display Settings.
$mform->addElement('header', 'displaysettings', get_string('appearance'));
// Framed / Popup Window.
$mform->addElement('select', 'popup', get_string('display', 'scorm'), scorm_get_popup_display_array());
$mform->setDefault('popup', $cfgscorm->popup);
$mform->setAdvanced('popup', $cfgscorm->popup_adv);
// Width.
$mform->addElement('text', 'width', get_string('width', 'scorm'), 'maxlength="5" size="5"');
$mform->setDefault('width', $cfgscorm->framewidth);
$mform->setType('width', PARAM_INT);
$mform->setAdvanced('width', $cfgscorm->framewidth_adv);
$mform->hideIf('width', 'popup', 'eq', 0);
// Height.
$mform->addElement('text', 'height', get_string('height', 'scorm'), 'maxlength="5" size="5"');
$mform->setDefault('height', $cfgscorm->frameheight);
$mform->setType('height', PARAM_INT);
$mform->setAdvanced('height', $cfgscorm->frameheight_adv);
$mform->hideIf('height', 'popup', 'eq', 0);
// Window Options.
$winoptgrp = array();
foreach (scorm_get_popup_options_array() as $key => $value) {
$winoptgrp[] = &$mform->createElement('checkbox', $key, '', get_string($key, 'scorm'));
$mform->setDefault($key, $value);
}
$mform->addGroup($winoptgrp, 'winoptgrp', get_string('options', 'scorm'), '<br />', false);
$mform->hideIf('winoptgrp', 'popup', 'eq', 0);
$mform->setAdvanced('winoptgrp', $cfgscorm->winoptgrp_adv);
// Skip view page.
$skipviewoptions = scorm_get_skip_view_array();
$mform->addElement('select', 'skipview', get_string('skipview', 'scorm'), $skipviewoptions);
$mform->addHelpButton('skipview', 'skipview', 'scorm');
$mform->setDefault('skipview', $cfgscorm->skipview);
$mform->setAdvanced('skipview', $cfgscorm->skipview_adv);
// Hide Browse.
$mform->addElement('selectyesno', 'hidebrowse', get_string('hidebrowse', 'scorm'));
$mform->addHelpButton('hidebrowse', 'hidebrowse', 'scorm');
$mform->setDefault('hidebrowse', $cfgscorm->hidebrowse);
$mform->setAdvanced('hidebrowse', $cfgscorm->hidebrowse_adv);
// Display course structure.
$mform->addElement('selectyesno', 'displaycoursestructure', get_string('displaycoursestructure', 'scorm'));
$mform->addHelpButton('displaycoursestructure', 'displaycoursestructure', 'scorm');
$mform->setDefault('displaycoursestructure', $cfgscorm->displaycoursestructure);
$mform->setAdvanced('displaycoursestructure', $cfgscorm->displaycoursestructure_adv);
// Toc display.
$mform->addElement('select', 'hidetoc', get_string('hidetoc', 'scorm'), scorm_get_hidetoc_array());
$mform->addHelpButton('hidetoc', 'hidetoc', 'scorm');
$mform->setDefault('hidetoc', $cfgscorm->hidetoc);
$mform->setAdvanced('hidetoc', $cfgscorm->hidetoc_adv);
$mform->disabledIf('hidetoc', 'scormtype', 'eq', SCORM_TYPE_AICCURL);
// Navigation panel display.
$mform->addElement('select', 'nav', get_string('nav', 'scorm'), scorm_get_navigation_display_array());
$mform->addHelpButton('nav', 'nav', 'scorm');
$mform->setDefault('nav', $cfgscorm->nav);
$mform->setAdvanced('nav', $cfgscorm->nav_adv);
$mform->hideIf('nav', 'hidetoc', 'noteq', SCORM_TOC_SIDE);
// Navigation panel position from left.
$mform->addElement('text', 'navpositionleft', get_string('fromleft', 'scorm'), 'maxlength="5" size="5"');
$mform->setDefault('navpositionleft', $cfgscorm->navpositionleft);
$mform->setType('navpositionleft', PARAM_INT);
$mform->setAdvanced('navpositionleft', $cfgscorm->navpositionleft_adv);
$mform->hideIf('navpositionleft', 'hidetoc', 'noteq', SCORM_TOC_SIDE);
$mform->hideIf('navpositionleft', 'nav', 'noteq', SCORM_NAV_FLOATING);
// Navigation panel position from top.
$mform->addElement('text', 'navpositiontop', get_string('fromtop', 'scorm'), 'maxlength="5" size="5"');
$mform->setDefault('navpositiontop', $cfgscorm->navpositiontop);
$mform->setType('navpositiontop', PARAM_INT);
$mform->setAdvanced('navpositiontop', $cfgscorm->navpositiontop_adv);
$mform->hideIf('navpositiontop', 'hidetoc', 'noteq', SCORM_TOC_SIDE);
$mform->hideIf('navpositiontop', 'nav', 'noteq', SCORM_NAV_FLOATING);
// Display attempt status.
$mform->addElement('select', 'displayattemptstatus', get_string('displayattemptstatus', 'scorm'),
scorm_get_attemptstatus_array());
$mform->addHelpButton('displayattemptstatus', 'displayattemptstatus', 'scorm');
$mform->setDefault('displayattemptstatus', $cfgscorm->displayattemptstatus);
$mform->setAdvanced('displayattemptstatus', $cfgscorm->displayattemptstatus_adv);
// Availability.
$mform->addElement('header', 'availability', get_string('availability'));
$mform->addElement('date_time_selector', 'timeopen', get_string("scormopen", "scorm"), array('optional' => true));
$mform->addElement('date_time_selector', 'timeclose', get_string("scormclose", "scorm"), array('optional' => true));
// Grade Settings.
$mform->addElement('header', 'gradesettings', get_string('gradenoun'));
// Grade Method.
$mform->addElement('select', 'grademethod', get_string('grademethod', 'scorm'), scorm_get_grade_method_array());
$mform->addHelpButton('grademethod', 'grademethod', 'scorm');
$mform->setDefault('grademethod', $cfgscorm->grademethod);
// Maximum Grade.
for ($i = 0; $i <= 100; $i++) {
$grades[$i] = "$i";
}
$mform->addElement('select', 'maxgrade', get_string('maximumgrade'), $grades);
$mform->setDefault('maxgrade', $cfgscorm->maxgrade);
$mform->hideIf('maxgrade', 'grademethod', 'eq', GRADESCOES);
// Attempts management.
$mform->addElement('header', 'attemptsmanagementhdr', get_string('attemptsmanagement', 'scorm'));
// Max Attempts.
$mform->addElement('select', 'maxattempt', get_string('maximumattempts', 'scorm'), scorm_get_attempts_array());
$mform->addHelpButton('maxattempt', 'maximumattempts', 'scorm');
$mform->setDefault('maxattempt', $cfgscorm->maxattempt);
// What Grade.
$mform->addElement('select', 'whatgrade', get_string('whatgrade', 'scorm'), scorm_get_what_grade_array());
$mform->hideIf('whatgrade', 'maxattempt', 'eq', 1);
$mform->addHelpButton('whatgrade', 'whatgrade', 'scorm');
$mform->setDefault('whatgrade', $cfgscorm->whatgrade);
// Force new attempt.
$newattemptselect = scorm_get_forceattempt_array();
$mform->addElement('select', 'forcenewattempt', get_string('forcenewattempts', 'scorm'), $newattemptselect);
$mform->addHelpButton('forcenewattempt', 'forcenewattempts', 'scorm');
$mform->setDefault('forcenewattempt', $cfgscorm->forcenewattempt);
// Last attempt lock - lock the enter button after the last available attempt has been made.
$mform->addElement('selectyesno', 'lastattemptlock', get_string('lastattemptlock', 'scorm'));
$mform->addHelpButton('lastattemptlock', 'lastattemptlock', 'scorm');
$mform->setDefault('lastattemptlock', $cfgscorm->lastattemptlock);
// Compatibility settings.
$mform->addElement('header', 'compatibilitysettingshdr', get_string('compatibilitysettings', 'scorm'));
// Force completed.
$mform->addElement('selectyesno', 'forcecompleted', get_string('forcecompleted', 'scorm'));
$mform->addHelpButton('forcecompleted', 'forcecompleted', 'scorm');
$mform->setDefault('forcecompleted', $cfgscorm->forcecompleted);
// Autocontinue.
$mform->addElement('selectyesno', 'auto', get_string('autocontinue', 'scorm'));
$mform->addHelpButton('auto', 'autocontinue', 'scorm');
$mform->setDefault('auto', $cfgscorm->auto);
// Autocommit.
$mform->addElement('selectyesno', 'autocommit', get_string('autocommit', 'scorm'));
$mform->addHelpButton('autocommit', 'autocommit', 'scorm');
$mform->setDefault('autocommit', $cfgscorm->autocommit);
// Mastery score overrides status.
$mform->addElement('selectyesno', 'masteryoverride', get_string('masteryoverride', 'scorm'));
$mform->addHelpButton('masteryoverride', 'masteryoverride', 'scorm');
$mform->setDefault('masteryoverride', $cfgscorm->masteryoverride);
// Hidden Settings.
$mform->addElement('hidden', 'datadir', null);
$mform->setType('datadir', PARAM_RAW);
$mform->addElement('hidden', 'pkgtype', null);
$mform->setType('pkgtype', PARAM_RAW);
$mform->addElement('hidden', 'launch', null);
$mform->setType('launch', PARAM_RAW);
$mform->addElement('hidden', 'redirect', null);
$mform->setType('redirect', PARAM_RAW);
$mform->addElement('hidden', 'redirecturl', null);
$mform->setType('redirecturl', PARAM_RAW);
$this->standard_coursemodule_elements();
// A SCORM module should define this within itself and is not needed here.
$suffix = $this->get_suffix();
$completionpassgradeel = 'completionpassgrade' . $suffix;
// The 'completionpassgrade' is a radio element with multiple options, so we should remove all of them.
while ($mform->elementExists($completionpassgradeel)) {
$mform->removeElement($completionpassgradeel);
}
// Buttons.
$this->add_action_buttons();
}
public function data_preprocessing(&$defaultvalues) {
global $CFG, $COURSE;
if (isset($defaultvalues['popup']) && ($defaultvalues['popup'] == 1) && isset($defaultvalues['options'])) {
if (!empty($defaultvalues['options'])) {
$options = explode(',', $defaultvalues['options']);
foreach ($options as $option) {
list($element, $value) = explode('=', $option);
$element = trim($element);
$defaultvalues[$element] = trim($value);
}
}
}
if (isset($defaultvalues['grademethod'])) {
$defaultvalues['grademethod'] = intval($defaultvalues['grademethod']);
}
if (isset($defaultvalues['width']) && (strpos($defaultvalues['width'], '%') === false)
&& ($defaultvalues['width'] <= 100)) {
$defaultvalues['width'] .= '%';
}
if (isset($defaultvalues['height']) && (strpos($defaultvalues['height'], '%') === false)
&& ($defaultvalues['height'] <= 100)) {
$defaultvalues['height'] .= '%';
}
$scorms = get_all_instances_in_course('scorm', $COURSE);
$coursescorm = current($scorms);
$draftitemid = file_get_submitted_draft_itemid('packagefile');
file_prepare_draft_area($draftitemid, $this->context->id, 'mod_scorm', 'package', 0,
array('subdirs' => 0, 'maxfiles' => 1));
$defaultvalues['packagefile'] = $draftitemid;
if (($COURSE->format == 'singleactivity') && ((count($scorms) == 0) || ($defaultvalues['instance'] == $coursescorm->id))) {
$defaultvalues['redirect'] = 'yes';
$defaultvalues['redirecturl'] = $CFG->wwwroot.'/course/view.php?id='.$defaultvalues['course'];
} else {
$defaultvalues['redirect'] = 'no';
$defaultvalues['redirecturl'] = $CFG->wwwroot.'/mod/scorm/view.php?id='.$defaultvalues['coursemodule'];
}
if (isset($defaultvalues['version'])) {
$defaultvalues['pkgtype'] = (substr($defaultvalues['version'], 0, 5) == 'SCORM') ? 'scorm' : 'aicc';
}
if (isset($defaultvalues['instance'])) {
$defaultvalues['datadir'] = $defaultvalues['instance'];
}
if (empty($defaultvalues['timeopen'])) {
$defaultvalues['timeopen'] = 0;
}
if (empty($defaultvalues['timeclose'])) {
$defaultvalues['timeclose'] = 0;
}
// Set some completion default data.
$suffix = $this->get_suffix();
$completionstatusrequiredel = 'completionstatusrequired' . $suffix;
$cvalues = array();
if (!empty($defaultvalues[$completionstatusrequiredel]) && !is_array($defaultvalues[$completionstatusrequiredel])) {
// Unpack values.
foreach (scorm_status_options() as $key => $value) {
if (($defaultvalues[$completionstatusrequiredel] & $key) == $key) {
$cvalues[$key] = 1;
}
}
} else if (empty($this->_instance) && !array_key_exists($completionstatusrequiredel, $defaultvalues)) {
// When in add mode, set a default completion rule that requires the SCORM's status be set to "Completed".
$cvalues[4] = 1;
}
if (!empty($cvalues)) {
$defaultvalues[$completionstatusrequiredel] = $cvalues;
}
$completionscorerequiredel = 'completionscorerequired' . $suffix;
if (isset($defaultvalues[$completionscorerequiredel])) {
$completionscoreenabledel = 'completionscoreenabled' . $suffix;
$defaultvalues[$completionscoreenabledel] = 1;
}
}
public function validation($data, $files) {
global $CFG, $USER;
$errors = parent::validation($data, $files);
$type = $data['scormtype'];
if ($type === SCORM_TYPE_LOCAL) {
if (empty($data['packagefile'])) {
$errors['packagefile'] = get_string('required');
} else {
$draftitemid = file_get_submitted_draft_itemid('packagefile');
file_prepare_draft_area($draftitemid, $this->context->id, 'mod_scorm', 'packagefilecheck', null,
array('subdirs' => 0, 'maxfiles' => 1));
// Get file from users draft area.
$usercontext = context_user::instance($USER->id);
$fs = get_file_storage();
$files = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', false);
if (count($files) < 1) {
$errors['packagefile'] = get_string('required');
return $errors;
}
$file = reset($files);
if (!$file->is_external_file() && !empty($data['updatefreq'])) {
// Make sure updatefreq is not set if using normal local file.
$errors['updatefreq'] = get_string('updatefreq_error', 'mod_scorm');
}
if (strtolower($file->get_filename()) == 'imsmanifest.xml') {
if (!$file->is_external_file()) {
$errors['packagefile'] = get_string('aliasonly', 'mod_scorm');
} else {
$repository = repository::get_repository_by_id($file->get_repository_id(), context_system::instance());
if (!$repository->supports_relative_file()) {
$errors['packagefile'] = get_string('repositorynotsupported', 'mod_scorm');
}
}
} else if (strtolower(substr($file->get_filename(), -3)) == 'xml') {
$errors['packagefile'] = get_string('invalidmanifestname', 'mod_scorm');
} else {
// Validate this SCORM package.
$errors = array_merge($errors, scorm_validate_package($file));
}
}
} else if ($type === SCORM_TYPE_EXTERNAL) {
$reference = $data['packageurl'];
// Syntax check.
if (!preg_match('/(http:\/\/|https:\/\/|www).*\/imsmanifest.xml$/i', $reference)) {
$errors['packageurl'] = get_string('invalidurl', 'scorm');
} else {
// Availability check.
$result = scorm_check_url($reference);
if (is_string($result)) {
$errors['packageurl'] = $result;
}
}
} else if ($type === 'packageurl') {
$reference = $data['reference'];
// Syntax check.
if (!preg_match('/(http:\/\/|https:\/\/|www).*(\.zip|\.pif)$/i', $reference)) {
$errors['packageurl'] = get_string('invalidurl', 'scorm');
} else {
// Availability check.
$result = scorm_check_url($reference);
if (is_string($result)) {
$errors['packageurl'] = $result;
}
}
} else if ($type === SCORM_TYPE_AICCURL) {
$reference = $data['packageurl'];
// Syntax check.
if (!preg_match('/(http:\/\/|https:\/\/|www).*/', $reference)) {
$errors['packageurl'] = get_string('invalidurl', 'scorm');
} else {
// Availability check.
$result = scorm_check_url($reference);
if (is_string($result)) {
$errors['packageurl'] = $result;
}
}
}
// Validate availability dates.
if ($data['timeopen'] && $data['timeclose']) {
if ($data['timeopen'] > $data['timeclose']) {
$errors['timeclose'] = get_string('closebeforeopen', 'scorm');
}
}
$suffix = $this->get_suffix();
$completionstatusallscosel = 'completionstatusallscos' . $suffix;
if (!empty($data[$completionstatusallscosel])) {
$completionstatusrequiredel = 'completionstatusrequired' . $suffix;
$requirestatus = false;
foreach (scorm_status_options(true) as $key => $value) {
if (!empty($data[$completionstatusrequiredel][$key])) {
$requirestatus = true;
}
}
if (!$requirestatus) {
$errors[$completionstatusallscosel] = get_string('youmustselectastatus', 'scorm');
}
}
// Validate 'Require minimum score' value.
$completionscorerequiredel = 'completionscorerequired' . $this->get_suffix();
$completionscoreenabledel = 'completionscoreenabled' . $this->get_suffix();
if (array_key_exists($completionscoreenabledel, $data) &&
$data[$completionscoreenabledel] &&
array_key_exists($completionscorerequiredel, $data) &&
strlen($data[$completionscorerequiredel]) &&
$data[$completionscorerequiredel] <= 0
) {
$errors['completionscoregroup' . $this->get_suffix()] = get_string('minimumscoregreater', 'scorm');
}
return $errors;
}
// Need to translate the "options" and "reference" field.
public function set_data($defaultvalues) {
$defaultvalues = (array)$defaultvalues;
if (isset($defaultvalues['scormtype']) and isset($defaultvalues['reference'])) {
switch ($defaultvalues['scormtype']) {
case SCORM_TYPE_LOCALSYNC :
case SCORM_TYPE_EXTERNAL:
case SCORM_TYPE_AICCURL:
$defaultvalues['packageurl'] = $defaultvalues['reference'];
}
}
unset($defaultvalues['reference']);
if (!empty($defaultvalues['options'])) {
$options = explode(',', $defaultvalues['options']);
foreach ($options as $option) {
$opt = explode('=', $option);
if (isset($opt[1])) {
$defaultvalues[$opt[0]] = $opt[1];
}
}
}
parent::set_data($defaultvalues);
}
public function add_completion_rules() {
$suffix = $this->get_suffix();
$mform =& $this->_form;
$items = [];
// Require score.
$group = [];
$completionscorerequiredel = 'completionscorerequired' . $suffix;
$completionscoreenabledel = 'completionscoreenabled' . $suffix;
$group[] =& $mform->createElement(
'checkbox',
$completionscoreenabledel,
null,
get_string('completionscorerequired', 'scorm')
);
$group[] =& $mform->createElement('text', $completionscorerequiredel, '', ['size' => 5]);
$mform->setType($completionscorerequiredel, PARAM_INT);
$completionscoregroupel = 'completionscoregroup' . $suffix;
$mform->addGroup($group, $completionscoregroupel, '', '', false);
$mform->hideIf($completionscorerequiredel, $completionscoreenabledel, 'notchecked');
$mform->setDefault($completionscorerequiredel, 0);
$items[] = $completionscoregroupel;
// Require status.
$completionstatusrequiredel = 'completionstatusrequired' . $suffix;
foreach (scorm_status_options(true) as $key => $value) {
$key = $completionstatusrequiredel . '['.$key.']';
$mform->addElement('checkbox', $key, '', $value);
$mform->setType($key, PARAM_BOOL);
$mform->hideIf($key, $completionstatusrequiredel, 'notchecked');
$items[] = $key;
}
$completionstatusallscosel = 'completionstatusallscos' . $suffix;
$mform->addElement('checkbox', $completionstatusallscosel, get_string('completionstatusallscos', 'scorm'));
$mform->setType($completionstatusallscosel, PARAM_BOOL);
$mform->addHelpButton($completionstatusallscosel, 'completionstatusallscos', 'scorm');
$mform->setDefault($completionstatusallscosel, 0);
$items[] = $completionstatusallscosel;
return $items;
}
public function completion_rule_enabled($data) {
$suffix = $this->get_suffix();
$status = !empty($data['completionstatusrequired' . $suffix]);
$score = !empty($data['completionscoreenabled' . $suffix]) &&
strlen($data['completionscorerequired' . $suffix] && $data['completionscorerequired' . $suffix] > 0);
return $status || $score;
}
/**
* Allows module to modify the data returned by form get_data().
* This method is also called in the bulk activity completion form.
*
* Only available on moodleform_mod.
*
* @param stdClass $data the form data to be modified.
*/
public function data_postprocessing($data) {
parent::data_postprocessing($data);
// Convert completionstatusrequired to a proper integer, if any.
$total = 0;
$suffix = $this->get_suffix();
if (isset($data->{'completionstatusrequired' . $suffix}) && is_array($data->{'completionstatusrequired' . $suffix})) {
foreach ($data->{'completionstatusrequired' . $suffix} as $state => $value) {
if ($value) {
$total |= $state;
}
}
if (!$total) {
$total = null;
}
$data->{'completionstatusrequired' . $suffix} = $total;
}
if (!empty($data->completionunlocked)) {
// Turn off completion settings if the checkboxes aren't ticked.
$completion = $data->{'completion' . $suffix};
$autocompletion = isset($completion) && $completion == COMPLETION_TRACKING_AUTOMATIC;
if (!(isset($data->{'completionstatusrequired' . $suffix}) && $autocompletion)) {
$data->{'completionstatusrequired' . $suffix} = null;
}
// Else do nothing: completionstatusrequired has been already converted into a correct integer representation.
if (!(isset($data->{'completionscoreenabled' . $suffix}) && $autocompletion)) {
$data->{'completionscorerequired' . $suffix} = null;
}
}
}
}
+950
View File
@@ -0,0 +1,950 @@
// 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/>.
/**
* Javascript helper function for SCORM module.
*
* @package mod-scorm
* @copyright 2009 Petr Skoda (http://skodak.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
mod_scorm_launch_next_sco = null;
mod_scorm_launch_prev_sco = null;
mod_scorm_activate_item = null;
mod_scorm_parse_toc_tree = null;
scorm_layout_widget = null;
window.scorm_current_node = null;
function underscore(str) {
str = String(str).replace(/.N/g,".");
return str.replace(/\./g,"__");
}
M.mod_scorm = {};
M.mod_scorm.init = function(Y, nav_display, navposition_left, navposition_top, hide_toc, collapsetocwinsize, toc_title, window_name, launch_sco, scoes_nav) {
var scorm_disable_toc = false;
var scorm_hide_nav = true;
var scorm_hide_toc = true;
var launch_sco_fix = launch_sco;
if (hide_toc == 0) {
if (nav_display !== 0) {
scorm_hide_nav = false;
}
scorm_hide_toc = false;
} else if (hide_toc == 3) {
scorm_disable_toc = true;
}
scoes_nav = Y.JSON.parse(scoes_nav);
var scorm_update_siblings = function (scoesnav) {
for(var key in scoesnav ){
var siblings = [],
parentscoid = key;
for (var mk in scoesnav) {
var val = scoesnav[mk];
if (typeof val !== "undefined" && typeof val.parentscoid !== 'undefined' && val.parentscoid === parentscoid) {
siblings.push(mk);
}
}
if (siblings.length > 1) {
scoesnav = scorm_get_siblings(scoesnav, siblings);
}
}
return scoesnav;
};
var scorm_get_siblings = function (scoesnav, siblings) {
siblings.forEach(function (key, index) {
if (index > 0 && typeof scoesnav[key] !== "undefined" && typeof scoesnav[key].prevsibling === "undefined") {
scoesnav[key].prevsibling = siblings[index - 1];
}
if (index < siblings.length - 1 && typeof scoesnav[key] !== "undefined" &&
typeof scoesnav[key].nextsibling === "undefined") {
scoesnav[key].nextsibling = siblings[index + 1];
}
});
return scoesnav;
};
scoes_nav = scorm_update_siblings(scoes_nav);
var scorm_buttons = [];
var scorm_bloody_labelclick = false;
var scorm_nav_panel;
Y.use('button', 'dd-plugin', 'panel', 'resize', 'gallery-sm-treeview', function(Y) {
Y.TreeView.prototype.getNodeByAttribute = function(attribute, value) {
var node = null,
domnode = Y.one('a[' + attribute + '="' + value + '"]');
if (domnode !== null) {
node = scorm_tree_node.getNodeById(domnode.ancestor('li').get('id'));
}
return node;
};
Y.TreeView.prototype.openAll = function () {
this.get('container').all('.yui3-treeview-can-have-children').each(function(target) {
this.getNodeById(target.get('id')).open();
}, this);
};
Y.TreeView.prototype.closeAll = function () {
this.get('container').all('.yui3-treeview-can-have-children').each(function(target) {
this.getNodeById(target.get('id')).close();
}, this);
}
var scorm_parse_toc_tree = function(srcNode) {
var SELECTORS = {
child: '> li',
label: '> li, > a',
textlabel : '> li, > span',
subtree: '> ul, > li'
},
children = [];
srcNode.all(SELECTORS.child).each(function(childNode) {
var child = {},
labelNode = childNode.one(SELECTORS.label),
textNode = childNode.one(SELECTORS.textlabel),
subTreeNode = childNode.one(SELECTORS.subtree);
if (labelNode) {
var title = labelNode.getAttribute('title');
var scoid = labelNode.getData('scoid');
child.label = labelNode.get('outerHTML');
// Will be good to change to url instead of title.
if (title && title !== '#') {
child.title = title;
}
if (typeof scoid !== 'undefined') {
child.scoid = scoid;
}
} else if (textNode) {
// The selector did not find a label node with anchor.
child.label = textNode.get('outerHTML');
}
if (subTreeNode) {
child.children = scorm_parse_toc_tree(subTreeNode);
}
children.push(child);
});
return children;
};
mod_scorm_parse_toc_tree = scorm_parse_toc_tree;
var scorm_activate_item = function(node) {
if (!node) {
return;
}
// Check if the item is already active, avoid recursive calls.
var content = Y.one('#scorm_content');
var old = Y.one('#scorm_object');
if (old) {
var scorm_active_url = Y.one('#scorm_object').getAttribute('src');
var node_full_url = M.cfg.wwwroot + '/mod/scorm/loadSCO.php?' + node.title;
if (node_full_url === scorm_active_url) {
return;
}
// Start to unload iframe here
if(!window_name){
content.removeChild(old);
old = null;
}
}
// End of - Avoid recursive calls.
scorm_current_node = node;
if (!scorm_current_node.state.selected) {
scorm_current_node.select();
}
scorm_tree_node.closeAll();
var url_prefix = M.cfg.wwwroot + '/mod/scorm/loadSCO.php?';
var el_old_api = document.getElementById('scormapi123');
if (el_old_api) {
el_old_api.parentNode.removeChild(el_old_api);
}
var obj = document.createElement('iframe');
obj.setAttribute('id', 'scorm_object');
obj.setAttribute('type', 'text/html');
obj.setAttribute('allowfullscreen', 'allowfullscreen');
obj.setAttribute('webkitallowfullscreen', 'webkitallowfullscreen');
obj.setAttribute('mozallowfullscreen', 'mozallowfullscreen');
if (!window_name && node.title != null) {
obj.setAttribute('src', url_prefix + node.title);
}
// Attach unload observers to the iframe. The scorm package may be observing these unload events
// and trying to save progress when they occur. We need to ensure we use the Beacon API in those
// situations.
if (typeof mod_scorm_monitorForBeaconRequirement !== 'undefined') {
mod_scorm_monitorForBeaconRequirement(obj);
}
if (window_name) {
var mine = window.open('','','width=1,height=1,left=0,top=0,scrollbars=no');
if(! mine) {
alert(M.util.get_string('popupsblocked', 'scorm'));
}
mine.close();
}
if (old) {
if(window_name) {
var cwidth = scormplayerdata.cwidth;
var cheight = scormplayerdata.cheight;
var poptions = scormplayerdata.popupoptions;
poptions = poptions + ',resizable=yes'; // Added for IE (MDL-32506).
scorm_openpopup(M.cfg.wwwroot + "/mod/scorm/loadSCO.php?" + node.title, window_name, poptions, cwidth, cheight);
}
} else {
content.prepend(obj);
}
if (scorm_hide_nav == false) {
if (nav_display === 1 && navposition_left > 0 && navposition_top > 0) {
Y.one('#scorm_object').addClass(cssclasses.scorm_nav_under_content);
}
scorm_fixnav();
}
scorm_tree_node.openAll();
};
mod_scorm_activate_item = scorm_activate_item;
/**
* Enables/disables navigation buttons as needed.
* @return void
*/
var scorm_fixnav = function() {
launch_sco_fix = launch_sco;
var skipprevnode = scorm_skipprev(scorm_current_node);
var prevnode = scorm_prev(scorm_current_node);
var upnode = scorm_up(scorm_current_node);
var nextnode = scorm_next(scorm_current_node, true, true);
var skipnextnode = scorm_skipnext(scorm_current_node, true, true);
scorm_buttons[0].set('disabled', ((skipprevnode === null) ||
(typeof(skipprevnode.scoid) === 'undefined') ||
(scoes_nav[skipprevnode.scoid].isvisible === "false") ||
(skipprevnode.title === null) ||
(scoes_nav[launch_sco].hideprevious === 1)));
scorm_buttons[1].set('disabled', ((prevnode === null) ||
(typeof(prevnode.scoid) === 'undefined') ||
(scoes_nav[prevnode.scoid].isvisible === "false") ||
(prevnode.title === null) ||
(scoes_nav[launch_sco].hideprevious === 1)));
scorm_buttons[2].set('disabled', (upnode === null) ||
(typeof(upnode.scoid) === 'undefined') ||
(scoes_nav[upnode.scoid].isvisible === "false") ||
(upnode.title === null));
scorm_buttons[3].set('disabled', ((nextnode === null) ||
((nextnode.title === null) && (scoes_nav[launch_sco].flow !== 1)) ||
(typeof(nextnode.scoid) === 'undefined') ||
(scoes_nav[nextnode.scoid].isvisible === "false") ||
(scoes_nav[launch_sco].hidecontinue === 1)));
scorm_buttons[4].set('disabled', ((skipnextnode === null) ||
(skipnextnode.title === null) ||
(typeof(skipnextnode.scoid) === 'undefined') ||
(scoes_nav[skipnextnode.scoid].isvisible === "false") ||
scoes_nav[launch_sco].hidecontinue === 1));
};
var scorm_toggle_toc = function(windowresize) {
var toc = Y.one('#scorm_toc');
var scorm_content = Y.one('#scorm_content');
var scorm_toc_toggle_btn = Y.one('#scorm_toc_toggle_btn');
var toc_disabled = toc.hasClass('disabled');
var disabled_by = toc.getAttribute('disabled-by');
// Remove width element style from resize handle.
toc.setStyle('width', null);
scorm_content.setStyle('width', null);
if (windowresize === true) {
if (disabled_by === 'user') {
return;
}
var body = Y.one('body');
if (body.get('winWidth') < collapsetocwinsize) {
toc.addClass(cssclasses.disabled)
.setAttribute('disabled-by', 'screen-size');
scorm_toc_toggle_btn.setHTML('&gt;')
.set('title', M.util.get_string('show', 'moodle'));
scorm_content.removeClass(cssclasses.scorm_grid_content_toc_visible)
.addClass(cssclasses.scorm_grid_content_toc_hidden);
} else if (body.get('winWidth') > collapsetocwinsize) {
toc.removeClass(cssclasses.disabled)
.removeAttribute('disabled-by');
scorm_toc_toggle_btn.setHTML('&lt;')
.set('title', M.util.get_string('hide', 'moodle'));
scorm_content.removeClass(cssclasses.scorm_grid_content_toc_hidden)
.addClass(cssclasses.scorm_grid_content_toc_visible);
}
return;
}
if (toc_disabled) {
toc.removeClass(cssclasses.disabled)
.removeAttribute('disabled-by');
scorm_toc_toggle_btn.setHTML('&lt;')
.set('title', M.util.get_string('hide', 'moodle'));
scorm_content.removeClass(cssclasses.scorm_grid_content_toc_hidden)
.addClass(cssclasses.scorm_grid_content_toc_visible);
} else {
toc.addClass(cssclasses.disabled)
.setAttribute('disabled-by', 'user');
scorm_toc_toggle_btn.setHTML('&gt;')
.set('title', M.util.get_string('show', 'moodle'));
scorm_content.removeClass(cssclasses.scorm_grid_content_toc_visible)
.addClass(cssclasses.scorm_grid_content_toc_hidden);
}
};
var scorm_resize_layout = function() {
if (window_name) {
return;
}
// make sure that the max width of the TOC doesn't go to far
var scorm_toc_node = Y.one('#scorm_toc');
var maxwidth = parseInt(Y.one('#scorm_layout').getComputedStyle('width'), 10);
scorm_toc_node.setStyle('maxWidth', (maxwidth - 200));
var cwidth = parseInt(scorm_toc_node.getComputedStyle('width'), 10);
if (cwidth > (maxwidth - 1)) {
scorm_toc_node.setStyle('width', (maxwidth - 50));
}
// Calculate the rough new height from the viewport height.
var newheight = Y.one('body').get('winHeight') - 5
- Y.one('#scorm_layout').getY()
- window.pageYOffset;
if (newheight < 680 || isNaN(newheight)) {
newheight = 680;
}
Y.one('#scorm_layout').setStyle('height', newheight);
};
/**
* @deprecated as it is now unused.
* @param {string} url
* @param {string} datastring
* @returns {string|*|boolean}
*/
var scorm_ajax_request = function(url, datastring) {
var myRequest = NewHttpReq();
var result = DoRequest(myRequest, url + datastring);
return result;
};
var scorm_up = function(node, update_launch_sco) {
if (node.parent && node.parent.parent && typeof scoes_nav[launch_sco].parentscoid !== 'undefined') {
var parentscoid = scoes_nav[launch_sco].parentscoid;
var parent = node.parent;
if (parent.title !== scoes_nav[parentscoid].url) {
parent = scorm_tree_node.getNodeByAttribute('title', scoes_nav[parentscoid].url);
if (parent === null) {
parent = scorm_tree_node.rootNode.children[0];
parent.title = scoes_nav[parentscoid].url;
}
}
if (update_launch_sco) {
launch_sco = parentscoid;
}
return parent;
}
return null;
};
var scorm_lastchild = function(node) {
if (node.children.length) {
return scorm_lastchild(node.children[node.children.length - 1]);
} else {
return node;
}
};
var scorm_prev = function(node, update_launch_sco) {
if (node.previous() && node.previous().children.length &&
typeof scoes_nav[launch_sco].prevscoid !== 'undefined') {
node = scorm_lastchild(node.previous());
if (node) {
var prevscoid = scoes_nav[launch_sco].prevscoid;
if (node.title !== scoes_nav[prevscoid].url) {
node = scorm_tree_node.getNodeByAttribute('title', scoes_nav[prevscoid].url);
if (node === null) {
node = scorm_tree_node.rootNode.children[0];
node.title = scoes_nav[prevscoid].url;
}
}
if (update_launch_sco) {
launch_sco = prevscoid;
}
return node;
} else {
return null;
}
}
return scorm_skipprev(node, update_launch_sco);
};
var scorm_skipprev = function(node, update_launch_sco) {
if (node.previous() && typeof scoes_nav[launch_sco].prevsibling !== 'undefined') {
var prevsibling = scoes_nav[launch_sco].prevsibling;
var previous = node.previous();
var prevscoid = scoes_nav[launch_sco].prevscoid;
if (previous.title !== scoes_nav[prevscoid].url) {
previous = scorm_tree_node.getNodeByAttribute('title', scoes_nav[prevsibling].url);
if (previous === null) {
previous = scorm_tree_node.rootNode.children[0];
previous.title = scoes_nav[prevsibling].url;
}
}
if (update_launch_sco) {
launch_sco = prevsibling;
}
return previous;
} else if (node.parent && node.parent.parent && typeof scoes_nav[launch_sco].parentscoid !== 'undefined') {
var parentscoid = scoes_nav[launch_sco].parentscoid;
var parent = node.parent;
if (parent.title !== scoes_nav[parentscoid].url) {
parent = scorm_tree_node.getNodeByAttribute('title', scoes_nav[parentscoid].url);
if (parent === null) {
parent = scorm_tree_node.rootNode.children[0];
parent.title = scoes_nav[parentscoid].url;
}
}
if (update_launch_sco) {
launch_sco = parentscoid;
}
return parent;
}
return null;
};
var scorm_next = function(node, update_launch_sco, test) {
if (node === false) {
return scorm_tree_node.children[0];
}
if (node.children.length && typeof scoes_nav[launch_sco_fix].nextscoid != 'undefined') {
node = node.children[0];
var nextscoid = scoes_nav[launch_sco_fix].nextscoid;
if (node.title !== scoes_nav[nextscoid].url) {
node = scorm_tree_node.getNodeByAttribute('title', scoes_nav[nextscoid].url);
if (node === null) {
node = scorm_tree_node.rootNode.children[0];
node.title = scoes_nav[nextscoid].url;
}
}
if (update_launch_sco) {
launch_sco_fix = nextscoid;
if (!test) {
launch_sco = launch_sco_fix;
}
}
return node;
}
return scorm_skipnext(node, update_launch_sco, test);
};
var scorm_skipnext = function(node, update_launch_sco, test) {
var next = node.next();
if (next && next.title && typeof scoes_nav[launch_sco_fix] !== 'undefined' &&
typeof scoes_nav[launch_sco_fix].nextsibling !== 'undefined') {
var nextsibling = scoes_nav[launch_sco_fix].nextsibling;
if (next.title !== scoes_nav[nextsibling].url) {
next = scorm_tree_node.getNodeByAttribute('title', scoes_nav[nextsibling].url);
if (next === null) {
next = scorm_tree_node.rootNode.children[0];
next.title = scoes_nav[nextsibling].url;
}
}
if (update_launch_sco) {
launch_sco_fix = nextsibling;
if (!test) {
launch_sco = launch_sco_fix;
}
}
return next;
} else if (node.parent && node.parent.parent && typeof scoes_nav[launch_sco_fix].parentscoid !== 'undefined') {
var parentscoid = scoes_nav[launch_sco_fix].parentscoid;
var parent = node.parent;
if (parent.title !== scoes_nav[parentscoid].url) {
parent = scorm_tree_node.getNodeByAttribute('title', scoes_nav[parentscoid].url);
if (parent === null) {
parent = scorm_tree_node.rootNode.children[0];
}
}
if (update_launch_sco) {
launch_sco_fix = parentscoid;
if (!test) {
launch_sco = launch_sco_fix;
}
}
return scorm_skipnext(parent, update_launch_sco, test);
}
return null;
};
/**
* Sends a request to the sequencing handler script on the server.
* @param {string} datastring
* @returns {string|boolean|*}
*/
var scorm_dorequest_sequencing = function(datastring) {
var myRequest = NewHttpReq();
var result = DoRequest(
myRequest,
M.cfg.wwwroot + '/mod/scorm/datamodels/sequencinghandler.php?' + datastring,
'',
false
);
return result;
};
// Launch prev sco
var scorm_launch_prev_sco = function() {
var result = null;
if (scoes_nav[launch_sco].flow === 1) {
var datastring = scoes_nav[launch_sco].url + '&function=scorm_seq_flow&request=backward';
result = scorm_dorequest_sequencing(datastring);
// Check the scorm_ajax_result, it may be false.
if (result === false) {
// Either the outcome was a failure, or we are unloading and simply just don't know
// what the outcome actually was.
result = {};
} else {
result = Y.JSON.parse(result);
}
if (typeof result.nextactivity !== 'undefined' && typeof result.nextactivity.id !== 'undefined') {
var node = scorm_prev(scorm_tree_node.getSelectedNodes()[0]);
if (node == null) {
// Avoid use of TreeView for Navigation.
node = scorm_tree_node.getSelectedNodes()[0];
}
if (node.title !== scoes_nav[result.nextactivity.id].url) {
node = scorm_tree_node.getNodeByAttribute('title', scoes_nav[result.nextactivity.id].url);
if (node === null) {
node = scorm_tree_node.rootNode.children[0];
node.title = scoes_nav[result.nextactivity.id].url;
}
}
launch_sco = result.nextactivity.id;
scorm_activate_item(node);
scorm_fixnav();
} else {
scorm_activate_item(scorm_prev(scorm_tree_node.getSelectedNodes()[0], true));
}
} else {
scorm_activate_item(scorm_prev(scorm_tree_node.getSelectedNodes()[0], true));
}
};
// Launch next sco
var scorm_launch_next_sco = function () {
launch_sco_fix = launch_sco;
var result = null;
if (scoes_nav[launch_sco].flow === 1) {
var datastring = scoes_nav[launch_sco].url + '&function=scorm_seq_flow&request=forward';
result = scorm_dorequest_sequencing(datastring);
// Check the scorm_ajax_result, it may be false.
if (result === false) {
// Either the outcome was a failure, or we are unloading and simply just don't know
// what the outcome actually was.
result = {};
} else {
result = Y.JSON.parse(result);
}
if (typeof result.nextactivity !== 'undefined' && typeof result.nextactivity.id !== 'undefined') {
var node = scorm_next(scorm_tree_node.getSelectedNodes()[0]);
if (node === null) {
// Avoid use of TreeView for Navigation.
node = scorm_tree_node.getSelectedNodes()[0];
}
node = scorm_tree_node.getNodeByAttribute('title', scoes_nav[result.nextactivity.id].url);
if (node === null) {
node = scorm_tree_node.rootNode.children[0];
node.title = scoes_nav[result.nextactivity.id].url;
}
launch_sco = result.nextactivity.id;
launch_sco_fix = launch_sco;
scorm_activate_item(node);
scorm_fixnav();
} else {
scorm_activate_item(scorm_next(scorm_tree_node.getSelectedNodes()[0], true, false));
}
} else {
scorm_activate_item(scorm_next(scorm_tree_node.getSelectedNodes()[0], true,false));
}
};
mod_scorm_launch_prev_sco = scorm_launch_prev_sco;
mod_scorm_launch_next_sco = scorm_launch_next_sco;
var cssclasses = {
// YUI grid class: use 100% of the available width to show only content, TOC hidden.
scorm_grid_content_toc_hidden: 'yui3-u-1',
// YUI grid class: use 1/5 of the available width to show TOC.
scorm_grid_toc: 'yui3-u-1-5',
// YUI grid class: use 1/24 of the available width to show TOC toggle button.
scorm_grid_toggle: 'yui3-u-1-24',
// YUI grid class: use 3/4 of the available width to show content, TOC visible.
scorm_grid_content_toc_visible: 'yui3-u-3-4',
// Reduce height of #scorm_object to accomodate nav buttons under content.
scorm_nav_under_content: 'scorm_nav_under_content',
disabled: 'disabled'
};
// layout
Y.one('#scorm_toc_title').setHTML(toc_title);
if (scorm_disable_toc) {
Y.one('#scorm_toc').addClass(cssclasses.disabled);
Y.one('#scorm_toc_toggle').addClass(cssclasses.disabled);
Y.one('#scorm_content').addClass(cssclasses.scorm_grid_content_toc_hidden);
} else {
Y.one('#scorm_toc').addClass(cssclasses.scorm_grid_toc);
Y.one('#scorm_toc_toggle').addClass(cssclasses.scorm_grid_toggle);
Y.one('#scorm_toc_toggle_btn')
.setHTML('&lt;')
.setAttribute('title', M.util.get_string('hide', 'moodle'));
Y.one('#scorm_content').addClass(cssclasses.scorm_grid_content_toc_visible);
scorm_toggle_toc(true);
}
// hide the TOC if that is the default
if (!scorm_disable_toc) {
if (scorm_hide_toc == true) {
Y.one('#scorm_toc').addClass(cssclasses.disabled);
Y.one('#scorm_toc_toggle_btn')
.setHTML('&gt;')
.setAttribute('title', M.util.get_string('show', 'moodle'));
Y.one('#scorm_content')
.removeClass(cssclasses.scorm_grid_content_toc_visible)
.addClass(cssclasses.scorm_grid_content_toc_hidden);
}
}
// Basic initialization completed, show the elements.
Y.one('#scorm_toc').removeClass('loading');
Y.one('#scorm_toc_toggle').removeClass('loading');
// TOC Resize handle.
var layout_width = parseInt(Y.one('#scorm_layout').getComputedStyle('width'), 10);
var scorm_resize_handle = new Y.Resize({
node: '#scorm_toc',
handles: 'r',
defMinWidth: 0.2 * layout_width
});
// TOC tree
var toc_source = Y.one('#scorm_tree > ul');
var toc = scorm_parse_toc_tree(toc_source);
// Empty container after parsing toc.
var el = document.getElementById('scorm_tree');
el.innerHTML = '';
var tree = new Y.TreeView({
container: '#scorm_tree',
nodes: toc,
multiSelect: false,
lazyRender: false
});
scorm_tree_node = tree;
// Trigger after instead of on, avoid recursive calls.
tree.after('select', function(e) {
var node = e.node;
if (node.title == '' || node.title == null) {
return; //this item has no navigation
}
// If item is already active, return; avoid recursive calls.
if (obj = Y.one('#scorm_object')) {
var scorm_active_url = obj.getAttribute('src');
var node_full_url = M.cfg.wwwroot + '/mod/scorm/loadSCO.php?' + node.title;
if (node_full_url === scorm_active_url) {
return;
}
} else if(scorm_current_node == node){
return;
}
// Update launch_sco.
if (typeof node.scoid !== 'undefined') {
launch_sco = node.scoid;
}
scorm_activate_item(node);
if (node.children.length) {
scorm_bloody_labelclick = true;
}
});
if (!scorm_disable_toc) {
tree.on('close', function(e) {
if (scorm_bloody_labelclick) {
scorm_bloody_labelclick = false;
return false;
}
});
tree.subscribe('open', function(e) {
if (scorm_bloody_labelclick) {
scorm_bloody_labelclick = false;
return false;
}
});
}
tree.render();
tree.openAll();
// On getting the window, always set the focus on the current item
Y.one(Y.config.win).on('focus', function (e) {
var current = scorm_tree_node.getSelectedNodes()[0];
var toc_disabled = Y.one('#scorm_toc').hasClass('disabled');
if (current.id && !toc_disabled) {
Y.one('#' + current.id).focus();
}
});
// navigation
if (scorm_hide_nav == false) {
// TODO: make some better&accessible buttons.
var navbuttonshtml = '<span id="scorm_nav"><button id="nav_skipprev">&lt;&lt;</button>&nbsp;' +
'<button id="nav_prev">&lt;</button>&nbsp;<button id="nav_up">^</button>&nbsp;' +
'<button id="nav_next">&gt;</button>&nbsp;<button id="nav_skipnext">&gt;&gt;</button></span>';
if (nav_display === 1) {
Y.one('#scorm_navpanel').setHTML(navbuttonshtml);
} else {
// Nav panel is floating type.
var navposition = null;
if (navposition_left < 0 && navposition_top < 0) {
// Set default XY.
navposition = Y.one('#scorm_toc').getXY();
navposition[1] += 200;
} else {
// Set user defined XY.
navposition = [];
navposition[0] = parseInt(navposition_left, 10);
navposition[1] = parseInt(navposition_top, 10);
}
scorm_nav_panel = new Y.Panel({
fillHeight: "body",
headerContent: M.util.get_string('navigation', 'scorm'),
visible: true,
xy: navposition,
zIndex: 999
});
scorm_nav_panel.set('bodyContent', navbuttonshtml);
scorm_nav_panel.removeButton('close');
scorm_nav_panel.plug(Y.Plugin.Drag, {handles: ['.yui3-widget-hd']});
scorm_nav_panel.render();
}
scorm_buttons[0] = new Y.Button({
srcNode: '#nav_skipprev',
render: true,
on: {
'click' : function(ev) {
scorm_activate_item(scorm_skipprev(scorm_tree_node.getSelectedNodes()[0], true));
},
'keydown' : function(ev) {
if (ev.domEvent.keyCode === 13 || ev.domEvent.keyCode === 32) {
scorm_activate_item(scorm_skipprev(scorm_tree_node.getSelectedNodes()[0], true));
}
}
}
});
scorm_buttons[1] = new Y.Button({
srcNode: '#nav_prev',
render: true,
on: {
'click' : function(ev) {
scorm_launch_prev_sco();
},
'keydown' : function(ev) {
if (ev.domEvent.keyCode === 13 || ev.domEvent.keyCode === 32) {
scorm_launch_prev_sco();
}
}
}
});
scorm_buttons[2] = new Y.Button({
srcNode: '#nav_up',
render: true,
on: {
'click' : function(ev) {
scorm_activate_item(scorm_up(scorm_tree_node.getSelectedNodes()[0], true));
},
'keydown' : function(ev) {
if (ev.domEvent.keyCode === 13 || ev.domEvent.keyCode === 32) {
scorm_activate_item(scorm_up(scorm_tree_node.getSelectedNodes()[0], true));
}
}
}
});
scorm_buttons[3] = new Y.Button({
srcNode: '#nav_next',
render: true,
on: {
'click' : function(ev) {
scorm_launch_next_sco();
},
'keydown' : function(ev) {
if (ev.domEvent.keyCode === 13 || ev.domEvent.keyCode === 32) {
scorm_launch_next_sco();
}
}
}
});
scorm_buttons[4] = new Y.Button({
srcNode: '#nav_skipnext',
render: true,
on: {
'click' : function(ev) {
launch_sco_fix = launch_sco;
scorm_activate_item(scorm_skipnext(scorm_tree_node.getSelectedNodes()[0], true, false));
},
'keydown' : function(ev) {
launch_sco_fix = launch_sco;
if (ev.domEvent.keyCode === 13 || ev.domEvent.keyCode === 32) {
scorm_activate_item(scorm_skipnext(scorm_tree_node.getSelectedNodes()[0], true, false));
}
}
}
});
}
// finally activate the chosen item
var scorm_first_url = null;
if (typeof tree.rootNode.children[0] !== 'undefined') {
if (tree.rootNode.children[0].title !== scoes_nav[launch_sco].url) {
var node = tree.getNodeByAttribute('title', scoes_nav[launch_sco].url);
if (node !== null) {
scorm_first_url = node;
}
} else {
scorm_first_url = tree.rootNode.children[0];
}
}
if (scorm_first_url == null) { // This is probably a single sco with no children (AICC Direct uses this).
scorm_first_url = tree.rootNode;
}
scorm_first_url.title = scoes_nav[launch_sco].url;
scorm_activate_item(scorm_first_url);
// resizing
scorm_resize_layout();
// Collapse/expand TOC.
Y.one('#scorm_toc_toggle').on('click', scorm_toggle_toc);
Y.one('#scorm_toc_toggle').on('key', scorm_toggle_toc, 'down:enter,32');
// fix layout if window resized
Y.on("windowresize", function() {
scorm_resize_layout();
var toc_displayed = Y.one('#scorm_toc').getComputedStyle('display') !== 'none';
if ((!scorm_disable_toc && !scorm_hide_toc) || toc_displayed) {
scorm_toggle_toc(true);
}
// Set 20% as minWidth constrain of TOC.
var layout_width = parseInt(Y.one('#scorm_layout').getComputedStyle('width'), 10);
scorm_resize_handle.set('defMinWidth', 0.2 * layout_width);
});
// On resize drag, change width of scorm_content.
scorm_resize_handle.on('resize:resize', function() {
var tocwidth = parseInt(Y.one('#scorm_toc').getComputedStyle('width'), 10);
var layoutwidth = parseInt(Y.one('#scorm_layout').getStyle('width'), 10);
Y.one('#scorm_content').setStyle('width', (layoutwidth - tocwidth - 60));
});
});
};
M.mod_scorm.connectPrereqCallback = {
success: function(id, o) {
if (o.responseText !== undefined) {
var snode = null,
stitle = null;
if (scorm_tree_node && o.responseText) {
snode = scorm_tree_node.getSelectedNodes()[0];
stitle = null;
if (snode) {
stitle = snode.title;
}
// All gone with clear, add new root node.
scorm_tree_node.clear(scorm_tree_node.createNode());
}
// Make sure the temporary tree element is not there.
var el_old_tree = document.getElementById('scormtree123');
if (el_old_tree) {
el_old_tree.parentNode.removeChild(el_old_tree);
}
var el_new_tree = document.createElement('div');
var pagecontent = document.getElementById("page-content");
if (!pagecontent) {
pagecontent = document.getElementById("content");
}
if (!pagecontent) {
pagecontent = document.getElementById("scormpage");
}
el_new_tree.setAttribute('id','scormtree123');
el_new_tree.innerHTML = o.responseText;
// Make sure it does not show.
el_new_tree.style.display = 'none';
pagecontent.appendChild(el_new_tree);
// Ignore the first level element as this is the title.
var startNode = el_new_tree.firstChild.firstChild;
if (startNode.tagName == 'LI') {
// Go back to the beginning.
startNode = el_new_tree;
}
var toc_source = Y.one('#scormtree123 > ul');
var toc = mod_scorm_parse_toc_tree(toc_source);
scorm_tree_node.appendNode(scorm_tree_node.rootNode, toc);
var el = document.getElementById('scormtree123');
el.parentNode.removeChild(el);
scorm_tree_node.render();
scorm_tree_node.openAll();
if (stitle !== null) {
snode = scorm_tree_node.getNodeByAttribute('title', stitle);
// Do not let destroyed node to be selected.
if (snode && !snode.state.destroyed) {
snode.select();
var toc_disabled = Y.one('#scorm_toc').hasClass('disabled');
if (!toc_disabled) {
if (!snode.state.selected) {
snode.select();
}
}
}
}
}
},
failure: function(id, o) {
// TODO: do some sort of error handling.
}
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 384 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 464c-8.8 0-16-7.2-16-16V64c0-8.8 7.2-16 16-16h48c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16h48v80c0 17.7 14.3 32 32 32h80V448c0 8.8-7.2 16-16 16H64zM64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V154.5c0-17-6.7-33.3-18.7-45.3L274.7 18.7C262.7 6.7 246.5 0 229.5 0H64zm48 112c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H128c-8.8 0-16 7.2-16 16zm0 64c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H128c-8.8 0-16 7.2-16 16zm-6.3 71.8L82.1 335.9c-1.4 5.4-2.1 10.9-2.1 16.4c0 35.2 28.8 63.7 64 63.7s64-28.5 64-63.7c0-5.5-.7-11.1-2.1-16.4l-23.5-88.2c-3.7-14-16.4-23.8-30.9-23.8H136.6c-14.5 0-27.2 9.7-30.9 23.8zM128 336h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H128c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 384 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 464c-8.8 0-16-7.2-16-16V64c0-8.8 7.2-16 16-16h48c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16h48v80c0 17.7 14.3 32 32 32h80V448c0 8.8-7.2 16-16 16H64zM64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V154.5c0-17-6.7-33.3-18.7-45.3L274.7 18.7C262.7 6.7 246.5 0 229.5 0H64zm48 112c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H128c-8.8 0-16 7.2-16 16zm0 64c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H128c-8.8 0-16 7.2-16 16zm-6.3 71.8L82.1 335.9c-1.4 5.4-2.1 10.9-2.1 16.4c0 35.2 28.8 63.7 64 63.7s64-28.5 64-63.7c0-5.5-.7-11.1-2.1-16.4l-23.5-88.2c-3.7-14-16.4-23.8-30.9-23.8H136.6c-14.5 0-27.2 9.7-30.9 23.8zM128 336h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H128c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M96 0C43 0 0 43 0 96V416c0 53 43 96 96 96H384h32c17.7 0 32-14.3 32-32s-14.3-32-32-32V384c17.7 0 32-14.3 32-32V32c0-17.7-14.3-32-32-32H384 96zm0 384H352v64H96c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16H336c8.8 0 16 7.2 16 16s-7.2 16-16 16H144c-8.8 0-16-7.2-16-16zm16 48H336c8.8 0 16 7.2 16 16s-7.2 16-16 16H144c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/></svg>

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H384c8.8 0 16-7.2 16-16V96c0-8.8-7.2-16-16-16H64zM0 96C0 60.7 28.7 32 64 32H384c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM337 209L209 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L303 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg>

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 384 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M441 58.9L453.1 71c9.4 9.4 9.4 24.6 0 33.9L424 134.1 377.9 88 407 58.9c9.4-9.4 24.6-9.4 33.9 0zM209.8 256.2L344 121.9 390.1 168 255.8 302.2c-2.9 2.9-6.5 5-10.4 6.1l-58.5 16.7 16.7-58.5c1.1-3.9 3.2-7.5 6.1-10.4zM373.1 25L175.8 222.2c-8.7 8.7-15 19.4-18.3 31.1l-28.6 100c-2.4 8.4-.1 17.4 6.1 23.6s15.2 8.5 23.6 6.1l100-28.6c11.8-3.4 22.5-9.7 31.1-18.3L487 138.9c28.1-28.1 28.1-73.7 0-101.8L474.9 25C446.8-3.1 401.2-3.1 373.1 25zM88 64C39.4 64 0 103.4 0 152V424c0 48.6 39.4 88 88 88H360c48.6 0 88-39.4 88-88V312c0-13.3-10.7-24-24-24s-24 10.7-24 24V424c0 22.1-17.9 40-40 40H88c-22.1 0-40-17.9-40-40V152c0-22.1 17.9-40 40-40H200c13.3 0 24-10.7 24-24s-10.7-24-24-24H88z"/></svg>

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M432 256c0 17.7-14.3 32-32 32L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l352 0c17.7 0 32 14.3 32 32z"/></svg>

After

Width:  |  Height:  |  Size: 399 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMid meet">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2236 3.05511C12.0828 2.98472 11.9172 2.98472 11.7764 3.05511L3.77839 7.05411C3.772 7.05727 3.76567 7.06057 3.7594 7.06401C3.59987 7.15155 3.5 7.31935 3.5 7.50232V16.4977C3.5 16.6871 3.607 16.8602 3.77639 16.9449L11.7744 20.9439C11.7809 20.9472 11.7874 20.9503 11.794 20.9533C11.9301 21.0148 12.088 21.0127 12.2236 20.9449M12.2256 20.9439L20.2236 16.9449C20.393 16.8602 20.5 16.6871 20.5 16.4977V7.50232C20.5 7.33661 20.4181 7.18334 20.2841 7.09087C20.2708 7.08171 20.257 7.07314 20.2428 7.06521C20.2358 7.06132 20.2287 7.05761 20.2215 7.05407L12.2236 3.05511M12.5 19.6887L19.5 16.1887V8.31134L12.5 11.8113V19.6887ZM11.5 19.6887V11.8113L8.5 10.3113V13.0049C8.5 13.2811 8.27614 13.5049 8 13.5049C7.72386 13.5049 7.5 13.2811 7.5 13.0049V9.81134L4.5 8.31134V16.1887L11.5 19.6887ZM5.11803 7.50232L12 4.06134L14.7218 5.42223L7.83982 8.86321L5.11803 7.50232ZM8.95786 9.42223L12 10.9433L18.882 7.50232L15.8398 5.98125L8.95786 9.42223Z" fill="#212529"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M384 80c8.8 0 16 7.2 16 16V416c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V96c0-8.8 7.2-16 16-16H384zM64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64z"/></svg>

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32V224H48c-17.7 0-32 14.3-32 32s14.3 32 32 32H192V432c0 17.7 14.3 32 32 32s32-14.3 32-32V288H400c17.7 0 32-14.3 32-32s-14.3-32-32-32H256V80z"/></svg>

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H448c8.8 0 16-7.2 16-16V96c0-8.8-7.2-16-16-16H64zM0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zm175 79c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/></svg>

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M432 48H208c-17.7 0-32 14.3-32 32V96H128V80c0-44.2 35.8-80 80-80H432c44.2 0 80 35.8 80 80V304c0 44.2-35.8 80-80 80H416V336h16c17.7 0 32-14.3 32-32V80c0-17.7-14.3-32-32-32zM48 448c0 8.8 7.2 16 16 16H320c8.8 0 16-7.2 16-16V256H48V448zM64 128H320c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V192c0-35.3 28.7-64 64-64z"/></svg>

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 320 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M48 64C21.5 64 0 85.5 0 112V400c0 26.5 21.5 48 48 48H80c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H48zm192 0c-26.5 0-48 21.5-48 48V400c0 26.5 21.5 48 48 48h32c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H240z"/></svg>

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M464 256A208 208 0 1 1 48 256a208 208 0 1 1 416 0zM0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256zM232 120V256c0 8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2V120c0-13.3-10.7-24-24-24s-24 10.7-24 24z"/></svg>

After

Width:  |  Height:  |  Size: 527 B

+36
View File
@@ -0,0 +1,36 @@
// 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/>.
function scorm_openpopup(url,name,options,width,height) {
if (width <= 100) {
width = Math.round(screen.availWidth * width / 100);
}
if (height <= 100) {
height = Math.round(screen.availHeight * height / 100);
}
options += ",width=" + width + ",height=" + height;
windowobj = window.open(url,name,options);
windowobj.opener = null;
if (!windowobj) {
return;
}
if ((width == 100) && (height == 100)) {
// Fullscreen
windowobj.moveTo(0,0);
}
windowobj.focus();
return windowobj;
}
+302
View File
@@ -0,0 +1,302 @@
<?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/>.
// This page prints a particular instance of aicc/scorm package.
require_once('../../config.php');
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
require_once($CFG->libdir . '/completionlib.php');
$id = optional_param('cm', '', PARAM_INT); // Course Module ID, or
$a = optional_param('a', '', PARAM_INT); // scorm ID
$scoid = required_param('scoid', PARAM_INT); // sco ID
$mode = optional_param('mode', 'normal', PARAM_ALPHA); // navigation mode
$currentorg = optional_param('currentorg', '', PARAM_RAW); // selected organization
$newattempt = optional_param('newattempt', 'off', PARAM_ALPHA); // the user request to start a new attempt.
$displaymode = optional_param('display', '', PARAM_ALPHA);
if (!empty($id)) {
if (! $cm = get_coursemodule_from_id('scorm', $id, 0, true)) {
throw new \moodle_exception('invalidcoursemodule');
}
if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
throw new \moodle_exception('coursemisconf');
}
if (! $scorm = $DB->get_record("scorm", array("id" => $cm->instance))) {
throw new \moodle_exception('invalidcoursemodule');
}
} else if (!empty($a)) {
if (! $scorm = $DB->get_record("scorm", array("id" => $a))) {
throw new \moodle_exception('invalidcoursemodule');
}
if (! $course = $DB->get_record("course", array("id" => $scorm->course))) {
throw new \moodle_exception('coursemisconf');
}
if (! $cm = get_coursemodule_from_instance("scorm", $scorm->id, $course->id, true)) {
throw new \moodle_exception('invalidcoursemodule');
}
} else {
throw new \moodle_exception('missingparameter');
}
// PARAM_RAW is used for $currentorg, validate it against records stored in the table.
if (!empty($currentorg)) {
if (!$DB->record_exists('scorm_scoes', array('scorm' => $scorm->id, 'identifier' => $currentorg))) {
$currentorg = '';
}
}
// If new attempt is being triggered set normal mode and increment attempt number.
$attempt = scorm_get_last_attempt($scorm->id, $USER->id);
// Check mode is correct and set/validate mode/attempt/newattempt (uses pass by reference).
scorm_check_mode($scorm, $newattempt, $attempt, $USER->id, $mode);
if (!empty($scoid)) {
$scoid = scorm_check_launchable_sco($scorm, $scoid);
}
$url = new moodle_url('/mod/scorm/player.php', array('scoid' => $scoid, 'cm' => $cm->id));
if ($mode !== 'normal') {
$url->param('mode', $mode);
}
if ($currentorg !== '') {
$url->param('currentorg', $currentorg);
}
if ($newattempt !== 'off') {
$url->param('newattempt', $newattempt);
}
if ($displaymode !== '') {
$url->param('display', $displaymode);
}
$PAGE->set_url($url);
$PAGE->set_secondary_active_tab("modulepage");
$forcejs = get_config('scorm', 'forcejavascript');
if (!empty($forcejs)) {
$PAGE->add_body_class('forcejavascript');
}
$collapsetocwinsize = get_config('scorm', 'collapsetocwinsize');
if (empty($collapsetocwinsize)) {
// Set as default window size to collapse TOC.
$collapsetocwinsize = 767;
} else {
$collapsetocwinsize = intval($collapsetocwinsize);
}
require_login($course, false, $cm);
$strscorms = get_string('modulenameplural', 'scorm');
$strscorm = get_string('modulename', 'scorm');
$strpopup = get_string('popup', 'scorm');
$strexit = get_string('exitactivity', 'scorm');
$coursecontext = context_course::instance($course->id);
if ($displaymode == 'popup') {
$PAGE->set_pagelayout('embedded');
} else {
$shortname = format_string($course->shortname, true, array('context' => $coursecontext));
$pagetitle = strip_tags("$shortname: ".format_string($scorm->name));
$PAGE->set_title($pagetitle);
$PAGE->set_heading($course->fullname);
}
if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', context_module::instance($cm->id))) {
echo $OUTPUT->header();
notice(get_string("activityiscurrentlyhidden"));
echo $OUTPUT->footer();
die;
}
// Check if SCORM available.
list($available, $warnings) = scorm_get_availability_status($scorm);
if (!$available) {
$reason = current(array_keys($warnings));
echo $OUTPUT->header();
echo $OUTPUT->box(get_string($reason, "scorm", $warnings[$reason]), "generalbox boxaligncenter");
echo $OUTPUT->footer();
die;
}
// TOC processing
$scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR)); // Just to be safe.
if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) {
$scorm->version = 'scorm_12';
}
require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php');
$result = scorm_get_toc($USER, $scorm, $cm->id, TOCJSLINK, $currentorg, $scoid, $mode, $attempt, true, true);
$sco = $result->sco;
if ($scorm->lastattemptlock == 1 && $result->attemptleft == 0) {
echo $OUTPUT->header();
echo $OUTPUT->notification(get_string('exceededmaxattempts', 'scorm'));
echo $OUTPUT->footer();
exit;
}
$scoidstr = '&amp;scoid='.$sco->id;
$modestr = '&amp;mode='.$mode;
$SESSION->scorm = new stdClass();
$SESSION->scorm->scoid = $sco->id;
$SESSION->scorm->scormstatus = 'Not Initialized';
$SESSION->scorm->scormmode = $mode;
$SESSION->scorm->attempt = $attempt;
// Mark module viewed.
$completion = new completion_info($course);
$completion->set_module_viewed($cm);
// Generate the exit button.
$exiturl = "";
if (empty($scorm->popup) || $displaymode == 'popup') {
if ($course->format == 'singleactivity' && $scorm->skipview == SCORM_SKIPVIEW_ALWAYS
&& !has_capability('mod/scorm:viewreport', context_module::instance($cm->id))) {
// Redirect students back to site home to avoid redirect loop.
$exiturl = $CFG->wwwroot;
} else {
// Redirect back to the correct section if one section per page is being used.
$exiturl = course_get_url($course, $cm->sectionnum)->out();
}
}
// Print the page header.
$PAGE->requires->data_for_js('scormplayerdata', Array('launch' => false,
'currentorg' => '',
'sco' => 0,
'scorm' => 0,
'courseid' => $scorm->course,
'cwidth' => $scorm->width,
'cheight' => $scorm->height,
'popupoptions' => $scorm->options), true);
$PAGE->requires->js('/mod/scorm/request.js', true);
$PAGE->requires->js('/lib/cookies.js', true);
if (file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'.js')) {
$PAGE->requires->js('/mod/scorm/datamodels/'.$scorm->version.'.js', true);
} else {
$PAGE->requires->js('/mod/scorm/datamodels/scorm_12.js', true);
}
$activityheader = $PAGE->activityheader;
$headerconfig = [
'description' => '',
'hidecompletion' => true
];
$activityheader->set_attrs($headerconfig);
echo $OUTPUT->header();
$PAGE->requires->string_for_js('navigation', 'scorm');
$PAGE->requires->string_for_js('toc', 'scorm');
$PAGE->requires->string_for_js('hide', 'moodle');
$PAGE->requires->string_for_js('show', 'moodle');
$PAGE->requires->string_for_js('popupsblocked', 'scorm');
$name = false;
// Exit button should ONLY be displayed when in the current window.
if ($displaymode !== 'popup') {
$renderer = $PAGE->get_renderer('mod_scorm');
echo $renderer->generate_exitbar($exiturl);
}
echo html_writer::start_div('', array('id' => 'scormpage'));
echo html_writer::start_div('', array('id' => 'tocbox'));
echo html_writer::div(html_writer::tag('script', '', array('id' => 'external-scormapi', 'type' => 'text/JavaScript')), '',
array('id' => 'scormapi-parent'));
if ($scorm->hidetoc == SCORM_TOC_POPUP or $mode == 'browse' or $mode == 'review') {
echo html_writer::start_div('mb-3', array('id' => 'scormtop'));
if ($mode == 'browse' || $mode == 'review') {
echo html_writer::div(get_string("{$mode}mode", 'scorm'), 'scorm-left h3', ['id' => 'scormmode']);
}
if ($scorm->hidetoc == SCORM_TOC_POPUP) {
echo html_writer::div($result->tocmenu, 'scorm-right', array('id' => 'scormnav'));
}
echo html_writer::end_div();
}
echo html_writer::start_div('', array('id' => 'toctree'));
if (empty($scorm->popup) || $displaymode == 'popup') {
echo $result->toc;
} else {
// Added incase javascript popups are blocked we don't provide a direct link
// to the pop-up as JS communication can fail - the user must disable their pop-up blocker.
$linkcourse = html_writer::link($CFG->wwwroot.'/course/view.php?id='.
$scorm->course, get_string('finishscormlinkname', 'scorm'));
echo $OUTPUT->box(get_string('finishscorm', 'scorm', $linkcourse), 'generalbox', 'altfinishlink');
}
echo html_writer::end_div(); // Toc tree ends.
echo html_writer::end_div(); // Toc box ends.
echo html_writer::tag('noscript', html_writer::div(get_string('noscriptnoscorm', 'scorm'), '', array('id' => 'noscript')));
if ($result->prerequisites) {
if ($scorm->popup != 0 && $displaymode !== 'popup') {
// Clean the name for the window as IE is fussy.
$name = preg_replace("/[^A-Za-z0-9]/", "", $scorm->name);
if (!$name) {
$name = 'DefaultPlayerWindow';
}
$name = 'scorm_'.$name;
echo html_writer::script('', $CFG->wwwroot.'/mod/scorm/player.js');
$url = new moodle_url($PAGE->url, array('scoid' => $sco->id, 'display' => 'popup', 'mode' => $mode));
echo html_writer::script(
js_writer::function_call('scorm_openpopup', Array($url->out(false),
$name, $scorm->options,
$scorm->width, $scorm->height)));
echo html_writer::tag('noscript', html_writer::tag('iframe', '', array('id' => 'main',
'class' => 'scoframe', 'name' => 'main', 'src' => 'loadSCO.php?id='.$cm->id.$scoidstr.$modestr)));
}
} else {
echo $OUTPUT->box(get_string('noprerequisites', 'scorm'));
}
echo html_writer::end_div(); // Scorm page ends.
$scoes = scorm_get_toc_object($USER, $scorm, $currentorg, $sco->id, $mode, $attempt);
$adlnav = scorm_get_adlnav_json($scoes['scoes']);
if (empty($scorm->popup) || $displaymode == 'popup') {
if (!isset($result->toctitle)) {
$result->toctitle = get_string('toc', 'scorm');
}
$jsmodule = array(
'name' => 'mod_scorm',
'fullpath' => '/mod/scorm/module.js',
'requires' => array('json'),
);
$scorm->nav = intval($scorm->nav);
$PAGE->requires->js_init_call('M.mod_scorm.init', array($scorm->nav, $scorm->navpositionleft, $scorm->navpositiontop,
$scorm->hidetoc, $collapsetocwinsize, $result->toctitle, $name, $sco->id, $adlnav), false, $jsmodule);
}
if (!empty($forcejs)) {
$message = $OUTPUT->box(get_string("forcejavascriptmessage", "scorm"), "generalbox boxaligncenter forcejavascriptmessage");
echo html_writer::tag('noscript', $message);
}
if (file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'.php')) {
include_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'.php');
} else {
include_once($CFG->dirroot.'/mod/scorm/datamodels/scorm_12.php');
}
// Add the keepalive system to keep checking for a connection.
\core\session\manager::keepalive('networkdropped', 'mod_scorm', 30, 10);
echo $OUTPUT->footer();
// Set the start time of this SCO.
scorm_insert_track($USER->id, $scorm->id, $scoid, $attempt, 'x.start.time', time());
+78
View File
@@ -0,0 +1,78 @@
<?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/>.
// This page is called via AJAX to repopulte the TOC when LMSFinish() is called.
require_once('../../config.php');
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
$id = optional_param('id', '', PARAM_INT); // Course Module ID, or
$a = optional_param('a', '', PARAM_INT); // scorm ID
$scoid = required_param('scoid', PARAM_INT); // sco ID
$attempt = required_param('attempt', PARAM_INT); // attempt number
$mode = optional_param('mode', 'normal', PARAM_ALPHA); // navigation mode
$currentorg = optional_param('currentorg', '', PARAM_RAW); // selected organization.
if (!empty($id)) {
if (! $cm = get_coursemodule_from_id('scorm', $id)) {
throw new \moodle_exception('invalidcoursemodule');
}
if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
throw new \moodle_exception('coursemisconf');
}
if (! $scorm = $DB->get_record("scorm", array("id" => $cm->instance))) {
throw new \moodle_exception('invalidcoursemodule');
}
} else if (!empty($a)) {
if (! $scorm = $DB->get_record("scorm", array("id" => $a))) {
throw new \moodle_exception('invalidcoursemodule');
}
if (! $course = $DB->get_record("course", array("id" => $scorm->course))) {
throw new \moodle_exception('coursemisconf');
}
if (! $cm = get_coursemodule_from_instance("scorm", $scorm->id, $course->id)) {
throw new \moodle_exception('invalidcoursemodule');
}
} else {
throw new \moodle_exception('missingparameter');
}
// PARAM_RAW is used for $currentorg, validate it against records stored in the table.
if (!empty($currentorg)) {
if (!$DB->record_exists('scorm_scoes', array('scorm' => $scorm->id, 'identifier' => $currentorg))) {
$currentorg = '';
}
}
$PAGE->set_url('/mod/scorm/prereqs.php', array('scoid' => $scoid, 'attempt' => $attempt, 'id' => $cm->id));
require_login($course, false, $cm);
$scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR)); // Just to be safe.
if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) {
$scorm->version = 'scorm_12';
}
require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php');
if (confirm_sesskey() && (!empty($scoid))) {
$result = true;
$request = null;
if (has_capability('mod/scorm:savetrack', context_module::instance($cm->id))) {
$result = scorm_get_toc($USER, $scorm, $cm->id, TOCJSLINK, $currentorg, $scoid, $mode, $attempt, true, false);
echo $result->toc;
}
}
+231
View File
@@ -0,0 +1,231 @@
<?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/>.
/**
* Defines the renderer for the scorm module.
*
* @package mod_scorm
* @copyright 2013 Dan Marsden
* @author Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* The renderer for the scorm module.
*
* @copyright 2013 Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_scorm_renderer extends plugin_renderer_base {
public function view_user_heading($user, $course, $baseurl, $attempt, $attemptids) {
$output = '';
$output .= $this->box_start('generalbox boxaligncenter');
$output .= html_writer::start_tag('div', array('class' => 'mdl-align'));
$output .= $this->user_picture($user, array('courseid' => $course->id, 'link' => true));
$url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $course->id));
$output .= html_writer::link($url, fullname($user));
$baseurl->param('attempt', '');
$pb = new mod_scorm_attempt_bar($attemptids, $attempt, $baseurl, 'attempt');
$output .= $this->render($pb);
$output .= html_writer::end_tag('div');
$output .= $this->box_end();
return $output;
}
/**
* scorm attempt bar renderer
*
* @param mod_scorm_attempt_bar $attemptbar
* @return string
*/
protected function render_mod_scorm_attempt_bar(mod_scorm_attempt_bar $attemptbar) {
$output = '';
$attemptbar = clone($attemptbar);
$attemptbar->prepare($this, $this->page, $this->target);
if (count($attemptbar->attemptids) > 1) {
$output .= get_string('attempt', 'scorm') . ':';
if (!empty($attemptbar->previouslink)) {
$output .= '&#160;(' . $attemptbar->previouslink . ')&#160;';
}
foreach ($attemptbar->attemptlinks as $link) {
$output .= "&#160;&#160;$link";
}
if (!empty($attemptbar->nextlink)) {
$output .= '&#160;&#160;(' . $attemptbar->nextlink . ')';
}
}
return html_writer::tag('div', $output, array('class' => 'paging'));
}
/**
* Rendered HTML for the report action is provided.
*
* @param \mod_scorm\output\actionbar $actionbar actionbar object.
* @return bool|string rendered HTML for the report action.
*/
public function report_actionbar(\mod_scorm\output\actionbar $actionbar): string {
return $this->render_from_template('mod_scorm/report_actionbar', $actionbar->export_for_template($this));
}
/**
* Rendered HTML for the user report action is provided
*
* @param \mod_scorm\output\userreportsactionbar $userreportsactionbar userreportsactionbar object
* @return string rendered HTML for the user report action.
*/
public function user_report_actionbar(\mod_scorm\output\userreportsactionbar $userreportsactionbar): string {
return $this->render_from_template('mod_scorm/user_report_actionbar', $userreportsactionbar->export_for_template($this));
}
/**
* Generate the SCORM's "Exit activity" button
*
* @param string $url The url to be hooked up to the exit button
* @return string
*/
public function generate_exitbar(string $url): string {
return $this->render_from_template('mod_scorm/player_exitbar', ['action' => $url]);
}
}
/**
* Component representing a SCORM attempts bar.
*
* @copyright 2013 Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package mod_scorm
*/
class mod_scorm_attempt_bar implements renderable {
/**
* @var array An array of the attemptids
*/
public $attemptids;
/**
* @var int The attempt you are currently viewing.
*/
public $attempt;
/**
* @var string|moodle_url If this is a string then it is the url which will be appended with $pagevar,
* an equals sign and the attempt number.
* If this is a moodle_url object then the pagevar param will be replaced by
* the attempt no, for each attempt.
*/
public $baseurl;
/**
* @var string This is the variable name that you use for the attempt in your
* code (ie. 'tablepage', 'blogpage', etc)
*/
public $pagevar;
/**
* @var string A HTML link representing the "previous" attempt.
*/
public $previouslink = null;
/**
* @var string A HTML link representing the "next" attempt.
*/
public $nextlink = null;
/**
* @var array An array of strings. One of them is just a string: the current attempt
*/
public $attemptlinks = array();
/**
* Constructor mod_scorm_attempt_bar with only the required params.
*
* @param array $attemptids an array of attempts the user has made
* @param int $attempt The attempt you are currently viewing
* @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added
* @param string $pagevar name of page parameter that holds the attempt number
*/
public function __construct($attemptids, $attempt, $baseurl, $pagevar = 'page') {
$this->attemptids = $attemptids;
$this->attempt = $attempt;
$this->baseurl = $baseurl;
$this->pagevar = $pagevar;
}
/**
* Prepares the scorm attempt bar for output.
*
* This method validates the arguments set up for the scorm attempt bar and then
* produces fragments of HTML to assist display later on.
*
* @param renderer_base $output
* @param moodle_page $page
* @param string $target
* @throws coding_exception
*/
public function prepare(renderer_base $output, moodle_page $page, $target) {
if (empty($this->attemptids)) {
throw new coding_exception('mod_scorm_attempt_bar requires a attemptids value.');
}
if (!isset($this->attempt) || is_null($this->attempt)) {
throw new coding_exception('mod_scorm_attempt_bar requires a attempt value.');
}
if (empty($this->baseurl)) {
throw new coding_exception('mod_scorm_attempt_bar requires a baseurl value.');
}
if (count($this->attemptids) > 1) {
$lastattempt = end($this->attemptids); // Get last attempt.
$firstattempt = reset($this->attemptids); // get first attempt.
$nextattempt = 0;
$prevattempt = null;
$previous = 0;
foreach ($this->attemptids as $attemptid) {
if ($this->attempt == $attemptid) {
$this->attemptlinks[] = $attemptid;
$prevattempt = $previous;
} else {
$attemptlink = html_writer::link(
new moodle_url($this->baseurl, array($this->pagevar => $attemptid)), $attemptid);
$this->attemptlinks[] = $attemptlink;
if (empty($nextattempt) && $prevattempt !== null) {
// Set the nextattempt var as we have set previous attempt earlier.
$nextattempt = $attemptid;
}
}
$previous = $attemptid; // Store this attempt as previous in case we need it.
}
if ($this->attempt != $firstattempt) {
$this->previouslink = html_writer::link(
new moodle_url($this->baseurl, array($this->pagevar => $prevattempt)),
get_string('previous'), array('class' => 'previous'));
}
if ($this->attempt != $lastattempt) {
$this->nextlink = html_writer::link(
new moodle_url($this->baseurl, array($this->pagevar => $nextattempt)),
get_string('next'), array('class' => 'next'));
}
}
}
}
+108
View File
@@ -0,0 +1,108 @@
<?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/>.
// This script uses installed report plugins to print scorm reports.
require_once("../../config.php");
require_once($CFG->libdir.'/tablelib.php');
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
require_once($CFG->dirroot.'/mod/scorm/reportsettings_form.php');
require_once($CFG->dirroot.'/mod/scorm/report/reportlib.php');
require_once($CFG->libdir.'/formslib.php');
define('SCORM_REPORT_DEFAULT_PAGE_SIZE', 20);
define('SCORM_REPORT_ATTEMPTS_ALL_STUDENTS', 0);
define('SCORM_REPORT_ATTEMPTS_STUDENTS_WITH', 1);
define('SCORM_REPORT_ATTEMPTS_STUDENTS_WITH_NO', 2);
$id = required_param('id', PARAM_INT);// Course Module ID, or ...
$download = optional_param('download', '', PARAM_RAW);
$mode = optional_param('mode', '', PARAM_ALPHA); // Report mode.
$cm = get_coursemodule_from_id('scorm', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
$scorm = $DB->get_record('scorm', array('id' => $cm->instance), '*', MUST_EXIST);
$contextmodule = context_module::instance($cm->id);
$reportlist = scorm_report_list($contextmodule);
$url = new moodle_url('/mod/scorm/report.php');
$url->param('id', $id);
if (empty($mode)) {
$mode = reset($reportlist);
} else if (!in_array($mode, $reportlist)) {
throw new \moodle_exception('erroraccessingreport', 'scorm');
}
$url->param('mode', $mode);
$PAGE->set_url($url);
require_login($course, false, $cm);
$PAGE->set_pagelayout('report');
require_capability('mod/scorm:viewreport', $contextmodule);
// Activate the secondary nav tab.
navigation_node::override_active_url(new moodle_url('/mod/scorm/report.php', ['id' => $id]));
if (count($reportlist) < 1) {
throw new \moodle_exception('erroraccessingreport', 'scorm');
}
// Trigger a report viewed event.
$event = \mod_scorm\event\report_viewed::create(array(
'context' => $contextmodule,
'other' => array(
'scormid' => $scorm->id,
'mode' => $mode
)
));
$event->add_record_snapshot('course_modules', $cm);
$event->add_record_snapshot('scorm', $scorm);
$event->trigger();
$userdata = null;
if (!empty($download)) {
$noheader = true;
}
// Print the page header.
if (empty($noheader)) {
$strreport = get_string('report', 'scorm');
$strattempt = get_string('attempt', 'scorm');
$PAGE->set_title("$course->shortname: ".format_string($scorm->name));
$PAGE->set_heading($course->fullname);
$PAGE->activityheader->set_attrs([
'hidecompletion' => true,
'description' => ''
]);
$PAGE->navbar->add($strreport, new moodle_url('/mod/scorm/report.php', array('id' => $cm->id)));
echo $OUTPUT->header();
}
// Open the selected Scorm report and display it.
$classname = "scormreport_{$mode}\\report";
$legacyclassname = "scorm_{$mode}_report";
$report = class_exists($classname) ? new $classname() : new $legacyclassname();
$report->display($scorm, $cm, $course, $download); // Run the report!
// Print footer.
if (empty($noheader)) {
echo $OUTPUT->footer();
}
@@ -0,0 +1,92 @@
<?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/>.
/**
* Privacy Subsystem implementation for scormreport_basic.
*
* @package scormreport_basic
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace scormreport_basic\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\writer;
/**
* Privacy Subsystem for scormreport_basic.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\user_preference_provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised item collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
// User preferences shared between different scorm reports.
$collection->add_user_preference('scorm_report_pagesize', 'privacy:metadata:preference:scorm_report_pagesize');
// User preferences specific for this scorm report.
$collection->add_user_preference('scorm_report_detailed', 'privacy:metadata:preference:scorm_report_detailed');
return $collection;
}
/**
* Store all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
static::get_and_export_user_preference($userid, 'scorm_report_pagesize');
static::get_and_export_user_preference($userid, 'scorm_report_detailed', true);
}
/**
* Get and export a user preference.
*
* @param int $userid The userid of the user whose data is to be exported.
* @param string $userpreference The user preference to export.
* @param boolean $transform If true, transform value to yesno.
*/
protected static function get_and_export_user_preference(int $userid, string $userpreference, $transform = false) {
$prefvalue = get_user_preferences($userpreference, null, $userid);
if ($prefvalue !== null) {
if ($transform) {
$transformedvalue = transform::yesno($prefvalue);
} else {
$transformedvalue = $prefvalue;
}
writer::export_user_preference(
'scormreport_basic',
$userpreference,
$transformedvalue,
get_string('privacy:metadata:preference:'.$userpreference, 'scormreport_basic')
);
}
}
}
+524
View File
@@ -0,0 +1,524 @@
<?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/>.
/**
* Core Report class of basic reporting plugin
* @package scormreport
* @subpackage basic
* @author Dan Marsden and Ankit Kumar Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace scormreport_basic;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/csvlib.class.php');
class report extends \mod_scorm\report {
/**
* displays the full report
* @param \stdClass $scorm full SCORM object
* @param \stdClass $cm - full course_module object
* @param \stdClass $course - full course object
* @param string $download - type of download being requested
*/
public function display($scorm, $cm, $course, $download) {
global $CFG, $DB, $OUTPUT, $PAGE;
$contextmodule = \context_module::instance($cm->id);
$action = optional_param('action', '', PARAM_ALPHA);
$attemptids = optional_param_array('attemptid', array(), PARAM_RAW);
$attemptsmode = optional_param('attemptsmode', SCORM_REPORT_ATTEMPTS_ALL_STUDENTS, PARAM_INT);
$PAGE->set_url(new \moodle_url($PAGE->url, array('attemptsmode' => $attemptsmode)));
// Scorm action bar for report.
if ($download === '') {
$actionbar = new \mod_scorm\output\actionbar($cm->id, true, $attemptsmode);
$renderer = $PAGE->get_renderer('mod_scorm');
echo $renderer->report_actionbar($actionbar);
}
if ($action == 'delete' && has_capability('mod/scorm:deleteresponses', $contextmodule) && confirm_sesskey()) {
if (scorm_delete_responses($attemptids, $scorm)) { // Delete responses.
echo $OUTPUT->notification(get_string('scormresponsedeleted', 'scorm'), 'notifysuccess');
}
}
// Find out current groups mode.
$currentgroup = groups_get_activity_group($cm, true);
// Detailed report.
$mform = new \mod_scorm_report_settings($PAGE->url, compact('currentgroup'));
if ($fromform = $mform->get_data()) {
$detailedrep = $fromform->detailedrep;
$pagesize = $fromform->pagesize;
set_user_preference('scorm_report_detailed', $detailedrep);
set_user_preference('scorm_report_pagesize', $pagesize);
} else {
$detailedrep = get_user_preferences('scorm_report_detailed', false);
$pagesize = get_user_preferences('scorm_report_pagesize', 0);
}
if ($pagesize < 1) {
$pagesize = SCORM_REPORT_DEFAULT_PAGE_SIZE;
}
// Select group menu.
$displayoptions = array();
$displayoptions['attemptsmode'] = $attemptsmode;
if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used.
if (!$download) {
groups_print_activity_menu($cm, new \moodle_url($PAGE->url, $displayoptions));
}
}
// We only want to show the checkbox to delete attempts
// if the user has permissions and if the report mode is showing attempts.
$candelete = has_capability('mod/scorm:deleteresponses', $contextmodule)
&& ($attemptsmode != SCORM_REPORT_ATTEMPTS_STUDENTS_WITH_NO);
// Select the students.
$nostudents = false;
list($allowedlistsql, $params) = get_enrolled_sql($contextmodule, 'mod/scorm:savetrack', (int) $currentgroup);
if (empty($currentgroup)) {
// All users who can attempt scoes.
if (!$DB->record_exists_sql($allowedlistsql, $params)) {
echo $OUTPUT->notification(get_string('nostudentsyet'));
$nostudents = true;
}
} else {
// All users who can attempt scoes and who are in the currently selected group.
if (!$DB->record_exists_sql($allowedlistsql, $params)) {
echo $OUTPUT->notification(get_string('nostudentsingroup'));
$nostudents = true;
}
}
if ( !$nostudents ) {
// Now check if asked download of data.
$coursecontext = \context_course::instance($course->id);
if ($download) {
$shortname = format_string($course->shortname, true, array('context' => $coursecontext));
$filename = clean_filename("$shortname ".format_string($scorm->name, true));
}
// Define table columns.
$columns = array();
$headers = array();
if (!$download && $candelete) {
$columns[] = 'checkbox';
$headers[] = $this->generate_master_checkbox();
}
if (!$download && $CFG->grade_report_showuserimage) {
$columns[] = 'picture';
$headers[] = '';
}
$columns[] = 'fullname';
$headers[] = get_string('name');
// TODO Does not support custom user profile fields (MDL-70456).
$extrafields = \core_user\fields::get_identity_fields($coursecontext, false);
foreach ($extrafields as $field) {
$columns[] = $field;
$headers[] = \core_user\fields::get_display_name($field);
}
$columns[] = 'attempt';
$headers[] = get_string('attempt', 'scorm');
$columns[] = 'start';
$headers[] = get_string('started', 'scorm');
$columns[] = 'finish';
$headers[] = get_string('last', 'scorm');
$columns[] = 'score';
$headers[] = get_string('score', 'scorm');
if ($detailedrep && $scoes = $DB->get_records('scorm_scoes', array("scorm" => $scorm->id), 'sortorder, id')) {
foreach ($scoes as $sco) {
if ($sco->launch != '') {
$columns[] = 'scograde'.$sco->id;
$headers[] = format_string($sco->title);
}
}
} else {
$scoes = null;
}
if (!$download) {
$table = new \flexible_table('mod-scorm-report');
$table->define_columns($columns);
$table->define_headers($headers);
$table->define_baseurl($PAGE->url);
$table->sortable(true);
$table->collapsible(true);
// This is done to prevent redundant data, when a user has multiple attempts.
$table->column_suppress('picture');
$table->column_suppress('fullname');
foreach ($extrafields as $field) {
$table->column_suppress($field);
}
$table->no_sorting('start');
$table->no_sorting('finish');
$table->no_sorting('score');
$table->no_sorting('checkbox');
$table->no_sorting('picture');
if ( $scoes ) {
foreach ($scoes as $sco) {
if ($sco->launch != '') {
$table->no_sorting('scograde'.$sco->id);
}
}
}
$table->column_class('picture', 'picture');
$table->column_class('fullname', 'bold');
$table->column_class('score', 'bold');
$table->set_attribute('cellspacing', '0');
$table->set_attribute('id', 'attempts');
$table->set_attribute('class', 'generaltable generalbox');
// Start working -- this is necessary as soon as the niceties are over.
$table->setup();
} else if ($download == 'ODS') {
require_once("$CFG->libdir/odslib.class.php");
$filename .= ".ods";
// Creating a workbook.
$workbook = new \MoodleODSWorkbook("-");
// Sending HTTP headers.
$workbook->send($filename);
// Creating the first worksheet.
$sheettitle = get_string('report', 'scorm');
$myxls = $workbook->add_worksheet($sheettitle);
// Format types.
$format = $workbook->add_format();
$format->set_bold(0);
$formatbc = $workbook->add_format();
$formatbc->set_bold(1);
$formatbc->set_align('center');
$formatb = $workbook->add_format();
$formatb->set_bold(1);
$formaty = $workbook->add_format();
$formaty->set_bg_color('yellow');
$formatc = $workbook->add_format();
$formatc->set_align('center');
$formatr = $workbook->add_format();
$formatr->set_bold(1);
$formatr->set_color('red');
$formatr->set_align('center');
$formatg = $workbook->add_format();
$formatg->set_bold(1);
$formatg->set_color('green');
$formatg->set_align('center');
// Here starts workshhet headers.
$colnum = 0;
foreach ($headers as $item) {
$myxls->write(0, $colnum, $item, $formatbc);
$colnum++;
}
$rownum = 1;
} else if ($download == 'Excel') {
require_once("$CFG->libdir/excellib.class.php");
$filename .= ".xls";
// Creating a workbook.
$workbook = new \MoodleExcelWorkbook("-");
// Sending HTTP headers.
$workbook->send($filename);
// Creating the first worksheet.
$sheettitle = get_string('report', 'scorm');
$myxls = $workbook->add_worksheet($sheettitle);
// Format types.
$format = $workbook->add_format();
$format->set_bold(0);
$formatbc = $workbook->add_format();
$formatbc->set_bold(1);
$formatbc->set_align('center');
$formatb = $workbook->add_format();
$formatb->set_bold(1);
$formaty = $workbook->add_format();
$formaty->set_bg_color('yellow');
$formatc = $workbook->add_format();
$formatc->set_align('center');
$formatr = $workbook->add_format();
$formatr->set_bold(1);
$formatr->set_color('red');
$formatr->set_align('center');
$formatg = $workbook->add_format();
$formatg->set_bold(1);
$formatg->set_color('green');
$formatg->set_align('center');
$colnum = 0;
foreach ($headers as $item) {
$myxls->write(0, $colnum, $item, $formatbc);
$colnum++;
}
$rownum = 1;
} else if ($download == 'CSV') {
$csvexport = new \csv_export_writer("tab");
$csvexport->set_filename($filename, ".txt");
$csvexport->add_data($headers);
}
// Construct the SQL.
$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 .= 'sa.scormid AS scormid, sa.attempt AS attempt ' . $selectfields . ' ';
// This part is the same for all cases - join users and user tracking tables.
$from = 'FROM {user} u ';
$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 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 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 (sa.userid IS NOT NULL OR sa.userid IS NULL)";
break;
}
$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;
if (!$download) {
$sort = $table->get_sql_sort();
} else {
$sort = '';
}
// Fix some wired sorting.
if (empty($sort)) {
$sort = ' ORDER BY uniqueid';
} else {
$sort = ' ORDER BY '.$sort;
}
if (!$download) {
// Add extra limits due to initials bar.
list($twhere, $tparams) = $table->get_sql_where();
if ($twhere) {
$where .= ' AND '.$twhere; // Initial bar.
$params = array_merge($params, $tparams);
}
if (!empty($countsql)) {
$count = $DB->get_record_sql($countsql, $params);
$totalinitials = $count->nbresults;
if ($twhere) {
$countsql .= ' AND '.$twhere;
}
$count = $DB->get_record_sql($countsql, $params);
$total = $count->nbresults;
}
$table->pagesize($pagesize, $total);
echo \html_writer::start_div('scormattemptcounts');
if ( $count->nbresults == $count->nbattempts ) {
echo get_string('reportcountattempts', 'scorm', $count);
} else if ( $count->nbattempts > 0 ) {
echo get_string('reportcountallattempts', 'scorm', $count);
} else {
echo $count->nbusers.' '.get_string('users');
}
echo \html_writer::end_div();
}
// Fetch the attempts.
if (!$download) {
$attempts = $DB->get_records_sql($select.$from.$where.$sort, $params,
$table->get_page_start(), $table->get_page_size());
echo \html_writer::start_div('', array('id' => 'scormtablecontainer'));
if ($candelete) {
// Start form.
$strreallydel = addslashes_js(get_string('deleteattemptcheck', 'scorm'));
echo \html_writer::start_tag('form', array('id' => 'attemptsform', 'method' => 'post',
'action' => $PAGE->url->out(false),
'onsubmit' => 'return confirm("'.$strreallydel.'");'));
echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'delete'));
echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
echo \html_writer::start_div('', array('style' => 'display: none;'));
echo \html_writer::input_hidden_params($PAGE->url);
echo \html_writer::end_div();
echo \html_writer::start_div();
}
$table->initialbars($totalinitials > 20); // Build table rows.
} else {
$attempts = $DB->get_records_sql($select.$from.$where.$sort, $params);
}
if ($attempts) {
foreach ($attempts as $scouser) {
$row = array();
if (!empty($scouser->attempt)) {
$timetracks = scorm_get_sco_runtime($scorm->id, false, $scouser->userid, $scouser->attempt);
} else {
$timetracks = '';
}
if (in_array('checkbox', $columns)) {
if ($candelete && !empty($timetracks->start)) {
$row[] = $this->generate_row_checkbox('attemptid[]', "{$scouser->userid}:{$scouser->attempt}");
} else if ($candelete) {
$row[] = '';
}
}
if (in_array('picture', $columns)) {
$user = new \stdClass();
$additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
$user = username_load_fields_from_object($user, $scouser, null, $additionalfields);
$user->id = $scouser->userid;
$row[] = $OUTPUT->user_picture($user, array('courseid' => $course->id));
}
if (!$download) {
$url = new \moodle_url('/user/view.php', array('id' => $scouser->userid, 'course' => $course->id));
$row[] = \html_writer::link($url, fullname($scouser));
} else {
$row[] = fullname($scouser);
}
foreach ($extrafields as $field) {
$row[] = s($scouser->{$field});
}
if (empty($timetracks->start)) {
$row[] = '-';
$row[] = '-';
$row[] = '-';
$row[] = '-';
} else {
if (!$download) {
$url = new \moodle_url('/mod/scorm/report/userreport.php', array('id' => $cm->id,
'user' => $scouser->userid, 'attempt' => $scouser->attempt, 'mode' => 'basic'));
$row[] = \html_writer::link($url, $scouser->attempt);
} else {
$row[] = $scouser->attempt;
}
if ($download == 'ODS' || $download == 'Excel' ) {
$row[] = userdate($timetracks->start, get_string("strftimedatetime", "langconfig"));
} else {
$row[] = userdate($timetracks->start);
}
if ($download == 'ODS' || $download == 'Excel' ) {
$row[] = userdate($timetracks->finish, get_string('strftimedatetime', 'langconfig'));
} else {
$row[] = userdate($timetracks->finish);
}
$row[] = scorm_grade_user_attempt($scorm, $scouser->userid, $scouser->attempt);
}
// Print out all scores of attempt.
if ($scoes) {
foreach ($scoes as $sco) {
if ($sco->launch != '') {
if ($trackdata = scorm_get_tracks($sco->id, $scouser->userid, $scouser->attempt)) {
if ($trackdata->status == '') {
$trackdata->status = 'notattempted';
}
$strstatus = get_string($trackdata->status, 'scorm');
// If raw score exists, print it.
if ($trackdata->score_raw != '') {
$score = $trackdata->score_raw;
// Add max score if it exists.
if (scorm_version_check($scorm->version, SCORM_13)) {
$maxkey = 'cmi.score.max';
} else {
$maxkey = 'cmi.core.score.max';
}
if (isset($trackdata->$maxkey)) {
$score .= '/'.$trackdata->$maxkey;
}
// Else print out status.
} else {
$score = $strstatus;
}
if (!$download) {
$url = new \moodle_url('/mod/scorm/report/userreporttracks.php', array('id' => $cm->id,
'scoid' => $sco->id, 'user' => $scouser->userid, 'attempt' => $scouser->attempt,
'mode' => 'basic'));
$row[] = $OUTPUT->pix_icon($trackdata->status, $strstatus, 'scorm') . '<br>' .
\html_writer::link($url, $score, array('title' => get_string('details', 'scorm')));
} else {
$row[] = $score;
}
} else {
// If we don't have track data, we haven't attempted yet.
$strstatus = get_string('notattempted', 'scorm');
if (!$download) {
$row[] = $OUTPUT->pix_icon('notattempted', $strstatus, 'scorm') . '<br>' . $strstatus;
} else {
$row[] = $strstatus;
}
}
}
}
}
if (!$download) {
$table->add_data($row);
} else if ($download == 'Excel' or $download == 'ODS') {
$colnum = 0;
foreach ($row as $item) {
$myxls->write($rownum, $colnum, $item, $format);
$colnum++;
}
$rownum++;
} else if ($download == 'CSV') {
$csvexport->add_data($row);
}
}
if (!$download) {
$table->finish_output();
if ($candelete) {
echo \html_writer::start_tag('table', array('id' => 'commands'));
echo \html_writer::start_tag('tr').\html_writer::start_tag('td');
echo $this->generate_delete_selected_button();
echo \html_writer::end_tag('td').\html_writer::end_tag('tr').\html_writer::end_tag('table');
// Close form.
echo \html_writer::end_tag('div');
echo \html_writer::end_tag('form');
}
}
} else {
if ($candelete && !$download) {
echo \html_writer::end_div();
echo \html_writer::end_tag('form');
$table->finish_output();
}
echo \html_writer::end_div();
}
// Show preferences form irrespective of attempts are there to report or not.
if (!$download) {
$mform->set_data(compact('detailedrep', 'pagesize', 'attemptsmode'));
$mform->display();
}
if ($download == 'Excel' or $download == 'ODS') {
$workbook->close();
exit;
} else if ($download == 'CSV') {
$csvexport->download_file();
exit;
}
} else {
echo $OUTPUT->notification(get_string('noactivity', 'scorm'));
}
}// Function ends.
}

Some files were not shown because too many files have changed in this diff Show More