<?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/>.
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/mod/scorm/datamodels/scormlib.php');
require_once($CFG->dirroot.'/mod/scorm/datamodels/sequencinglib.php');
function scorm_seq_overall ($scoid, $userid, $request, $attempt) {
$seq = scorm_seq_navigation($scoid, $userid, $request, $attempt);
if ($seq->navigation) {
if ($seq->termination != null) {
$seq = scorm_seq_termination($scoid, $userid, $seq);
}
if ($seq->sequencing != null) {
$seq = scorm_seq_sequencing($scoid, $userid, $seq);
if ($seq->sequencing == 'exit') { // Return the control to the LTS.
return 'true';
}
}
if ($seq->delivery != null) {
$seq = scorm_sequencing_delivery($scoid, $userid, $seq);
$seq = scorm_content_delivery_environment ($seq, $userid);
}
}
if ($seq->exception != null) {
$seq = scorm_sequencing_exception($seq);
}
return 'true';
}
function scorm_seq_navigation ($scoid, $userid, $request, $attempt=0) {
global $DB;
// Sequencing structure.
$seq = new stdClass();
$seq->currentactivity = scorm_get_sco($scoid);
$seq->traversaldir = null;
$seq->nextactivity = null;
$seq->deliveryvalid = null;
$seq->attempt = $attempt;
$seq->identifiedactivity = null;
$seq->delivery = null;
$seq->deliverable = false;
$seq->active = scorm_seq_is('active', $scoid, $userid);
$seq->suspended = scorm_seq_is('suspended', $scoid, $userid);
$seq->navigation = null;
$seq->termination = null;
$seq->sequencing = null;
$seq->target = null;
$seq->endsession = null;
$seq->exception = null;
$seq->reachable = true;
$seq->prevact = true;
$sco = scorm_get_sco($scoid);
switch ($request) {
case 'start_':
if (empty($seq->currentactivity)) {
$seq->navigation = true;
$seq->sequencing = 'start';
} else {
$seq->exception = 'NB.2.1-1'; // Sequencing session already begun.
}
break;
case 'resumeall_':
if (empty($seq->currentactivity)) {
// TODO: I think it's suspend instead of suspendedactivity.
< if ($track = $DB->get_record('scorm_scoes_track',
< array('scoid' => $scoid, 'userid' => $userid, 'element' => 'suspendedactivity'))) {
> if (scorm_get_sco_value($scoid, $userid, 'suspendedactivity')) {
$seq->navigation = true;
$seq->sequencing = 'resumeall';
} else {
$seq->exception = 'NB.2.1-3'; // No suspended activity found.
}
} else {
$seq->exception = 'NB.2.1-1'; // Sequencing session already begun.
}
break;
case 'continue_':
case 'previous_':
if (!empty($seq->currentactivity)) {
$sco = $seq->currentactivity;
if ($sco->parent != '/') {
if ($parentsco = scorm_get_parent($sco)) {
if (isset($parentsco->flow) && ($parentsco->flow == true)) { // I think it's parentsco.
// Current activity is active!
if (scorm_seq_is('active', $sco->id, $userid)) {
if ($request == 'continue_') {
$seq->navigation = true;
$seq->termination = 'exit';
$seq->sequencing = 'continue';
} else {
if (!isset($parentsco->forwardonly) || ($parentsco->forwardonly == false)) {
$seq->navigation = true;
$seq->termination = 'exit';
$seq->sequencing = 'previous';
} else {
$seq->exception = 'NB.2.1-5'; // Violates control mode.
}
}
}
}
}
}
} else {
$seq->exception = 'NB.2.1-2'; // Current activity not defined.
}
break;
case 'forward_':
case 'backward_':
$seq->exception = 'NB.2.1-7'; // None to be done, behavior not defined.
break;
case 'exit_':
case 'abandon_':
if (!empty($seq->currentactivity)) {
// Current activity is active !
$seq->navigation = true;
$seq->termination = substr($request, 0, -1);
$seq->sequencing = 'exit';
} else {
$seq->exception = 'NB.2.1-2'; // Current activity not defined.
}
case 'exitall_':
case 'abandonall_':
case 'suspendall_':
if (!empty($seq->currentactivity)) {
$seq->navigation = true;
$seq->termination = substr($request, 0, -1);
$seq->sequencing = 'exit';
} else {
$seq->exception = 'NB.2.1-2'; // Current activity not defined.
}
break;
default: // Example {target=<STRING>}choice.
if ($targetsco = $DB->get_record('scorm_scoes', array('scorm' => $sco->scorm, 'identifier' => $request))) {
if ($targetsco->parent != '/') {
$seq->target = $request;
} else {
if ($parentsco = scorm_get_parent($targetsco)) {
if (!isset($parentsco->choice) || ($parentsco->choice == true)) {
$seq->target = $request;
}
}
}
if ($seq->target != null) {
if (empty($seq->currentactivity)) {
$seq->navigation = true;
$seq->sequencing = 'choice';
} else {
if (!$sco = scorm_get_sco($scoid)) {
return $seq;
}
if ($sco->parent != $targetsco->parent) {
$ancestors = scorm_get_ancestors($sco);
$commonpos = scorm_find_common_ancestor($ancestors, $targetsco);
if ($commonpos !== false) {
if ($activitypath = array_slice($ancestors, 0, $commonpos)) {
foreach ($activitypath as $activity) {
if (scorm_seq_is('active', $activity->id, $userid) &&
(isset($activity->choiceexit) && ($activity->choiceexit == false))) {
$seq->navigation = false;
$seq->termination = null;
$seq->sequencing = null;
$seq->target = null;
$seq->exception = 'NB.2.1-8'; // Violates control mode.
return $seq;
}
}
} else {
$seq->navigation = false;
$seq->termination = null;
$seq->sequencing = null;
$seq->target = null;
$seq->exception = 'NB.2.1-9';
}
}
}
// Current activity is active !
$seq->navigation = true;
$seq->sequencing = 'choice';
}
} else {
$seq->exception = 'NB.2.1-10'; // Violates control mode.
}
} else {
$seq->exception = 'NB.2.1-11'; // Target activity does not exists.
}
break;
}
return $seq;
}
function scorm_seq_termination ($seq, $userid) {
if (empty($seq->currentactivity)) {
$seq->termination = false;
$seq->exception = 'TB.2.3-1';
return $seq;
}
$sco = $seq->currentactivity;
if ((($seq->termination == 'exit') || ($seq->termination == 'abandon')) && !$seq->active) {
$seq->termination = false;
$seq->exception = 'TB.2.3-2';
return $seq;
}
switch ($seq->termination) {
case 'exit':
scorm_seq_end_attempt($sco, $userid, $seq);
$seq = scorm_seq_exit_action_rules($seq, $userid);
do {
$exit = false;// I think this is false. Originally this was true.
$seq = scorm_seq_post_cond_rules($seq, $userid);
if ($seq->termination == 'exitparent') {
if ($sco->parent != '/') {
$sco = scorm_get_parent($sco);
$seq->currentactivity = $sco;
$seq->active = scorm_seq_is('active', $sco->id, $userid);
scorm_seq_end_attempt($sco, $userid, $seq);
$exit = true; // I think it's true. Originally this was false.
} else {
$seq->termination = false;
$seq->exception = 'TB.2.3-4';
return $seq;
}
}
} while (($exit == false) && ($seq->termination == 'exit'));
if ($seq->termination == 'exit') {
$seq->termination = true;
return $seq;
}
case 'exitall':
if ($seq->active) {
scorm_seq_end_attempt($sco, $userid, $seq);
}
// Terminate Descendent Attempts Process.
if ($ancestors = scorm_get_ancestors($sco)) {
foreach ($ancestors as $ancestor) {
scorm_seq_end_attempt($ancestor, $userid, $seq);
$seq->currentactivity = $ancestor;
}
}
$seq->active = scorm_seq_is('active', $seq->currentactivity->id, $userid);
$seq->termination = true;
$seq->sequencing = 'exit';
break;
case 'suspendall':
if (($seq->active) || ($seq->suspended)) {
scorm_seq_set('suspended', $sco->id, $userid, $attempt);
} else {
if ($sco->parent != '/') {
$parentsco = scorm_get_parent($sco);
scorm_seq_set('suspended', $parentsco->id, $userid, $attempt);
} else {
$seq->termination = false;
$seq->exception = 'TB.2.3-3';
}
}
if ($ancestors = scorm_get_ancestors($sco)) {
foreach ($ancestors as $ancestor) {
scorm_seq_set('active', $ancestor->id, $userid, $attempt, false);
scorm_seq_set('suspended', $ancestor->id, $userid, $attempt);
$seq->currentactivity = $ancestor;
}
$seq->termination = true;
$seq->sequencing = 'exit';
} else {
$seq->termination = false;
$seq->exception = 'TB.2.3-5';
}
break;
case 'abandon':
scorm_seq_set('active', $sco->id, $userid, $attempt, false);
$seq->active = null;
$seq->termination = true;
break;
case 'abandonall':
if ($ancestors = scorm_get_ancestors($sco)) {
foreach ($ancestors as $ancestor) {
scorm_seq_set('active', $ancestor->id, $userid, $attempt, false);
$seq->currentactivity = $ancestor;
}
$seq->termination = true;
$seq->sequencing = 'exit';
} else {
$seq->termination = false;
$seq->exception = 'TB.2.3-6';
}
break;
default:
$seq->termination = false;
$seq->exception = 'TB.2.3-7';
break;
}
return $seq;
}
function scorm_seq_end_attempt($sco, $userid, $seq) {
global $DB;
if (scorm_is_leaf($sco)) {
if (!isset($sco->tracked) || ($sco->tracked == 1)) {
if (!scorm_seq_is('suspended', $sco->id, $userid)) {
if (!isset($sco->completionsetbycontent) || ($sco->completionsetbycontent == 0)) {
if (!scorm_seq_is('attemptprogressstatus', $sco->id, $userid, $seq->attempt)) {
< $incomplete = $DB->get_field('scorm_scoes_track', 'value',
< array('scoid' => $sco->id,
< 'userid' => $userid,
< 'element' => 'cmi.completion_status'));
< if ($incomplete != 'incomplete') {
> $r = scorm_get_sco_value($sco->id, $userid, 'cmi.completion_status');
> if ($r->value != 'incomplete') {
scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt);
scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt);
}
}
}
if (!isset($sco->objectivesetbycontent) || ($sco->objectivesetbycontent == 0)) {
if ($objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id))) {
foreach ($objectives as $objective) {
if ($objective->primaryobj) {
if (!scorm_seq_is('objectiveprogressstatus', $sco->id, $userid, $seq->attempt)) {
scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $seq->attempt);
scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $seq->attempt);
}
}
}
}
}
}
}
} else if ($children = scorm_get_children($sco)) {
$suspended = false;
foreach ($children as $child) {
if (scorm_seq_is('suspended', $child, $userid, $seq->attempt)) {
$suspended = true;
break;
}
}
if ($suspended) {
scorm_seq_set('suspended', $sco, $userid, $seq->attempt);
} else {
scorm_seq_set('suspended', $sco, $userid, $seq->attempt, false);
}
}
scorm_seq_set('active', $sco->id, $userid, $seq->attempt, false);
scorm_seq_overall_rollup($sco, $userid, $seq);
}
function scorm_seq_is($what, $scoid, $userid, $attempt=0) {
< global $DB;
<
// Check if passed activity $what is active.
$active = false;
< if ($track = $DB->get_record('scorm_scoes_track',
< array('scoid' => $scoid, 'userid' => $userid, 'attempt' => $attempt, 'element' => $what))) {
> if (scorm_get_sco_value($scoid, $userid, $what, $attempt)) {
$active = true;
}
return $active;
}
function scorm_seq_set($what, $scoid, $userid, $attempt=0, $value='true') {
global $DB;
$sco = scorm_get_sco($scoid);
// Set passed activity to active or not.
if ($value == false) {
< $DB->delete_records('scorm_scoes_track', array('scoid' => $scoid, 'userid' => $userid,
< 'attempt' => $attempt, 'element' => $what));
> $params = ['userid' => $userid, 'scormid' => $sco->scorm, 'attempt' => $attempt, 'element' => $what];
> $sql = "WHERE scoid = :scoid AND attemptid = :attemptid AND elementid = (SELECT id
> FROM {scorm_element}
> WHERE element = :element)";
> $DB->delete_records_select('scorm_scoes_value', $sql, $params);
} else {
scorm_insert_track($userid, $sco->scorm, $sco->id, $attempt, $what, $value);
}
// Update grades in gradebook.
$scorm = $DB->get_record('scorm', array('id' => $sco->scorm));
scorm_update_grades($scorm, $userid, true);
}
function scorm_evaluate_condition ($rollupruleconds, $sco, $userid) {
global $DB;
$res = false;
if (strpos($rollupruleconds, 'and ')) {
$rollupruleconds = array_filter(explode(' and ', $rollupruleconds));
$conditioncombination = 'all';
} else {
$rollupruleconds = array_filter(explode(' or ', $rollupruleconds));
$conditioncombination = 'or';
}
foreach ($rollupruleconds as $rolluprulecond) {
$notflag = false;
if (strpos($rolluprulecond, 'not') !== false) {
$rolluprulecond = str_replace('not', '', $rolluprulecond);
$notflag = true;
}
$conditionarray['condition'] = $rolluprulecond;
$conditionarray['notflag'] = $notflag;
$conditions[] = $conditionarray;
}
foreach ($conditions as $condition) {
$checknot = true;
$res = false;
if ($condition['notflag']) {
$checknot = false;
}
switch ($condition['condition']) {
case 'satisfied':
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivesatisfiedstatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'objectivesatisfiedstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'objectiveprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
}
break;
case 'objectiveStatusKnown':
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'objectiveprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
break;
case 'notobjectiveStatusKnown':
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'objectiveprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
break;
case 'objectiveMeasureKnown':
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivemeasurestatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'objectivemeasurestatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
break;
case 'notobjectiveMeasureKnown':
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivemeasurestatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'objectivemeasurestatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
break;
case 'completed':
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'attemptcompletionstatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'attemptcompletionstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'attemptprogressstatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'attemptprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
}
break;
case 'attempted':
< $attempt = $DB->get_field('scorm_scoes_track', 'attempt',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'x.start.time'));
< if ($checknot && $attempt > 0) {
> $r = scorm_get_sco_value($sco->id, $userid, 'x.start.time');
> if ($checknot && $r->attempt > 0) {
$res = true;
< } else if (!$checknot && $attempt <= 0) {
> } else if (!$checknot && $r->attempt <= 0) {
$res = true;
}
break;
case 'attemptLimitExceeded':
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'activityprogressstatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'activityprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid,
< 'element' => 'limitconditionattemptlimitcontrol'));
> $r = scorm_get_sco_value($sco->id, $userid, 'limitconditionattemptlimitcontrol');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
< if ($r = $DB->get_field('scorm_scoes_track', 'attempt', array('scoid' => $sco->id, 'userid' => $userid)) &&
< $r2 = $DB->get_record('scorm_scoes_track', array('scoid' => $sco->id, 'userid' => $userid,
< 'element' => 'limitconditionattemptlimit')) ) {
<
< if ($checknot && ($r->value >= $r2->value)) {
> $sql = "SELECT max(attempt) as attempt
> FROM {scorm_attempt} a
> JOIN {scorm_scoes_value} v on v.attemptid = a.id
> WHERE v.scoid = :scoid AND a.userid = :userid";
> $r2 = scorm_get_sco_value($sco->id, $userid, 'limitconditionattemptlimit');
> $attempts = $DB->get_field_sql($sql, ['scoid' => $sco->id, 'userid' => $userid]);
> if (!empty($attempts) && !empty($r2)) {
> if ($checknot && ($attempts >= $r2->value)) {
$res = true;
< } else if (!$checknot && ($r->value < $r2->value)) {
> } else if (!$checknot && ($attempts < $r2->value)) {
$res = true;
}
}
}
}
break;
case 'activityProgressKnown':
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'activityprogressstatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'activityprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'attemptprogressstatus'));
> $r = scorm_get_sco_value($sco->id, $userid, 'attemptprogressstatus');
if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
$res = true;
}
}
break;
}
if ($conditioncombination == 'all' && !$res) {
break;
} else if ($conditioncombination == 'or' && $res) {
break;
}
}
return $res;
}
function scorm_check_activity ($activity, $userid) {
$act = scorm_seq_rules_check($activity, 'disabled');
if ($act != null) {
return true;
}
if (scorm_limit_cond_check ($activity, $userid)) {
return true;
}
return false;
}
function scorm_limit_cond_check ($activity, $userid) {
global $DB;
if (isset($activity->tracked) && ($activity->tracked == 0)) {
return false;
}
if (scorm_seq_is('active', $activity->id, $userid) || scorm_seq_is('suspended', $activity->id, $userid)) {
return false;
}
if (!isset($activity->limitcontrol) || ($activity->limitcontrol == 1)) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'activityattemptcount'));
> $r = scorm_get_sco_value($activity->id, $userid, 'activityattemptcount');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattempt)) {
return true;
}
}
if (!isset($activity->limitabsdurcontrol) || ($activity->limitabsdurcontrol == 1)) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'activityabsoluteduration'));
> $r = scorm_get_sco_value($activity->id, $userid, 'activityabsoluteduration');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitabsduration)) {
return true;
}
}
if (!isset($activity->limitexpdurcontrol) || ($activity->limitexpdurcontrol == 1)) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'activityexperiencedduration'));
> $r = scorm_get_sco_value($activity->id, $userid, 'activityexperiencedduration');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitexpduration)) {
return true;
}
}
if (!isset($activity->limitattabsdurcontrol) || ($activity->limitattabsdurcontrol == 1)) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'attemptabsoluteduration'));
> $r = scorm_get_sco_value($activity->id, $userid, 'attemptabsoluteduration');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattabsduration)) {
return true;
}
}
if (!isset($activity->limitattexpdurcontrol) || ($activity->limitattexpdurcontrol == 1)) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'attemptexperiencedduration'));
> $r = scorm_get_sco_value($activity->id, $userid, 'attemptexperiencedduration');
if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattexpduration)) {
return true;
}
}
if (!isset($activity->limitbegincontrol) || ($activity->limitbegincontrol == 1)) {
< $r = $DB->get_record('scorm_scoes_track',
< array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'begintime'));
> $r = scorm_get_sco_value($activity->id, $userid, 'begintime');
if (isset($activity->limitbegintime) && time() >= $activity->limitbegintime) {
return true;
}
}
if (!isset($activity->limitbegincontrol) || ($activity->limitbegincontrol == 1)) {
if (isset($activity->limitbegintime) && time() < $activity->limitbegintime) {
return true;
}
}
if (!isset($activity->limitendcontrol) || ($activity->limitendcontrol == 1)) {
if (isset($activity->limitendtime) && time() > $activity->limitendtime) {
return true;
}
}
return false;
}
function scorm_seq_rules_check ($sco, $action) {
global $DB;
$act = null;
if ($rules = $DB->get_records('scorm_seq_ruleconds', array('scoid' => $sco->id, 'action' => $action))) {
foreach ($rules as $rule) {
if ($act = scorm_seq_rule_check($sco, $rule)) {
return $act;
}
}
}
return $act;
}
function scorm_seq_rule_check ($sco, $rule) {
global $DB;
$bag = Array();
$cond = '';
$ruleconds = $DB->get_records('scorm_seq_rulecond', array('scoid' => $sco->id, 'ruleconditionsid' => $rule->id));
foreach ($ruleconds as $rulecond) {
if ($rulecond->operator == 'not') {
if ($rulecond->cond != 'unknown' ) {
$rulecond->cond = 'not'.$rulecond->cond;
}
}
$bag[] = $rulecond->cond;
}
if (empty($bag)) {
$cond = 'unknown';
return $cond;
}
if ($rule->conditioncombination == 'all') {
foreach ($bag as $con) {
$cond = $cond.' and '.$con;
}
} else {
foreach ($bag as $con) {
$cond = $cond.' or '.$con;
}
}
return $cond;
}
function scorm_seq_overall_rollup($sco, $userid, $seq) {
if ($ancestors = scorm_get_ancestors($sco)) {
foreach ($ancestors as $ancestor) {
if (!scorm_is_leaf($ancestor)) {
scorm_seq_measure_rollup($sco, $userid, $seq->attempt);
}
scorm_seq_objective_rollup($sco, $userid, $seq->attempt);
scorm_seq_activity_progress_rollup($sco, $userid, $seq);
}
}
}
function scorm_seq_measure_rollup($sco, $userid, $attempt = 0) {
global $DB;
$totalmeasure = 0; // Check if there is something similar in the database.
$valid = false; // Same as in the last line.
$countedmeasures = 0; // Same too.
$targetobjective = null;
$objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id));
foreach ($objectives as $objective) {
if ($objective->primaryobj == true) { // Objective contributes to rollup.
$targetobjective = $objective;
break;
}
}
if ($targetobjective != null) {
$children = scorm_get_children($sco);
if (!empty ($children)) {
foreach ($children as $child) {
$child = scorm_get_sco ($child);
if (!isset($child->tracked) || ($child->tracked == 1)) {
$rolledupobjective = null;// We set the rolled up activity to undefined.
$objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $child->id));
foreach ($objectives as $objective) {
if ($objective->primaryobj == true) {// Objective contributes to rollup I'm using primaryobj field, but not.
$rolledupobjective = $objective;
break;
}
}
if ($rolledupobjective != null) {
$child = scorm_get_sco($child->id);
$countedmeasures = $countedmeasures + ($child->measureweight);
if (!scorm_seq_is('objectivemeasurestatus', $sco->id, $userid, $attempt)) {
< $normalizedmeasure = $DB->get_record('scorm_scoes_track',
< array('scoid' => $child->id, 'userid' => $userid, 'element' => 'objectivenormalizedmeasure'));
> $normalizedmeasure = scorm_get_sco_value($child->id, $userid, 'objectivenormalizedmeasure');
$totalmeasure = $totalmeasure + (($normalizedmeasure->value) * ($child->measureweight));
$valid = true;
}
}
}
}
}
if (!$valid) {
scorm_seq_set('objectivemeasurestatus', $sco->id, $userid, $attempt, false);
} else {
if ($countedmeasures > 0) {
scorm_seq_set('objectivemeasurestatus', $sco->id, $userid, $attempt);
$val = $totalmeasure / $countedmeasures;
scorm_seq_set('objectivenormalizedmeasure', $sco->id, $userid, $attempt, $val);
} else {
scorm_seq_set('objectivemeasurestatus', $sco->id, $userid, $attempt, false);
}
}
}
}
function scorm_seq_objective_rollup($sco, $userid, $attempt = 0) {
global $DB;
scorm_seq_objective_rollup_measure($sco, $userid, $attempt);
scorm_seq_objective_rollup_rules($sco, $userid, $attempt);
scorm_seq_objective_rollup_default($sco, $userid, $attempt);
/*
if ($targetobjective->satisfiedbymeasure) {
scorm_seq_objective_rollup_measure($sco, $userid);
}
else{
if ((scorm_seq_rollup_rule_check($sco, $userid, 'incomplete'))
|| (scorm_seq_rollup_rule_check($sco, $userid, 'completed'))) {
scorm_seq_objective_rollup_rules($sco, $userid);
}
else{
$rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid'=>$sco->id, 'userid'=>$userid));
foreach ($rolluprules as $rolluprule) {
$rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid'=>$rolluprule->id));
foreach ($rollupruleconds as $rolluprulecond) {
switch ($rolluprulecond->cond!='satisfied'
&& $rolluprulecond->cond!='completed' && $rolluprulecond->cond!='attempted') {
scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, false);
break;
}
}
}
}
*/
}
function scorm_seq_objective_rollup_measure($sco, $userid, $attempt = 0) {
global $DB;
$targetobjective = null;
$objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id));
foreach ($objectives as $objective) {
if ($objective->primaryobj == true) {
$targetobjective = $objective;
break;
}
}
if ($targetobjective != null) {
if ($targetobjective->satisfiedbymeasure) {
if (!scorm_seq_is('objectiveprogressstatus', $sco->id, $userid, $attempt)) {
scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt, false);
} else {
if (scorm_seq_is('active', $sco->id, $userid, $attempt)) {
$isactive = true;
} else {
$isactive = false;
}
<
< $normalizedmeasure = $DB->get_record('scorm_scoes_track',
< array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivenormalizedmeasure'));
> $normalizedmeasure = scorm_get_sco_value($sco->id, $userid, 'objectivenormalizedmeasure');
$sco = scorm_get_sco ($sco->id);
if (!$isactive || ($isactive &&
(!isset($sco->measuresatisfactionifactive) || $sco->measuresatisfactionifactive == true))) {
if (isset($normalizedmeasure->value) && ($normalizedmeasure->value >= $targetobjective->minnormalizedmeasure)) {
scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt);
scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt);
} else {
// TODO: handle the case where cmi.success_status is passed and objectivenormalizedmeasure undefined.
scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt);
}
} else {
scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt, false);
}
}
}
}
}
function scorm_seq_objective_rollup_default($sco, $userid, $attempt = 0) {
global $DB;
if (!(scorm_seq_rollup_rule_check($sco, $userid, 'incomplete')) && !(scorm_seq_rollup_rule_check($sco, $userid, 'completed'))) {
if ($rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid' => $sco->id))) {
foreach ($rolluprules as $rolluprule) {
$rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid' => $rolluprule->id));
foreach ($rollupruleconds as $rolluprulecond) {
if ($rolluprulecond->cond != 'satisfied' && $rolluprulecond->cond != 'completed' &&
$rolluprulecond->cond != 'attempted') {
scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt, false);
break;
}
}
}
}
}
}
function scorm_seq_objective_rollup_rules($sco, $userid, $attempt = 0) {
global $DB;
$targetobjective = null;
$objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id));
foreach ($objectives as $objective) {
if ($objective->primaryobj == true) {// Objective contributes to rollup I'm using primaryobj field, but not.
$targetobjective = $objective;
break;
}
}
if ($targetobjective != null) {
if (scorm_seq_rollup_rule_check($sco, $userid, 'notsatisfied')) {// With not satisfied rollup for the activity.
scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt);
scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt, false);
}
if (scorm_seq_rollup_rule_check($sco, $userid, 'satisfied')) {// With satisfied rollup for the activity.
scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt);
scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt);
}
}
}
function scorm_seq_activity_progress_rollup ($sco, $userid, $seq) {
if (scorm_seq_rollup_rule_check($sco, $userid, 'incomplete')) {
// Incomplete rollup action.
scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt, false);
scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt, true);
}
if (scorm_seq_rollup_rule_check($sco, $userid, 'completed')) {
// Incomplete rollup action.
scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt, true);
scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt, true);
}
}
function scorm_seq_rollup_rule_check ($sco, $userid, $action) {
global $DB;
if ($rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid' => $sco->id, 'action' => $action))) {
$childrenbag = Array ();
$children = scorm_get_children ($sco);
foreach ($rolluprules as $rolluprule) {
foreach ($children as $child) {
< /*$tracked = $DB->get_records('scorm_scoes_track', array('scoid'=>$child->id, 'userid'=>$userid));
< if ($tracked && $tracked->attemp != 0) {*/
$child = scorm_get_sco ($child);
if (!isset($child->tracked) || ($child->tracked == 1)) {
if (scorm_seq_check_child ($child, $action, $userid)) {
$rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid' => $rolluprule->id));
$evaluate = scorm_seq_evaluate_rollupcond($child, $rolluprule->conditioncombination,
$rollupruleconds, $userid);
if ($evaluate == 'unknown') {
array_push($childrenbag, 'unknown');
} else {
if ($evaluate == true) {
array_push($childrenbag, true);
} else {
array_push($childrenbag, false);
}
}
}
}
}
$change = false;
switch ($rolluprule->childactivityset) {
case 'all':
// I think I can use this condition instead equivalent to OR.
if ((array_search(false, $childrenbag) === false) && (array_search('unknown', $childrenbag) === false)) {
$change = true;
}
break;
case 'any':
// I think I can use this condition instead equivalent to OR.
if (array_search(true, $childrenbag) !== false) {
$change = true;
}
break;
case 'none':
// I think I can use this condition instead equivalent to OR.
if ((array_search(true, $childrenbag) === false) && (array_search('unknown', $childrenbag) === false)) {
$change = true;
}
break;
case 'atleastcount':
// I think I can use this condition instead equivalent to OR.
foreach ($childrenbag as $itm) {
$cont = 0;
if ($itm === true) {
$cont++;
}
if ($cont >= $rolluprule->minimumcount) {
$change = true;
}
}
break;
case 'atleastcount':
foreach ($childrenbag as $itm) {// I think I can use this condition instead equivalent to OR.
$cont = 0;
if ($itm === true) {
$cont++;
}
if ($cont >= $rolluprule->minimumcount) {
$change = true;
}
}
break;
case 'atleastpercent':
foreach ($childrenbag as $itm) {// I think I can use this condition instead equivalent to OR.
$cont = 0;
if ($itm === true) {
$cont++;
}
if (($cont / count($childrenbag)) >= $rolluprule->minimumcount) {
$change = true;
}
}
break;
}
if ($change == true) {
return true;
}
}
}
return false;
}
function scorm_seq_flow_tree_traversal($activity, $direction, $childrenflag, $prevdirection, $seq, $userid, $skip = false) {
$revdirection = false;
$parent = scorm_get_parent($activity);
if (!empty($parent)) {
$children = scorm_get_available_children($parent);
} else {
$children = array();
}
$childrensize = count($children);
if (($prevdirection != null && $prevdirection == 'backward') && ($children[$childrensize - 1]->id == $activity->id)) {
$direction = 'backward';
$activity = $children[0];
$revdirection = true;
}
if ($direction == 'forward') {
$ancestors = scorm_get_ancestors($activity);
$ancestorsroot = array_reverse($ancestors);
$preorder = array();
$preorder = scorm_get_preorder($preorder, $ancestorsroot[0]);
$preordersize = count($preorder);
if (($activity->id == $preorder[$preordersize - 1]->id) || (($activity->parent == '/') && !($childrenflag))) {
$seq->endsession = true;
$seq->nextactivity = null;
return $seq;
}
if (scorm_is_leaf ($activity) || !$childrenflag) {
if ($children[$childrensize - 1]->id == $activity->id) {
$seq = scorm_seq_flow_tree_traversal ($parent, $direction, false, null, $seq, $userid);
if ($seq->nextactivity->launch == null) {
$seq = scorm_seq_flow_tree_traversal ($seq->nextactivity, $direction, true, null, $seq, $userid);
}
return $seq;
} else {
$position = 0;
foreach ($children as $sco) {
if ($sco->id == $activity->id) {
break;
}
$position++;
}
if ($position != ($childrensize - 1)) {
$seq->nextactivity = $children[$position + 1];
$seq->traversaldir = $direction;
return $seq;
} else {
$siblings = scorm_get_siblings($activity);
$children = scorm_get_children($siblings[0]);
$seq->nextactivity = $children[0];
return $seq;
}
}
} else {
$children = scorm_get_available_children($activity);
if (!empty($children)) {
$seq->traversaldir = $direction;
$seq->nextactivity = $children[0];
return $seq;
} else {
$seq->traversaldir = null;
$seq->nextactivity = null;
$seq->exception = 'SB.2.1-2';
return $seq;
}
}
} else if ($direction == 'backward') {
if ($activity->parent == '/') {
$seq->traversaldir = null;
$seq->nextactivity = null;
$seq->exception = 'SB.2.1-3';
return $seq;
}
if (scorm_is_leaf ($activity) || !$childrenflag) {
if (!$revdirection) {
if (isset($parent->forwardonly) && ($parent->forwardonly == true && !$skip)) {
$seq->traversaldir = null;
$seq->nextactivity = null;
$seq->exception = 'SB.2.1-4';
return $seq;
}
}
if ($children[0]->id == $activity->id) {
$seq = scorm_seq_flow_tree_traversal($parent, 'backward', false, null, $seq, $userid);
return $seq;
} else {
$ancestors = scorm_get_ancestors($activity);
$ancestorsroot = array_reverse($ancestors);
$preorder = array();
$preorder = scorm_get_preorder($preorder, $ancestorsroot[0]);
$position = 0;
foreach ($preorder as $sco) {
if ($sco->id == $activity->id) {
break;
}
$position++;
}
if (isset($preorder[$position])) {
$seq->nextactivity = $preorder[$position - 1];
$seq->traversaldir = $direction;
}
return $seq;
}
} else {
$children = scorm_get_available_children($activity);
if (!empty($children)) {
if (isset($parent->flow) && ($parent->flow == true)) {
$seq->traversaldir = 'forward';
$seq->nextactivity = $children[0];
return $seq;
} else {
$seq->traversaldir = 'backward';
$seq->nextactivity = $children[count($children) - 1];
return $seq;
}
} else {
$seq->traversaldir = null;
$seq->nextactivity = null;
$seq->exception = 'SB.2.1-2';
return $seq;
}
}
}
}
// Returns the next activity on the tree, traversal direction, control returned to the LTS, (may) exception.
function scorm_seq_flow_activity_traversal ($activity, $userid, $direction, $childrenflag, $prevdirection, $seq) {
$parent = scorm_get_parent ($activity);
if (!isset($parent->flow) || ($parent->flow == false)) {
$seq->deliverable = false;
$seq->exception = 'SB.2.2-1';
$seq->nextactivity = $activity;
return $seq;
}
$rulecheck = scorm_seq_rules_check($activity, 'skip');
if ($rulecheck != null) {
$skip = scorm_evaluate_condition ($rulecheck, $activity, $userid);
if ($skip) {
$seq = scorm_seq_flow_tree_traversal($activity, $direction, false, $prevdirection, $seq, $userid, $skip);
$seq = scorm_seq_flow_activity_traversal($seq->nextactivity, $userid, $direction,
$childrenflag, $prevdirection, $seq);
} else if (!empty($seq->identifiedactivity)) {
$seq->nextactivity = $activity;
}
return $seq;
}
$ch = scorm_check_activity ($activity, $userid);
if ($ch) {
$seq->deliverable = false;
$seq->exception = 'SB.2.2-2';
$seq->nextactivity = $activity;
return $seq;
}
if (!scorm_is_leaf($activity)) {
$seq = scorm_seq_flow_tree_traversal ($activity, $direction, true, null, $seq, $userid);
if ($seq->identifiedactivity == null) {
$seq->deliverable = false;
$seq->nextactivity = $activity;
return $seq;
} else {
if ($direction == 'backward' && $seq->traversaldir == 'forward') {
$seq = scorm_seq_flow_activity_traversal($seq->identifiedactivity, $userid,
'forward', $childrenflag, 'backward', $seq);
} else {
$seq = scorm_seq_flow_activity_traversal($seq->identifiedactivity, $userid,
$direction, $childrenflag, null, $seq);
}
return $seq;
}
}
$seq->deliverable = true;
$seq->nextactivity = $activity;
$seq->exception = null;
return $seq;
}
function scorm_seq_flow ($activity, $direction, $seq, $childrenflag, $userid) {
// TODO: $PREVDIRECTION NOT DEFINED YET.
$prevdirection = null;
$seq = scorm_seq_flow_tree_traversal ($activity, $direction, $childrenflag, $prevdirection, $seq, $userid);
if ($seq->nextactivity == null) {
$seq->nextactivity = $activity;
$seq->deliverable = false;
return $seq;
} else {
$activity = $seq->nextactivity;
$seq = scorm_seq_flow_activity_traversal($activity, $userid, $direction, $childrenflag, null, $seq);
return $seq;
}
}
/**
* Sets up $userdata array and default values for SCORM 1.3 .
*
* @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.3 package
*/
function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) {
global $DB, $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)) {
// According to SCORM 2004(RTE V1, 4.2.8), only cmi.exit==suspend should allow previous datamodel elements on re-launch.
if (isset($usertrack->{'cmi.exit'}) && ($usertrack->{'cmi.exit'} == 'suspend')) {
foreach ($usertrack as $key => $value) {
$userdata->$key = $value;
}
} else {
$userdata->status = '';
$userdata->score_raw = '';
}
} 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 (!isset($userdata->{'cmi.exit'}) || $userdata->{'cmi.exit'} == 'time-out' || $userdata->{'cmi.exit'} == 'normal') {
$userdata->entry = 'ab-initio';
} else {
if (isset($userdata->{'cmi.exit'}) && ($userdata->{'cmi.exit'} == 'suspend' || $userdata->{'cmi.exit'} == 'logout')) {
$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';
}
$objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $scoid));
$index = 0;
foreach ($objectives as $objective) {
if (!empty($objective->minnormalizedmeasure)) {
$userdata->{'cmi.scaled_passing_score'} = $objective->minnormalizedmeasure;
}
if (!empty($objective->objectiveid)) {
$userdata->{'cmi.objectives.N'.$index.'.id'} = $objective->objectiveid;
$index++;
}
}
$def = array();
$def['cmi.learner_id'] = $userdata->student_id;
$def['cmi.learner_name'] = $userdata->student_name;
$def['cmi.mode'] = $userdata->mode;
$def['cmi.entry'] = $userdata->entry;
$def['cmi.exit'] = scorm_isset($userdata, 'cmi.exit');
$def['cmi.credit'] = scorm_isset($userdata, 'credit');
$def['cmi.completion_status'] = scorm_isset($userdata, 'cmi.completion_status', 'unknown');
$def['cmi.completion_threshold'] = scorm_isset($userdata, 'threshold');
$def['cmi.learner_preference.audio_level'] = scorm_isset($userdata, 'cmi.learner_preference.audio_level', 1);
$def['cmi.learner_preference.language'] = scorm_isset($userdata, 'cmi.learner_preference.language');
$def['cmi.learner_preference.delivery_speed'] = scorm_isset($userdata, 'cmi.learner_preference.delivery_speed');
$def['cmi.learner_preference.audio_captioning'] = scorm_isset($userdata, 'cmi.learner_preference.audio_captioning', 0);
$def['cmi.location'] = scorm_isset($userdata, 'cmi.location');
$def['cmi.max_time_allowed'] = scorm_isset($userdata, 'attemptAbsoluteDurationLimit');
$def['cmi.progress_measure'] = scorm_isset($userdata, 'cmi.progress_measure');
$def['cmi.scaled_passing_score'] = scorm_isset($userdata, 'cmi.scaled_passing_score');
$def['cmi.score.scaled'] = scorm_isset($userdata, 'cmi.score.scaled');
$def['cmi.score.raw'] = scorm_isset($userdata, 'cmi.score.raw');
$def['cmi.score.min'] = scorm_isset($userdata, 'cmi.score.min');
$def['cmi.score.max'] = scorm_isset($userdata, 'cmi.score.max');
$def['cmi.success_status'] = scorm_isset($userdata, 'cmi.success_status', 'unknown');
$def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data');
$def['cmi.time_limit_action'] = scorm_isset($userdata, 'timelimitaction');
$def['cmi.total_time'] = scorm_isset($userdata, 'cmi.total_time', 'PT0H0M0S');
$def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms');
return $def;
}