Merge branch 'master_MDL-44712' of git://github.com/danmarsden/moodle
authorDan Poltawski <dan@moodle.com>
Mon, 22 Aug 2016 09:33:31 +0000 (10:33 +0100)
committerDan Poltawski <dan@moodle.com>
Mon, 22 Aug 2016 09:33:31 +0000 (10:33 +0100)
14 files changed:
mod/scorm/backup/moodle1/lib.php
mod/scorm/backup/moodle2/backup_scorm_stepslib.php
mod/scorm/backup/moodle2/restore_scorm_stepslib.php
mod/scorm/classes/external.php
mod/scorm/db/install.xml
mod/scorm/db/upgrade.php
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/scorm/mod_form.php
mod/scorm/tests/behat/completion_condition_require_status.feature [new file with mode: 0644]
mod/scorm/tests/externallib_test.php
mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12.zip [new file with mode: 0644]
mod/scorm/tests/packages/readme_moodle.txt
mod/scorm/version.php

index df3c78e..77bbda6 100644 (file)
@@ -66,6 +66,7 @@ class moodle1_mod_scorm_handler extends moodle1_mod_handler {
                         'timeopen' => '0',
                         'timeclose' => '0',
                         'introformat' => '0',
+                        'completionstatusallscos' => 0,
                     ),
                     'renamefields' => array(
                         'summary' => 'intro'
index 452a53f..1995b41 100644 (file)
@@ -46,7 +46,7 @@ class backup_scorm_activity_structure_step extends backup_activity_structure_ste
             'auto', 'popup', 'options', 'width',
             'height', 'timeopen', 'timeclose', 'timemodified',
             'completionstatusrequired', 'completionscorerequired',
-            'displayactivityname'));
+            'completionstatusallscos', 'displayactivityname'));
 
         $scoes = new backup_nested_element('scoes');
 
index 4678037..02b620c 100644 (file)
@@ -68,7 +68,9 @@ class restore_scorm_activity_structure_step extends restore_activity_structure_s
         if (!isset($data->displayactivityname)) {
             $data->displayactivityname = true;
         }
-
+        if (!isset($data->completionstatusallscos)) {
+            $data->completionstatusallscos = false;
+        }
         // insert the scorm record
         $newitemid = $DB->insert_record('scorm', $data);
         // immediately after inserting "activity" record, call this
index d41122f..72a00c2 100644 (file)
@@ -731,7 +731,8 @@ class mod_scorm_external extends external_api {
                     if (has_capability('moodle/course:manageactivities', $context)) {
 
                         $additionalfields = array('updatefreq', 'options', 'completionstatusrequired', 'completionscorerequired',
-                                                    'autocommit', 'timemodified', 'section', 'visible', 'groupmode', 'groupingid');
+                                                  'completionstatusallscos', 'autocommit', 'timemodified', 'section', 'visible',
+                                                  'groupmode', 'groupingid');
                         $viewablefields = array_merge($viewablefields, $additionalfields);
 
                     }
@@ -818,6 +819,7 @@ class mod_scorm_external extends external_api {
                             'completionstatusrequired' => new external_value(PARAM_INT, 'Status passed/completed required?',
                                                                                 VALUE_OPTIONAL),
                             'completionscorerequired' => new external_value(PARAM_INT, 'Minimum score required', VALUE_OPTIONAL),
+                            'completionstatusallscos' => new external_value(PARAM_INT, 'Require all scos to return completion status', VALUE_OPTIONAL),
                             'autocommit' => new external_value(PARAM_BOOL, 'Save track data automatically?', VALUE_OPTIONAL),
                             'timemodified' => new external_value(PARAM_INT, 'Time of last modification', VALUE_OPTIONAL),
                             'section' => new external_value(PARAM_INT, 'Course section id', VALUE_OPTIONAL),
index 0aed27f..07252be 100644 (file)
@@ -45,6 +45,7 @@
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="completionstatusrequired" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="completionscorerequired" TYPE="int" LENGTH="2" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="completionstatusallscos" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="displayactivityname" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
         <FIELD NAME="autocommit" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
       </FIELDS>
index f08a1e3..41b028f 100644 (file)
@@ -108,5 +108,17 @@ function xmldb_scorm_upgrade($oldversion) {
     // Moodle v3.1.0 release upgrade line.
     // Put any upgrade step following this.
 
+    // MDL-44712 improve multi-sco activity completion.
+    if ($oldversion < 2016080900) {
+        $table = new xmldb_table('scorm');
+
+        $field = new xmldb_field('completionstatusallscos', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'completionscorerequired');
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        upgrade_mod_savepoint(true, 2016080900, 'scorm');
+    }
+
     return true;
 }
index 8b2d014..35a3349 100644 (file)
@@ -83,6 +83,8 @@ $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['completionstatusallscos'] = 'Require all scos to return completion status';
+$string['completionstatusallscos_help'] = 'Some SCORM packages contain multiple components or "scos" - when this is enabled all scos within the package must return the relevant lesson_status for this activity to be flagged complete.';
 $string['completionstatusrequired'] = 'Require status';
 $string['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.';
@@ -413,3 +415,4 @@ Notes on handling of multiple attempts:
 $string['whatgradedesc'] = 'Whether the highest, average (mean), first or last completed attempt is recorded in the gradebook if multiple attempts are allowed.';
 $string['width'] = 'Width';
 $string['window'] = 'Window';
+$string['youmustselectastatus'] = 'You must select a status to require';
index db9b026..f2c1d9f 100644 (file)
@@ -102,6 +102,9 @@ function scorm_add_instance($scorm, $mform=null) {
     if (empty($scorm->timeclose)) {
         $scorm->timeclose = 0;
     }
+    if (empty($scorm->completionstatusallscos)) {
+        $scorm->completionstatusallscos = 0;
+    }
     $cmid       = $scorm->coursemodule;
     $cmidnumber = $scorm->cmidnumber;
     $courseid   = $scorm->course;
@@ -192,6 +195,9 @@ function scorm_update_instance($scorm, $mform=null) {
     if (empty($scorm->timeclose)) {
         $scorm->timeclose = 0;
     }
+    if (empty($scorm->completionstatusallscos)) {
+        $scorm->completionstatusallscos = 0;
+    }
 
     $cmid       = $scorm->coursemodule;
     $cmidnumber = $scorm->cmidnumber;
@@ -1210,6 +1216,7 @@ function scorm_get_completion_state($course, $cm, $userid, $type) {
             "
             SELECT
                 id,
+                scoid,
                 element,
                 value
             FROM
@@ -1240,23 +1247,32 @@ function scorm_get_completion_state($course, $cm, $userid, $type) {
         // Get status.
         $statuses = array_flip(scorm_status_options());
         $nstatus = 0;
-
+        // Check any track for these values.
+        $scostatus = array();
         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)) {
+                $scostatus[$track->scoid] = true;
                 $nstatus |= $statuses[$track->value];
             }
         }
 
-        if ($scorm->completionstatusrequired & $nstatus) {
+        if (!empty($scorm->completionstatusallscos)) {
+            // Iterate over all scos and make sure each has a lesson_status.
+            $scos = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id, 'scormtype' => 'sco'));
+            foreach ($scos as $sco) {
+                if (empty($scostatus[$sco->id])) {
+                    return completion_info::aggregate_completion_states($type, $result, false);
+                }
+            }
+            return completion_info::aggregate_completion_states($type, $result, true);
+        } else 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.
index 4b00473..5f6e5b0 100644 (file)
@@ -448,6 +448,17 @@ class mod_scorm_mod_form extends moodleform_mod {
                 $errors['timeclose'] = get_string('closebeforeopen', 'scorm');
             }
         }
+        if (!empty($data['completionstatusallscos'])) {
+            $requirestatus = false;
+            foreach (scorm_status_options(true) as $key => $value) {
+                if (!empty($data['completionstatusrequired'][$key])) {
+                    $requirestatus = true;
+                }
+            }
+            if (!$requirestatus) {
+                $errors['completionstatusallscos'] = get_string('youmustselectastatus', 'scorm');
+            }
+        }
 
         return $errors;
     }
@@ -513,6 +524,12 @@ class mod_scorm_mod_form extends moodleform_mod {
         }
         $mform->addHelpButton($firstkey, 'completionstatusrequired', 'scorm');
 
+        $mform->addElement('checkbox', 'completionstatusallscos', get_string('completionstatusallscos', 'scorm'));
+        $mform->setType('completionstatusallscos', PARAM_BOOL);
+        $mform->addHelpButton('completionstatusallscos', 'completionstatusallscos', 'scorm');
+        $mform->setDefault('completionstatusallscos', 0);
+        $items[] = 'completionstatusallscos';
+
         return $items;
     }
 
diff --git a/mod/scorm/tests/behat/completion_condition_require_status.feature b/mod/scorm/tests/behat/completion_condition_require_status.feature
new file mode 100644 (file)
index 0000000..084e231
--- /dev/null
@@ -0,0 +1,187 @@
+@mod @mod_scorm @_file_upload @_switch_frame
+Feature: Scorm multi-sco completion
+  In order to let students access a scorm package
+  As a teacher
+  I need to add scorm activity to a course
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | student1 | Student | 1 | student1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+
+  @javascript
+  Scenario: Test completion with a single sco completion.
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I click on "Edit settings" "link" in the "Administration" "block"
+    And I set the following fields to these values:
+      | Enable completion tracking | Yes |
+    And I press "Save and display"
+    And I add a "SCORM package" to section "1"
+    And I set the following fields to these values:
+      | Name | Basic Multi-sco SCORM package |
+      | Description | Description |
+      | Completion tracking | Show activity as complete when conditions are met |
+      | Require all scos to return completion status | 0 |
+    And I set the field "Completed" to "1"
+    And I upload "mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12.zip" file to "Package file" filemanager
+    And I click on "Save and display" "button"
+    Then I should see "Basic Multi-sco SCORM package"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Basic Multi-sco SCORM package"
+    And I should see "Normal"
+    And I press "Enter"
+    And I switch to "scorm_object" iframe
+    And I should see "Play of the game"
+    And I switch to the main frame
+    And I follow "Exit activity"
+    Then I should see "Basic Multi-sco SCORM package"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    Then "Student 1" user has completed "Basic Multi-sco SCORM package" activity
+
+  @javascript
+  Scenario: Test completion with all scos
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I click on "Edit settings" "link" in the "Administration" "block"
+    And I set the following fields to these values:
+      | Enable completion tracking | Yes |
+    And I press "Save and display"
+    And I add a "SCORM package" to section "1"
+    And I set the following fields to these values:
+      | Name | ADV Multi-sco SCORM package |
+      | Description | Description |
+      | Completion tracking | Show activity as complete when conditions are met |
+      | Require all scos to return completion status | 1 |
+    And I set the field "Completed" to "1"
+    And I upload "mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12.zip" file to "Package file" filemanager
+    And I click on "Save and display" "button"
+    Then I should see "ADV Multi-sco SCORM package"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "ADV Multi-sco SCORM package"
+    And I should see "Normal"
+    And I press "Enter"
+    And I switch to "scorm_object" iframe
+    And I should see "Play of the game"
+    And I switch to the main frame
+    And I follow "Exit activity"
+    Then I should see "ADV Multi-sco SCORM package"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    Then "Student 1" user has not completed "ADV Multi-sco SCORM package" activity
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "ADV Multi-sco SCORM package"
+    And I should see "Normal"
+    And I press "Enter"
+    And I switch to "scorm_object" iframe
+    And I should see "Play of the game"
+
+    And I switch to the main frame
+    And I click on "Par?" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Par"
+
+    And I switch to the main frame
+    And I click on "Keeping Score" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Scoring"
+
+    And I switch to the main frame
+    And I click on "Other Scoring Systems" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Other Scoring Systems"
+
+    And I switch to the main frame
+    And I click on "The Rules of Golf" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "The Rules of Golf"
+
+    And I switch to the main frame
+    And I click on "Playing Golf Quiz" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Knowledge Check"
+
+    And I switch to the main frame
+    And I click on "Taking Care of the Course" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Etiquette - Care For the Course"
+
+    And I switch to the main frame
+    And I click on "Avoiding Distraction" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Etiquette - Avoiding Distraction"
+
+    And I switch to the main frame
+    And I click on "Playing Politely" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Etiquette - Playing the Game"
+
+    And I switch to the main frame
+    And I click on "Etiquette Quiz" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Knowledge Check"
+
+    And I switch to the main frame
+    And I click on "Handicapping Overview" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Handicapping"
+
+    And I switch to the main frame
+    And I click on "Calculating a Handicap" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Calculating a Handicap"
+
+    And I switch to the main frame
+    And I click on "Calculating a Handicapped Score" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Calculating a Score"
+
+    And I switch to the main frame
+    And I click on "Handicapping Example" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Calculating a Score"
+
+    And I switch to the main frame
+    And I click on "Handicapping Quiz" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Knowledge Check"
+
+    And I switch to the main frame
+    And I click on "How to Have Fun Playing Golf" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "How to Have Fun Golfing"
+
+    And I switch to the main frame
+    And I click on "How to Make Friends Playing Golf" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "How to Make Friends on the Golf Course"
+
+    And I switch to the main frame
+    And I click on "Having Fun Quiz" "list_item"
+    And I switch to "scorm_object" iframe
+    And I should see "Knowledge Check"
+    And I switch to the main frame
+    And I follow "Exit activity"
+    Then I should see "ADV Multi-sco SCORM package"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And "Student 1" user has completed "ADV Multi-sco SCORM package" activity
\ No newline at end of file
index f081ae9..645241e 100644 (file)
@@ -770,8 +770,8 @@ class mod_scorm_external_testcase extends externallib_advanced_testcase {
         self::setUser($teacher);
 
         $additionalfields = array('updatefreq', 'timemodified', 'options',
-                                    'completionstatusrequired', 'completionscorerequired', 'autocommit',
-                                    'section', 'visible', 'groupmode', 'groupingid');
+                                    'completionstatusrequired', 'completionscorerequired', 'completionstatusallscos',
+                                    'autocommit', 'section', 'visible', 'groupmode', 'groupingid');
 
         foreach ($additionalfields as $field) {
             $fieldtype = $returndescription->keys['scorms']->content->keys[$field]->type;
diff --git a/mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12.zip b/mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12.zip
new file mode 100644 (file)
index 0000000..2f03d5b
Binary files /dev/null and b/mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12.zip differ
index a2f338d..cf624da 100644 (file)
@@ -1,33 +1,20 @@
-Description of singlesco_scorm12.zip package
+Description of test packages
 ============================================
 
-singlesco_scorm12.zip is a simple Scorm 1.2 package used to run tests related with
-Moodle's scorm features; this package was downloaded from http://scorm.com/ website,
-the website disclaimer states that *Content on this site is licensed under a Creative
-Commons Attribution 3.0 License*.
+Sample Packages downloaded from http://scorm.com/scorm-explained/technical-scorm/golf-examples/
 
+* singlescobasic.zip - Single SCO with basic runtime calls. SCORM 1.2.
+* singlesco_scorm12.zip - Single SCO content packaging example. SCORM 1.2.
+* RuntimeMinimumCalls_SCORM12.zip - Multi-SCO packaging example. SCORM 1.2.
 
-Original source
-===============
+These packages were downloaded from http://scorm.com/ website,  the website 
+disclaimer states that *Content on this site is licensed under a Creative
+Commons Attribution 3.0 License*. http://creativecommons.org/licenses/by/3.0/
 
-http://scorm.com
 
-http://scorm.com/scorm-explained/technical-scorm/golf-examples/
-
-
-Changes
-=======
-
-No changes were made to the original source contents.
-
-
-License
-=======
-
-http://creativecommons.org/licenses/by/3.0/
-
-
-Moodle commit history
-=====================
-
-MDL-43743
+Other test packages
+* badscorm.zip - contains a fake imsmanifest.xml inside a directory, used for validation check.
+* invalid.zip - zip file with an single html file, no SCORM config files, used for validation check.
+* validscorm.zip - non functional package with an imsmanifest.xml, used for validation check.
+* validaicc.zip - non functional package with AICC config files, used for validation check.
+* complexscorm.zip - copied from: https://github.com/jleyva/scorm-debugger
index ac22997..8576a41 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2016052300;    // The current module version (Date: YYYYMMDDXX).
+$plugin->version   = 2016080900;    // The current module version (Date: YYYYMMDDXX).
 $plugin->requires  = 2016051900;    // Requires this Moodle version.
 $plugin->component = 'mod_scorm';   // Full name of the plugin (used for diagnostics).
 $plugin->cron      = 300;