Merge branch 'MDL-54864-master' of https://github.com/snake/moodle
authorDan Poltawski <dan@moodle.com>
Mon, 22 Aug 2016 10:19:32 +0000 (11:19 +0100)
committerDan Poltawski <dan@moodle.com>
Mon, 22 Aug 2016 10:19:32 +0000 (11:19 +0100)
23 files changed:
admin/settings/server.php
lib/accesslib.php
lib/classes/task/stats_cron_task.php
lib/db/tasks.php
lib/db/upgrade.php
lib/statslib.php
lib/tests/accesslib_test.php
lib/tests/statslib_test.php
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
version.php

index 548031b..8ef94a7 100644 (file)
@@ -79,7 +79,6 @@ $temp->add(new admin_setting_configselect('statsmaxruntime', new lang_string('st
                                                                                                                                                             60*60*7 => '7 '.new lang_string('hours'),
                                                                                                                                                             60*60*8 => '8 '.new lang_string('hours') )));
 $temp->add(new admin_setting_configtext('statsruntimedays', new lang_string('statsruntimedays', 'admin'), new lang_string('configstatsruntimedays', 'admin'), 31, PARAM_INT));
-$temp->add(new admin_setting_configtime('statsruntimestarthour', 'statsruntimestartminute', new lang_string('statsruntimestart', 'admin'), new lang_string('configstatsruntimestart', 'admin'), array('h' => 0, 'm' => 0)));
 $temp->add(new admin_setting_configtext('statsuserthreshold', new lang_string('statsuserthreshold', 'admin'), new lang_string('configstatsuserthreshold', 'admin'), 0, PARAM_INT));
 $ADMIN->add('server', $temp);
 
index a2c4eb8..19033df 100644 (file)
@@ -4208,15 +4208,32 @@ function get_role_users($roleid, context $context, $parent = false, $fields = ''
     // Adding the fields from $sort that are not present in $fields.
     $sortarray = preg_split('/,\s*/', $sort);
     $fieldsarray = preg_split('/,\s*/', $fields);
+
+    // Discarding aliases from the fields.
+    $fieldnames = array();
+    foreach ($fieldsarray as $key => $field) {
+        list($fieldnames[$key]) = explode(' ', $field);
+    }
+
     $addedfields = array();
     foreach ($sortarray as $sortfield) {
         // Throw away any additional arguments to the sort (e.g. ASC/DESC).
-        list ($sortfield) = explode(' ', $sortfield);
-        if (!in_array($sortfield, $fieldsarray)) {
+        list($sortfield) = explode(' ', $sortfield);
+        list($tableprefix) = explode('.', $sortfield);
+        $fieldpresent = false;
+        foreach ($fieldnames as $fieldname) {
+            if ($fieldname === $sortfield || $fieldname === $tableprefix.'.*') {
+                $fieldpresent = true;
+                break;
+            }
+        }
+
+        if (!$fieldpresent) {
             $fieldsarray[] = $sortfield;
             $addedfields[] = $sortfield;
         }
     }
+
     $fields = implode(', ', $fieldsarray);
     if (!empty($addedfields)) {
         $addedfields = implode(', ', $addedfields);
index 5ce0791..f9f5465 100644 (file)
@@ -44,28 +44,19 @@ class stats_cron_task extends scheduled_task {
     public function execute() {
         global $CFG;
 
-        $timenow = time();
         // Run stats as at the end because they are known to take very long time on large sites.
         if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) {
             require_once($CFG->dirroot.'/lib/statslib.php');
-            // Check we're not before our runtime.
-            $timetocheck = stats_get_base_daily() + $CFG->statsruntimestarthour * 60 * 60 + $CFG->statsruntimestartminute * 60;
-
-            if ($timenow > $timetocheck) {
-                // Process configured number of days as max (defaulting to 31).
-                $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays);
-                if (stats_cron_daily($maxdays)) {
-                    if (stats_cron_weekly()) {
-                        if (stats_cron_monthly()) {
-                            stats_clean_old();
-                        }
+            // Process configured number of days as max (defaulting to 31).
+            $maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays);
+            if (stats_cron_daily($maxdays)) {
+                if (stats_cron_weekly()) {
+                    if (stats_cron_monthly()) {
+                        stats_clean_old();
                     }
                 }
-                \core_php_time_limit::raise();
-            } else {
-                mtrace('Next stats run after:'. userdate($timetocheck));
             }
+            \core_php_time_limit::raise();
         }
     }
-
 }
index 49863b8..f7296c5 100644 (file)
@@ -306,7 +306,7 @@ $tasks = array(
         'classname' => 'core\task\stats_cron_task',
         'blocking' => 0,
         'minute' => '0',
-        'hour' => '*',
+        'hour' => '0',
         'day' => '*',
         'dayofweek' => '*',
         'month' => '*'
index f9c4194..c413aa6 100644 (file)
@@ -2084,6 +2084,47 @@ function xmldb_main_upgrade($oldversion) {
     }
 
     if ($oldversion < 2016081700.02) {
+        // Default schedule values.
+        $hour = 0;
+        $minute = 0;
+
+        // Get the old settings.
+        if (isset($CFG->statsruntimestarthour)) {
+            $hour = $CFG->statsruntimestarthour;
+        }
+        if (isset($CFG->statsruntimestartminute)) {
+            $minute = $CFG->statsruntimestartminute;
+        }
+
+        // Retrieve the scheduled task record first.
+        $stattask = $DB->get_record('task_scheduled', array('component' => 'moodle', 'classname' => '\core\task\stats_cron_task'));
+
+        // Don't touch customised scheduling.
+        if ($stattask && !$stattask->customised) {
+
+            $nextruntime = mktime($hour, $minute, 0, date('m'), date('d'), date('Y'));
+            if ($nextruntime < $stattask->lastruntime) {
+                // Add 24 hours to the next run time.
+                $newtime = new DateTime();
+                $newtime->setTimestamp($nextruntime);
+                $newtime->add(new DateInterval('P1D'));
+                $nextruntime = $newtime->getTimestamp();
+            }
+            $stattask->nextruntime = $nextruntime;
+            $stattask->minute = $minute;
+            $stattask->hour = $hour;
+            $stattask->customised = 1;
+            $DB->update_record('task_scheduled', $stattask);
+        }
+        // These settings are no longer used.
+        unset_config('statsruntimestarthour');
+        unset_config('statsruntimestartminute');
+        unset_config('statslastexecution');
+
+        upgrade_main_savepoint(true, 2016081700.02);
+    }
+
+    if ($oldversion < 2016082200.00) {
         // An upgrade step to remove any duplicate stamps, within the same context, in the question_categories table, and to
         // add a unique index to (contextid, stamp) to avoid future stamp duplication. See MDL-54864.
 
@@ -2133,7 +2174,7 @@ function xmldb_main_upgrade($oldversion) {
         }
 
         // Savepoint reached.
-        upgrade_main_savepoint(true, 2016081700.02);
+        upgrade_main_savepoint(true, 2016082200.00);
     }
 
     return true;
index 23fa555..a414fae 100644 (file)
@@ -138,22 +138,6 @@ function stats_cron_daily($maxdays=1) {
         set_config('statslastdaily', $timestart);
     }
 
-    // calculate scheduled time
-    $scheduledtime = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
-
-    // Note: This will work fine for sites running cron each 4 hours or less (hopefully, 99.99% of sites). MDL-16709
-    // check to make sure we're due to run, at least 20 hours after last run
-    if (isset($CFG->statslastexecution) && ((time() - 20*60*60) < $CFG->statslastexecution)) {
-        mtrace("...preventing stats to run, last execution was less than 20 hours ago.");
-        return false;
-    // also check that we are a max of 4 hours after scheduled time, stats won't run after that
-    } else if (time() > $scheduledtime + 4*60*60) {
-        mtrace("...preventing stats to run, more than 4 hours since scheduled time.");
-        return false;
-    } else {
-        set_config('statslastexecution', time()); /// Grab this execution as last one
-    }
-
     $nextmidnight = stats_get_next_day_start($timestart);
 
     // are there any days that need to be processed?
@@ -161,7 +145,6 @@ function stats_cron_daily($maxdays=1) {
         return true; // everything ok and up-to-date
     }
 
-
     $timeout = empty($CFG->statsmaxruntime) ? 60*60*24 : $CFG->statsmaxruntime;
 
     if (!set_cron_lock('statsrunning', $now + $timeout)) {
@@ -1041,13 +1024,10 @@ function stats_get_base_monthly($time=0) {
  */
 function stats_get_next_day_start($time) {
     $next = stats_get_base_daily($time);
-    $next = $next + 60*60*26;
-    $next = stats_get_base_daily($next);
-    if ($next <= $time) {
-        //DST trouble - prevent infinite loops
-        $next = $next + 60*60*24;
-    }
-    return $next;
+    $nextdate = new DateTime();
+    $nextdate->setTimestamp($next);
+    $nextdate->add(new DateInterval('P1D'));
+    return $nextdate->getTimestamp();
 }
 
 /**
@@ -1057,13 +1037,10 @@ function stats_get_next_day_start($time) {
  */
 function stats_get_next_week_start($time) {
     $next = stats_get_base_weekly($time);
-    $next = $next + 60*60*24*9;
-    $next = stats_get_base_weekly($next);
-    if ($next <= $time) {
-        //DST trouble - prevent infinite loops
-        $next = $next + 60*60*24*7;
-    }
-    return $next;
+    $nextdate = new DateTime();
+    $nextdate->setTimestamp($next);
+    $nextdate->add(new DateInterval('P1W'));
+    return $nextdate->getTimestamp();
 }
 
 /**
@@ -1073,13 +1050,10 @@ function stats_get_next_week_start($time) {
  */
 function stats_get_next_month_start($time) {
     $next = stats_get_base_monthly($time);
-    $next = $next + 60*60*24*33;
-    $next = stats_get_base_monthly($next);
-    if ($next <= $time) {
-        //DST trouble - prevent infinite loops
-        $next = $next + 60*60*24*31;
-    }
-    return $next;
+    $nextdate = new DateTime();
+    $nextdate->setTimestamp($next);
+    $nextdate->add(new DateInterval('P1M'));
+    return $nextdate->getTimestamp();
 }
 
 /**
index e3a2f99..04686f6 100644 (file)
@@ -1453,6 +1453,22 @@ class core_accesslib_testcase extends advanced_testcase {
         $this->assertObjectHasAttribute('lastname', $users[$user3->id]);
         $this->assertObjectHasAttribute('firstname', $users[$user3->id]);
 
+        $users = get_role_users($teacherrole->id, $coursecontext, false, 'u.*');
+        $this->assertDebuggingNotCalled();
+        $this->assertCount(2, $users);
+
+        $users = get_role_users($teacherrole->id, $coursecontext, false, 'u.id AS id_alias');
+        $this->assertDebuggingCalled('get_role_users() adding u.lastname, u.firstname to the query result because they were required by $sort but missing in $fields');
+        $this->assertCount(2, $users);
+        $this->assertArrayHasKey($user1->id, $users);
+        $this->assertObjectHasAttribute('id_alias', $users[$user1->id]);
+        $this->assertObjectHasAttribute('lastname', $users[$user1->id]);
+        $this->assertObjectHasAttribute('firstname', $users[$user1->id]);
+        $this->assertArrayHasKey($user3->id, $users);
+        $this->assertObjectHasAttribute('id_alias', $users[$user3->id]);
+        $this->assertObjectHasAttribute('lastname', $users[$user3->id]);
+        $this->assertObjectHasAttribute('firstname', $users[$user3->id]);
+
         $users = get_role_users($teacherrole->id, $coursecontext, false, 'u.id, u.email, u.idnumber', 'u.idnumber', null, $group->id);
         $this->assertCount(1, $users);
         $this->assertArrayHasKey($user3->id, $users);
index aa7a251..66089a4 100644 (file)
@@ -61,7 +61,6 @@ class core_statslib_testcase extends advanced_testcase {
         core_date::set_default_server_timezone();
         $CFG->statsfirstrun           = 'all';
         $CFG->statslastdaily          = 0;
-        $CFG->statslastexecution      = 0;
 
         // Figure out the broken day start so I can figure out when to the start time should be.
         $time   = time();
@@ -74,9 +73,6 @@ class core_statslib_testcase extends advanced_testcase {
 
         $shour  = intval(($time - $stime) / (60*60));
 
-        $CFG->statsruntimestarthour   = $shour;
-        $CFG->statsruntimestartminute = 0;
-
         if ($DB->record_exists('user', array('username' => 'user1'))) {
             return;
         }
@@ -392,6 +388,22 @@ class core_statslib_testcase extends advanced_testcase {
     public function test_statslib_get_next_day_start() {
         $this->setTimezone(0);
         $this->assertEquals(1272758400, stats_get_next_day_start(1272686410));
+
+        // Try setting timezone to some place in the US.
+        $this->setTimezone('America/New_York', 'America/New_York');
+        // Then set the time for midnight before daylight savings.
+        // 1425790800 is midnight in New York (2015-03-08) Daylight saving will occur in 2 hours time.
+        // 1425873600 is midnight the next day.
+        $this->assertEquals(1425873600, stats_get_next_day_start(1425790800));
+        $this->assertEquals(23, ((1425873600 - 1425790800) / 60 ) / 60);
+        // Then set the time for midnight before daylight savings ends.
+        // 1446350400 is midnight in New York (2015-11-01) Daylight saving will finish in 2 hours time.
+        // 1446440400 is midnight the next day.
+        $this->assertEquals(1446440400, stats_get_next_day_start(1446350400));
+        $this->assertEquals(25, ((1446440400 - 1446350400) / 60 ) / 60);
+        // The next day should be normal.
+        $this->assertEquals(1446526800, stats_get_next_day_start(1446440400));
+        $this->assertEquals(24, ((1446526800 - 1446440400) / 60 ) / 60);
     }
 
     /**
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..0d0c003 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;
index f67a899..0c0b0b2 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2016081700.02;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2016082200.00;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.