Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
<?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 {
< print_error('cannotfindsco', 'scorm');
> throw new \moodle_exception('cannotfindsco', 'scorm');
} if (!$sco = scorm_get_sco($scoid)) {
< print_error('cannotfindsco', 'scorm');
> 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; }