diff --git a/lib/completionlib.php b/lib/completionlib.php index 7094c0b3a82..f9f153e995a 100644 --- a/lib/completionlib.php +++ b/lib/completionlib.php @@ -1296,6 +1296,22 @@ class completion_info { } } + /** + * Aggregate activity completion state + * + * @param int $type Aggregation type (COMPLETION_* constant) + * @param bool $old Old state + * @param bool $new New state + * @return bool + */ + public static function aggregate_completion_states($type, $old, $new) { + if ($type == COMPLETION_AND) { + return $old && $new; + } else { + return $old || $new; + } + } + /** * This is to be used only for system errors (things that shouldn't happen) * and not user-level errors. diff --git a/mod/scorm/backup/moodle2/backup_scorm_stepslib.php b/mod/scorm/backup/moodle2/backup_scorm_stepslib.php index fd14f5635e0..27862e4c204 100644 --- a/mod/scorm/backup/moodle2/backup_scorm_stepslib.php +++ b/mod/scorm/backup/moodle2/backup_scorm_stepslib.php @@ -44,7 +44,8 @@ class backup_scorm_activity_structure_step extends backup_activity_structure_ste 'sha1hash', 'md5hash', 'revision', 'launch', 'skipview', 'hidebrowse', 'hidetoc', 'hidenav', 'auto', 'popup', 'options', 'width', - 'height', 'timeopen', 'timeclose', 'timemodified')); + 'height', 'timeopen', 'timeclose', 'timemodified', + 'completionstatusrequired', 'completionscorerequired')); $scoes = new backup_nested_element('scoes'); diff --git a/mod/scorm/db/install.xml b/mod/scorm/db/install.xml index d9434b4fc88..c5611a40a7d 100644 --- a/mod/scorm/db/install.xml +++ b/mod/scorm/db/install.xml @@ -1,220 +1,221 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - -
- -
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd" + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/mod/scorm/db/upgrade.php b/mod/scorm/db/upgrade.php index 7f2bb3a349c..a7bcbd77e6b 100644 --- a/mod/scorm/db/upgrade.php +++ b/mod/scorm/db/upgrade.php @@ -38,6 +38,28 @@ function xmldb_scorm_upgrade($oldversion) { // Moodle v2.2.0 release upgrade line // Put any upgrade step following this + if ($oldversion < 2012032100) { + unset_config('updatetime', 'scorm'); + upgrade_mod_savepoint(true, 2012032100, 'scorm'); + } + + // Adding completion fields to scorm table + if ($oldversion < 2012032101) { + $table = new xmldb_table('scorm'); + + $field = new xmldb_field('completionstatusrequired', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, null, null, null, 'timemodified'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $field = new xmldb_field('completionscorerequired', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, null, null, null, 'completionstatusrequired'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + upgrade_mod_savepoint(true, 2012032101, 'scorm'); + } + return true; } diff --git a/mod/scorm/lang/en/scorm.php b/mod/scorm/lang/en/scorm.php index 9aa0f000574..bc271c80eb0 100644 --- a/mod/scorm/lang/en/scorm.php +++ b/mod/scorm/lang/en/scorm.php @@ -62,6 +62,12 @@ $string['browsemode'] = 'Preview mode'; $string['browserepository'] = 'Browse repository'; $string['cannotfindsco'] = 'Could not find SCO'; $string['completed'] = 'Completed'; +$string['completionscorerequired'] = 'Require minimum score'; +$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.'; +$string['completionstatus_passed'] = 'Passed'; +$string['completionstatus_completed'] = 'Completed'; +$string['completionstatusrequired'] = 'Require status'; +$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'; diff --git a/mod/scorm/lib.php b/mod/scorm/lib.php index e7dfeb8840a..15b7b44188b 100644 --- a/mod/scorm/lib.php +++ b/mod/scorm/lib.php @@ -41,6 +41,31 @@ define('SCORM_12', 1); define('SCORM_13', 2); define('SCORM_AICC', 3); +/** + * Return an array of status options + * + * Optionally with translated strings + * + * @param bool $with_strings (optional) + * @return array + */ +function scorm_status_options($with_strings = false) { + // Id's are important as they are bits + $options = array( + 2 => 'passed', + 4 => 'completed' + ); + + if ($with_strings) { + foreach ($options as $key => $value) { + $options[$key] = get_string('completionstatus_'.$value, 'scorm'); + } + } + + return $options; +} + + /** * Given an object containing all the necessary data, * (defined by the form in mod_form.php) this function @@ -654,6 +679,18 @@ function scorm_grade_item_update($scorm, $grades=null) { $grades = null; } + // Update activity completion if applicable + // Get course info + $course = new object(); + $course->id = $scorm->course; + + $cm = get_coursemodule_from_instance('scorm', $scorm->id, $course->id); + // CM will be false if this has been run from scorm_add_instance + if ($cm) { + $completion = new completion_info($course); + $completion->update_state($cm, COMPLETION_COMPLETE); + } + return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, $grades, $params); } @@ -936,6 +973,7 @@ function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownlo * @uses FEATURE_GROUPMEMBERSONLY * @uses FEATURE_MOD_INTRO * @uses FEATURE_COMPLETION_TRACKS_VIEWS + * @uses FEATURE_COMPLETION_HAS_RULES * @uses FEATURE_GRADE_HAS_GRADE * @uses FEATURE_GRADE_OUTCOMES * @param string $feature FEATURE_xx constant for requested feature @@ -948,6 +986,7 @@ function scorm_supports($feature) { case FEATURE_GROUPMEMBERSONLY: return true; case FEATURE_MOD_INTRO: return true; case FEATURE_COMPLETION_TRACKS_VIEWS: return true; + case FEATURE_COMPLETION_HAS_RULES: return true; case FEATURE_GRADE_HAS_GRADE: return true; case FEATURE_GRADE_OUTCOMES: return true; case FEATURE_BACKUP_MOODLE2: return true; @@ -1136,3 +1175,101 @@ function scorm_version_check($scormversion, $version='') { } return false; } + +/** + * Obtains the automatic completion state for this scorm based on any conditions + * in scorm settings. + * + * @param object $course Course + * @param object $cm Course-module + * @param int $userid User ID + * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) + * @return bool True if completed, false if not. (If no conditions, then return + * value depends on comparison type) + */ +function scorm_get_completion_state($course, $cm, $userid, $type) { + global $DB; + + $result = $type; + + // Get scorm + if (!$scorm = $DB->get_record('scorm', array('id' => $cm->instance))) { + print_error('cannotfindscorm'); + } + + // Get user's tracks data + $tracks = $DB->get_records_sql( + " + SELECT + id, + element, + value + FROM + {scorm_scoes_track} + WHERE + scormid = ? + AND userid = ? + AND element IN + ( + 'cmi.core.lesson_status', + 'cmi.completion_status', + 'cmi.success_status', + 'cmi.core.score.raw', + 'cmi.score.raw' + ) + ", + array($scorm->id, $userid) + ); + + if (!$tracks) { + return completion_info::aggregate_completion_states($type, $result, false); + } + + // Check for status + if ($scorm->completionstatusrequired !== null) { + + // Get status + $statuses = array_flip(scorm_status_options()); + $nstatus = 0; + + foreach ($tracks as $track) { + if (!in_array($track->element, array('cmi.core.lesson_status', 'cmi.completion_status', 'cmi.success_status'))) { + continue; + } + + if (array_key_exists($track->value, $statuses)) { + $nstatus |= $statuses[$track->value]; + } + } + + if ($scorm->completionstatusrequired & $nstatus) { + return completion_info::aggregate_completion_states($type, $result, true); + } else { + return completion_info::aggregate_completion_states($type, $result, false); + } + + } + + // Check for score + if ($scorm->completionscorerequired !== null) { + $maxscore = -1; + + foreach ($tracks as $track) { + if (!in_array($track->element, array('cmi.core.score.raw', 'cmi.score.raw'))) { + continue; + } + + if (strlen($track->value) && floatval($track->value) >= $maxscore) { + $maxscore = floatval($track->value); + } + } + + if ($scorm->completionscorerequired <= $maxscore) { + return completion_info::aggregate_completion_states($type, $result, true); + } else { + return completion_info::aggregate_completion_states($type, $result, false); + } + } + + return $result; +} diff --git a/mod/scorm/locallib.php b/mod/scorm/locallib.php index be0bedeafcb..3e39b6dcfb9 100644 --- a/mod/scorm/locallib.php +++ b/mod/scorm/locallib.php @@ -421,7 +421,8 @@ function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $valu } if (strstr($element, '.score.raw') || - (($element == 'cmi.core.lesson_status' || $element == 'cmi.completion_status') && ($track->value == 'completed' || $track->value == 'passed'))) { + (in_array($element, array('cmi.completion_status', 'cmi.core.lesson_status', 'cmi.success_status')) + && in_array($track->value, array('completed', 'passed')))) { $scorm = $DB->get_record('scorm', array('id' => $scormid)); include_once($CFG->dirroot.'/mod/scorm/lib.php'); scorm_update_grades($scorm, $userid); diff --git a/mod/scorm/mod_form.php b/mod/scorm/mod_form.php index 247b3212110..f936c31bcf7 100644 --- a/mod/scorm/mod_form.php +++ b/mod/scorm/mod_form.php @@ -315,6 +315,24 @@ class mod_scorm_mod_form extends moodleform_mod { if (empty($default_values['timeclose'])) { $default_values['timeclose'] = 0; } + + // Set some completion default data + if (!empty($default_values['completionstatusrequired']) && !is_array($default_values['completionstatusrequired'])) { + // Unpack values + $cvalues = array(); + foreach (scorm_status_options() as $key => $value) { + if (($default_values['completionstatusrequired'] & $key) == $key) { + $cvalues[$key] = 1; + } + } + + $default_values['completionstatusrequired'] = $cvalues; + } + + if (!isset($default_values['completionscorerequired']) || !strlen($default_values['completionscorerequired'])) { + $default_values['completionscoredisabled'] = 1; + } + } function validation($data, $files) { @@ -421,4 +439,78 @@ class mod_scorm_mod_form extends moodleform_mod { $this->data_preprocessing($default_values); parent::set_data($default_values); } + + function add_completion_rules() { + $mform =& $this->_form; + $items = array(); + + // Require score + $group = array(); + $group[] =& $mform->createElement('text', 'completionscorerequired', '', array('size' => 5)); + $group[] =& $mform->createElement('checkbox', 'completionscoredisabled', null, get_string('disable')); + $mform->setType('completionscorerequired', PARAM_INT); + $mform->addGroup($group, 'completionscoregroup', get_string('completionscorerequired', 'scorm'), '', false); + $mform->addHelpButton('completionscoregroup', 'completionscorerequired', 'scorm'); + $mform->disabledIf('completionscorerequired', 'completionscoredisabled', 'checked'); + $mform->setDefault('completionscorerequired', 0); + + $items[] = 'completionscoregroup'; + + + // Require status + $first = true; + $firstkey = null; + foreach (scorm_status_options(true) as $key => $value) { + $name = null; + $key = 'completionstatusrequired['.$key.']'; + if ($first) { + $name = get_string('completionstatusrequired', 'scorm'); + $first = false; + $firstkey = $key; + } + $mform->addElement('checkbox', $key, $name, $value); + $mform->setType($key, PARAM_BOOL); + $items[] = $key; + } + $mform->addHelpButton($firstkey, 'completionstatusrequired', 'scorm'); + + return $items; + } + + function completion_rule_enabled($data) { + $status = !empty($data['completionstatusrequired']); + $score = empty($data['completionscoredisabled']) && strlen($data['completionscorerequired']); + + return $status || $score; + } + + function get_data($slashed = true) { + $data = parent::get_data($slashed); + + if (!$data) { + return false; + } + + // Turn off completion settings if the checkboxes aren't ticked + $autocompletion = !empty($data->completion) && $data->completion == COMPLETION_TRACKING_AUTOMATIC; + + if (isset($data->completionstatusrequired) && is_array($data->completionstatusrequired)) { + $total = 0; + foreach (array_keys($data->completionstatusrequired) as $state) { + $total |= $state; + } + + $data->completionstatusrequired = $total; + } + + if (!$autocompletion) { + $data->completionstatusrequired = null; + } + + if (!empty($data->completionscoredisabled) || !$autocompletion) { + $data->completionscorerequired = null; + } + + return $data; + } } diff --git a/mod/scorm/version.php b/mod/scorm/version.php index 78b35627274..c4630d0a4d1 100644 --- a/mod/scorm/version.php +++ b/mod/scorm/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$module->version = 2012030700; // The current module version (Date: YYYYMMDDXX) +$module->version = 2012032101; // The current module version (Date: YYYYMMDDXX) $module->requires = 2012030100.04; // Requires this Moodle version $module->component = 'mod_scorm'; // Full name of the plugin (used for diagnostics) $module->cron = 300;