Merge branch 'MDL-50357_master' of git://github.com/markn86/moodle
authorDan Poltawski <dan@moodle.com>
Wed, 30 Dec 2015 09:19:17 +0000 (09:19 +0000)
committerDan Poltawski <dan@moodle.com>
Wed, 30 Dec 2015 09:19:17 +0000 (09:19 +0000)
23 files changed:
Gruntfile.js
course/format/social/tests/behat/social_adjust_discussion_count.feature
course/modedit.php
enrol/guest/classes/enrol_guest_edit_form.php
enrol/guest/lib.php
lib/db/services.php
lib/filestorage/tests/file_storage_test.php
message/tests/behat/display_history.feature
mod/assign/locallib.php
mod/forum/tests/behat/discussion_navigation.feature
mod/forum/tests/generator/lib.php
mod/forum/tests/lib_test.php
mod/lesson/tests/behat/completion_condition_time_spent.feature
mod/lesson/tests/behat/date_availability.feature
mod/lesson/tests/behat/lesson_student_resume.feature
mod/scorm/classes/external.php
mod/scorm/db/services.php
mod/scorm/loadSCO.php
mod/scorm/locallib.php
mod/scorm/tests/externallib_test.php
mod/scorm/version.php
repository/tests/repositorylib_test.php
version.php

index dcc22a8..665bb44 100644 (file)
@@ -114,6 +114,10 @@ module.exports = function(grunt) {
                 args.push('--lint-stderr');
             }
 
+            if (grunt.option('no-color')) {
+                args.push('--color=false');
+            }
+
             var execShifter = function() {
 
                 shifter = exec("node", args, {
index 2a589ec..8748786 100644 (file)
@@ -23,6 +23,7 @@ Background:
     And I press "Post to forum"
     And I wait to be redirected
     And I follow "Course 1"
+    And I wait "1" seconds
     And I press "Add a new discussion topic"
     And I set the following fields to these values:
       | Subject | Forum Post 9 |
@@ -30,6 +31,7 @@ Background:
     And I press "Post to forum"
     And I wait to be redirected
     And I follow "Course 1"
+    And I wait "1" seconds
     And I press "Add a new discussion topic"
     And I set the following fields to these values:
       | Subject | Forum Post 8 |
@@ -78,6 +80,7 @@ Background:
       | Message | This is forum post two |
     And I press "Post to forum"
     And I wait to be redirected
+    And I wait "1" seconds
     And I follow "Course 1"
     And I press "Add a new discussion topic"
     And I set the following fields to these values:
index 4b9edaf..65880f9 100644 (file)
@@ -187,15 +187,14 @@ if (!empty($add)) {
 
     if ($items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$data->modulename,
                                              'iteminstance'=>$data->instance, 'courseid'=>$course->id))) {
-        // add existing outcomes
+        // Add existing outcomes.
         foreach ($items as $item) {
-            if (!empty($item->gradepass)) {
+            if (!empty($item->outcomeid)) {
+                $data->{'outcome_' . $item->outcomeid} = 1;
+            } else if (!empty($item->gradepass)) {
                 $decimalpoints = $item->get_decimals();
                 $data->gradepass = format_float($item->gradepass, $decimalpoints);
             }
-            if (!empty($item->outcomeid)) {
-                $data->{'outcome_'.$item->outcomeid} = 1;
-            }
         }
 
         // set category if present
index bfcb20d..4eb6ef1 100644 (file)
@@ -58,7 +58,11 @@ class enrol_guest_edit_form extends moodleform {
         $mform->addElement('passwordunmask', 'password', get_string('password', 'enrol_guest'));
         $mform->addHelpButton('password', 'password', 'enrol_guest');
 
-        if ($plugin->get_config('requirepassword')) {
+        // If we have a new instance and the password is required - make sure it is set. For existing
+        // instances we do not force the password to be required as it may have been set to empty before
+        // the password was required. We check in the validation function whether this check is required
+        // for existing instances.
+        if (empty($instance->id) && $plugin->get_config('requirepassword')) {
             $mform->addRule('password', get_string('required'), 'required', null);
         }
 
@@ -84,20 +88,25 @@ class enrol_guest_edit_form extends moodleform {
         $checkpassword = false;
 
         if ($data['id']) {
-            if ($data['status'] == ENROL_INSTANCE_ENABLED) {
-                if ($instance->password !== $data['password']) {
-                    $checkpassword = true;
-                }
+            // Check the password if we are enabling the plugin again.
+            if (($instance->status == ENROL_INSTANCE_DISABLED) && ($data['status'] == ENROL_INSTANCE_ENABLED)) {
+                $checkpassword = true;
             }
-        } else {
-            if ($data['status'] == ENROL_INSTANCE_ENABLED) {
+
+            // Check the password if the instance is enabled and the password has changed.
+            if (($data['status'] == ENROL_INSTANCE_ENABLED) && ($instance->password !== $data['password'])) {
                 $checkpassword = true;
             }
+        } else {
+            $checkpassword = true;
         }
 
         if ($checkpassword) {
+            $require = $plugin->get_config('requirepassword');
             $policy  = $plugin->get_config('usepasswordpolicy');
-            if ($policy) {
+            if ($require && trim($data['password']) === '') {
+                $errors['password'] = get_string('required');
+            } else if (!empty($data['password']) && $policy) {
                 $errmsg = '';
                 if (!check_password_policy($data['password'], $errmsg)) {
                     $errors['password'] = $errmsg;
index 51e2edb..0c69877 100644 (file)
@@ -371,7 +371,8 @@ class enrol_guest_plugin extends enrol_plugin {
                 }
             }
 
-            if ($this->get_config('usepasswordpolicy')) {
+            // Only check the password if it is set.
+            if (!empty($instance->password) && $this->get_config('usepasswordpolicy')) {
                 if (!check_password_policy($instance->password, $errmsg)) {
                     return false;
                 }
index 21af3df..3642c07 100644 (file)
@@ -1246,6 +1246,7 @@ $services = array(
             'mod_scorm_get_scorm_sco_tracks',
             'mod_scorm_get_scorm_attempt_count',
             'mod_scorm_get_scorms_by_courses',
+            'mod_scorm_launch_sco',
             'mod_survey_get_surveys_by_courses',
             'mod_survey_view_survey',
             'mod_survey_get_questions',
index 95d6065..f453f34 100644 (file)
@@ -327,8 +327,8 @@ class core_files_file_storage_testcase extends advanced_testcase {
         $repositorypluginname = 'user';
         // Override repository permission.
         $capability = 'repository/' . $repositorypluginname . ':view';
-        $allroles = $DB->get_records_menu('role', array(), 'id', 'archetype, id');
-        assign_capability($capability, CAP_ALLOW, $allroles['guest'], $syscontext->id, true);
+        $guestroleid = $DB->get_field('role', 'id', array('shortname' => 'guest'));
+        assign_capability($capability, CAP_ALLOW, $guestroleid, $syscontext->id, true);
 
         $args = array();
         $args['type'] = $repositorypluginname;
index 9083023..62b0766 100644 (file)
@@ -11,6 +11,7 @@ Feature: Message history displays correctly
       | user2 | User | Two | two@example.com |
     And I log in as "user1"
     And I send "Message 1 from user1 to user2" message to "User Two" user
+    And I wait "1" seconds
     And I send "Message 2 from user1 to user2" message to "User Two" user
     And I send "Message 3 from user1 to user2" message to "User Two" user
     And I send "Message 4 from user1 to user2" message to "User Two" user
@@ -19,6 +20,7 @@ Feature: Message history displays correctly
     And I send "Message 7 from user1 to user2" message to "User Two" user
     And I send "Message 8 from user1 to user2" message to "User Two" user
     And I send "Message 9 from user1 to user2" message to "User Two" user
+    And I wait "1" seconds
     And I send "Message 10 from user1 to user2" message to "User Two" user
 
   Scenario: View sent messages
index 392098a..aa70d7f 100644 (file)
@@ -2825,8 +2825,7 @@ class assign {
      * recorded separately.
      *
      * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
-     * @param bool $create optional - defaults to false. If set to true a new submission object
-     *                     will be created in the database with the status set to "new".
+     * @param bool $create If set to true a new submission object will be created in the database with the status set to "new".
      * @param int $attemptnumber - -1 means the latest attempt
      * @return stdClass The submission
      */
index b244772..49c83e3 100644 (file)
@@ -56,6 +56,7 @@ Feature: A user can navigate to previous and next discussions
     And I follow "Discussion 1"
     And I should see "Discussion 2"
     And I should not see "Discussion 3"
+    And I wait "1" seconds
     And I follow "Reply"
     And I set the following fields to these values:
       | Message | Answer to discussion |
index 77198d7..3492860 100644 (file)
@@ -185,11 +185,25 @@ class mod_forum_generator extends testing_module_generator {
             $record['mailnow'] = "0";
         }
 
+        if (isset($record['timemodified'])) {
+            $timemodified = $record['timemodified'];
+        }
+
         $record = (object) $record;
 
         // Add the discussion.
         $record->id = forum_add_discussion($record, null, null, $record->userid);
 
+        if (isset($timemodified)) {
+            // Enforce the time modified.
+            $post = $DB->get_record('forum_posts', array('discussion' => $record->id));
+            $record->timemodified = $timemodified;
+            $post->modified = $post->created = $timemodified;
+
+            $DB->update_record('forum_discussions', $record);
+            $DB->update_record('forum_posts', $post);
+        }
+
         return $record;
     }
 
index 20b04e5..07876b3 100644 (file)
@@ -811,14 +811,15 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->course = $course->id;
         $record->userid = $user->id;
         $record->forum = $forum->id;
+        $record->timemodified = time();
         $disc1 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $disc2 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $disc3 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $disc4 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $disc5 = $forumgen->create_discussion($record);
 
         // Getting the neighbours.
@@ -844,8 +845,8 @@ class mod_forum_lib_testcase extends advanced_testcase {
 
         // Post in some discussions. We manually update the discussion record because
         // the data generator plays with timemodified in a way that would break this test.
-        sleep(1);
-        $disc1->timemodified = time();
+        $record->timemodified++;
+        $disc1->timemodified = $record->timemodified;
         $DB->update_record('forum_discussions', $disc1);
 
         $neighbours = forum_get_discussion_neighbours($cm, $disc5, $forum);
@@ -861,13 +862,13 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $this->assertEmpty($neighbours['next']);
 
         // After some discussions were created.
-        sleep(1);
+        $record->timemodified++;
         $disc6 = $forumgen->create_discussion($record);
         $neighbours = forum_get_discussion_neighbours($cm, $disc6, $forum);
         $this->assertEquals($disc1->id, $neighbours['prev']->id);
         $this->assertEmpty($neighbours['next']);
 
-        sleep(1);
+        $record->timemodified++;
         $disc7 = $forumgen->create_discussion($record);
         $neighbours = forum_get_discussion_neighbours($cm, $disc7, $forum);
         $this->assertEquals($disc6->id, $neighbours['prev']->id);
@@ -875,7 +876,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
 
         // Adding timed discussions.
         $CFG->forum_enabletimedposts = true;
-        $now = time();
+        $now = $record->timemodified;
         $past = $now - 60;
         $future = $now + 60;
 
@@ -885,25 +886,26 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->forum = $forum->id;
         $record->timestart = $past;
         $record->timeend = $future;
-        sleep(1);
+        $record->timemodified = $now;
+        $record->timemodified++;
         $disc8 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $record->timestart = $future;
         $record->timeend = 0;
         $disc9 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $record->timestart = 0;
         $record->timeend = 0;
         $disc10 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $record->timestart = 0;
         $record->timeend = $past;
         $disc11 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $record->timestart = $past;
         $record->timeend = $future;
         $disc12 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $record->timestart = $future + 1; // Should be last post for those that can see it.
         $record->timeend = 0;
         $disc13 = $forumgen->create_discussion($record);
@@ -986,10 +988,9 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $this->setAdminUser();
 
         // Two discussions with identical timemodified ignore each other.
-        sleep(1);
-        $now = time();
-        $DB->update_record('forum_discussions', (object) array('id' => $disc3->id, 'timemodified' => $now));
-        $DB->update_record('forum_discussions', (object) array('id' => $disc2->id, 'timemodified' => $now));
+        $record->timemodified++;
+        $DB->update_record('forum_discussions', (object) array('id' => $disc3->id, 'timemodified' => $record->timemodified));
+        $DB->update_record('forum_discussions', (object) array('id' => $disc2->id, 'timemodified' => $record->timemodified));
         $disc2 = $DB->get_record('forum_discussions', array('id' => $disc2->id));
         $disc3 = $DB->get_record('forum_discussions', array('id' => $disc3->id));
 
@@ -1023,14 +1024,15 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->course = $course->id;
         $record->userid = $user->id;
         $record->forum = $forum->id;
+        $record->timemodified = time();
         $disc1 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $disc2 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $disc3 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $disc4 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $disc5 = $forumgen->create_discussion($record);
 
         // Getting the neighbours.
@@ -1055,8 +1057,8 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $this->assertEmpty($neighbours['next']);
 
         // Make sure that the thread's timemodified does not affect the order.
-        sleep(1);
-        $disc1->timemodified = time();
+        $record->timemodified++;
+        $disc1->timemodified = $record->timemodified;
         $DB->update_record('forum_discussions', $disc1);
 
         $neighbours = forum_get_discussion_neighbours($cm, $disc1, $forum);
@@ -1068,13 +1070,13 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $this->assertEquals($disc3->id, $neighbours['next']->id);
 
         // Add another blog post.
-        sleep(1);
+        $record->timemodified++;
         $disc6 = $forumgen->create_discussion($record);
         $neighbours = forum_get_discussion_neighbours($cm, $disc6, $forum);
         $this->assertEquals($disc5->id, $neighbours['prev']->id);
         $this->assertEmpty($neighbours['next']);
 
-        sleep(1);
+        $record->timemodified++;
         $disc7 = $forumgen->create_discussion($record);
         $neighbours = forum_get_discussion_neighbours($cm, $disc7, $forum);
         $this->assertEquals($disc6->id, $neighbours['prev']->id);
@@ -1082,7 +1084,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
 
         // Adding timed discussions.
         $CFG->forum_enabletimedposts = true;
-        $now = time();
+        $now = $record->timemodified;
         $past = $now - 60;
         $future = $now + 60;
 
@@ -1092,21 +1094,22 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->forum = $forum->id;
         $record->timestart = $past;
         $record->timeend = $future;
-        sleep(1);
+        $record->timemodified = $now;
+        $record->timemodified++;
         $disc8 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $record->timestart = $future;
         $record->timeend = 0;
         $disc9 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $record->timestart = 0;
         $record->timeend = 0;
         $disc10 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $record->timestart = 0;
         $record->timeend = $past;
         $disc11 = $forumgen->create_discussion($record);
-        sleep(1);
+        $record->timemodified++;
         $record->timestart = $past;
         $record->timeend = $future;
         $disc12 = $forumgen->create_discussion($record);
@@ -1174,10 +1177,9 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $this->setAdminUser();
 
         // Two blog posts with identical creation time ignore each other.
-        sleep(1);
-        $now = time();
-        $DB->update_record('forum_posts', (object) array('id' => $disc2->firstpost, 'created' => $now));
-        $DB->update_record('forum_posts', (object) array('id' => $disc3->firstpost, 'created' => $now));
+        $record->timemodified++;
+        $DB->update_record('forum_posts', (object) array('id' => $disc2->firstpost, 'created' => $record->timemodified));
+        $DB->update_record('forum_posts', (object) array('id' => $disc3->firstpost, 'created' => $record->timemodified));
 
         $neighbours = forum_get_discussion_neighbours($cm, $disc2, $forum);
         $this->assertEquals($disc12->id, $neighbours['prev']->id);
@@ -1218,11 +1220,13 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->userid = $user1->id;
         $record->forum = $forum1->id;
         $record->groupid = $group1->id;
+        $record->timemodified = time();
         $disc11 = $forumgen->create_discussion($record);
         $record->forum = $forum2->id;
+        $record->timemodified++;
         $disc21 = $forumgen->create_discussion($record);
 
-        sleep(1);
+        $record->timemodified++;
         $record->userid = $user2->id;
         $record->forum = $forum1->id;
         $record->groupid = $group2->id;
@@ -1230,7 +1234,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->forum = $forum2->id;
         $disc22 = $forumgen->create_discussion($record);
 
-        sleep(1);
+        $record->timemodified++;
         $record->userid = $user1->id;
         $record->forum = $forum1->id;
         $record->groupid = null;
@@ -1238,7 +1242,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->forum = $forum2->id;
         $disc23 = $forumgen->create_discussion($record);
 
-        sleep(1);
+        $record->timemodified++;
         $record->userid = $user2->id;
         $record->forum = $forum1->id;
         $record->groupid = $group2->id;
@@ -1246,7 +1250,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->forum = $forum2->id;
         $disc24 = $forumgen->create_discussion($record);
 
-        sleep(1);
+        $record->timemodified++;
         $record->userid = $user1->id;
         $record->forum = $forum1->id;
         $record->groupid = $group1->id;
@@ -1412,11 +1416,13 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->userid = $user1->id;
         $record->forum = $forum1->id;
         $record->groupid = $group1->id;
+        $record->timemodified = time();
         $disc11 = $forumgen->create_discussion($record);
         $record->forum = $forum2->id;
+        $record->timemodified++;
         $disc21 = $forumgen->create_discussion($record);
 
-        sleep(1);
+        $record->timemodified++;
         $record->userid = $user2->id;
         $record->forum = $forum1->id;
         $record->groupid = $group2->id;
@@ -1424,7 +1430,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->forum = $forum2->id;
         $disc22 = $forumgen->create_discussion($record);
 
-        sleep(1);
+        $record->timemodified++;
         $record->userid = $user1->id;
         $record->forum = $forum1->id;
         $record->groupid = null;
@@ -1432,7 +1438,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->forum = $forum2->id;
         $disc23 = $forumgen->create_discussion($record);
 
-        sleep(1);
+        $record->timemodified++;
         $record->userid = $user2->id;
         $record->forum = $forum1->id;
         $record->groupid = $group2->id;
@@ -1440,7 +1446,7 @@ class mod_forum_lib_testcase extends advanced_testcase {
         $record->forum = $forum2->id;
         $disc24 = $forumgen->create_discussion($record);
 
-        sleep(1);
+        $record->timemodified++;
         $record->userid = $user1->id;
         $record->forum = $forum1->id;
         $record->groupid = $group1->id;
index 920709a..b533878 100644 (file)
@@ -4,7 +4,6 @@ Feature: Set time spent as a completion condition for a lesson
   As a teacher
   I need to set time spent to mark the lesson activity as completed
 
-  @javascript
   Scenario: Set time spent as a condition
     Given the following "users" exist:
       | username | firstname | lastname | email |
@@ -29,8 +28,8 @@ Feature: Set time spent as a completion condition for a lesson
       | Description | Test lesson description |
       | Completion tracking | Show activity as complete when conditions are met |
       | completiontimespentenabled | 1 |
-      | completiontimespent[timeunit] | 60 |
-      | completiontimespent[number] | 1 |
+      | completiontimespent[timeunit] | 1 |
+      | completiontimespent[number] | 10 |
     And I follow "Test lesson"
     And I follow "Add a content page"
     And I set the following fields to these values:
@@ -54,15 +53,17 @@ Feature: Set time spent as a completion condition for a lesson
     Then the "Test lesson" "lesson" activity with "auto" completion should be marked as not complete
     And I follow "Test lesson"
     And I press "Next page"
+    # Add 1 sec delay so lesson knows a valid attempt has been made in past.
+    And I wait "1" seconds
     And I press "Next page"
     And I should see "You completed this lesson in"
-    And I should see ", which is less than the required time of 1 min. You might need to attempt the lesson again."
+    And I should see ", which is less than the required time of 10 secs. You might need to attempt the lesson again."
     And I follow "Course 1"
     And the "Test lesson" "lesson" activity with "auto" completion should be marked as not complete
     And I follow "Course 1"
     And I follow "Test lesson"
     And I press "Next page"
-    And I wait "61" seconds
+    And I wait "11" seconds
     And I press "Next page"
     And I should not see "You might need to attempt the lesson again."
     And I follow "Course 1"
index a730490..c94eda3 100644 (file)
@@ -20,11 +20,10 @@ Feature: A teacher can set available from and deadline dates to access a lesson
     And I follow "Course 1"
     And I turn editing mode on
 
-  @javascript
   Scenario: Forbidding lesson accesses until a specified date
     Given I add a "Lesson" to section "1"
     And I expand all fieldsets
-    And I click on "id_available_enabled" "checkbox"
+    And I set the field "id_available_enabled" to "1"
     And I set the following fields to these values:
       | Name | Test lesson |
       | Description | Test lesson description |
@@ -48,11 +47,9 @@ Feature: A teacher can set available from and deadline dates to access a lesson
     Then I should see "This lesson will be open on Wednesday, 1 January 2020, 8:00"
     And I should not see "First page contents"
 
-  @javascript
   Scenario: Forbidding lesson accesses until a specified date
     Given I add a "Lesson" to section "1"
-    And I expand all fieldsets
-    And I click on "id_deadline_enabled" "checkbox"
+    And I set the field "id_deadline_enabled" to "1"
     And I set the following fields to these values:
       | Name | Test lesson |
       | Description | Test lesson description |
index 34a0cf8..946c590 100644 (file)
@@ -25,7 +25,6 @@ Feature: In a lesson activity a student should
     And I press "Save and return to course"
     And I follow "Test lesson name"
 
-  @javascript
   Scenario: resume a lesson with both content then question pages
     Given I follow "Add a content page"
     And I set the following fields to these values:
@@ -34,7 +33,7 @@ Feature: In a lesson activity a student should
       | id_answer_editor_0 | Next page |
       | id_jumpto_0 | Next page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -47,7 +46,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -60,7 +59,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Add a content page"
+    And I select "Add a content page" from the "qtype" singleselect
     And I set the following fields to these values:
       | Page title | Third page name |
       | Page contents | Third page contents |
@@ -69,7 +68,7 @@ Feature: In a lesson activity a student should
       | id_answer_editor_1 | Next page |
       | id_jumpto_1 | Next page |
     And I press "Save page"
-    And I set the field "qtype" to "Add a content page"
+    And I select "Add a content page" from the "qtype" singleselect
     And I set the following fields to these values:
       | Page title | Second page name |
       | Page contents | Second page contents |
@@ -84,6 +83,8 @@ Feature: In a lesson activity a student should
     And I follow "Test lesson name"
     And I should see "First page contents"
     And I press "Next page"
+    # Add 1 sec delay so lesson knows a valid attempt has been made in past.
+    And I wait "1" seconds
     And I should see "Second page contents"
     And I press "Next page"
     And I should see "Third page contents"
@@ -92,6 +93,8 @@ Feature: In a lesson activity a student should
     And I should see "Do you want to start at the last page you saw?"
     And I follow "Yes"
     Then I should see "Third page contents"
+    # Add 1 sec delay so lesson knows differentiate 3rd and paper attempts.
+    And I wait "1" seconds
     And I press "Next page"
     And I should see "Paper is made from trees."
     And I follow "Test lesson name"
@@ -115,7 +118,6 @@ Feature: In a lesson activity a student should
     And I press "Continue"
     And I should see "Congratulations - end of lesson reached"
 
-  @javascript
   Scenario: resume a lesson with only content pages
     Given I follow "Add a content page"
     And I set the following fields to these values:
@@ -124,7 +126,7 @@ Feature: In a lesson activity a student should
       | id_answer_editor_0 | Next page |
       | id_jumpto_0 | Next page |
     And I press "Save page"
-    And I set the field "qtype" to "Add a content page"
+    And I select "Add a content page" from the "qtype" singleselect
     And I set the following fields to these values:
       | Page title | Fourth page name |
       | Page contents | Fourth page contents |
@@ -133,7 +135,7 @@ Feature: In a lesson activity a student should
       | id_answer_editor_1 | End of lesson |
       | id_jumpto_1 | End of lesson |
     And I press "Save page"
-    And I set the field "qtype" to "Add a content page"
+    And I select "Add a content page" from the "qtype" singleselect
     And I set the following fields to these values:
       | Page title | Third page name |
       | Page contents | Third page contents |
@@ -142,7 +144,7 @@ Feature: In a lesson activity a student should
       | id_answer_editor_1 | Next page |
       | id_jumpto_1 | Next page |
     And I press "Save page"
-    And I set the field "qtype" to "Add a content page"
+    And I select "Add a content page" from the "qtype" singleselect
     And I set the following fields to these values:
       | Page title | Second page name |
       | Page contents | Second page contents |
@@ -158,6 +160,8 @@ Feature: In a lesson activity a student should
     And I should see "First page contents"
     And I press "Next page"
     And I should see "Second page contents"
+    # Add 1 sec delay so lesson knows a valid attempt has been made in past.
+    And I wait "1" seconds
     And I press "Next page"
     And I should see "Third page contents"
     And I follow "Test lesson name"
@@ -167,6 +171,8 @@ Feature: In a lesson activity a student should
     And I should see "Third page contents"
     And I press "Next page"
     And I should see "Fourth page contents"
+    # Add 1 sec delay so lesson knows a valid attempt has been made in past.
+    And I wait "1" seconds
     And I press "End of lesson"
     And I log out
     And I log in as "student1"
@@ -175,7 +181,6 @@ Feature: In a lesson activity a student should
     And I should see "First page contents"
     And I log out
 
-  @javascript
   Scenario: resume a lesson with both question then content pages
     Given I follow "Add a question page"
     And I set the field "Select a question type" to "True/false"
@@ -190,7 +195,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -203,14 +208,14 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Add a content page"
+    And I select "Add a content page" from the "qtype" singleselect
     And I set the following fields to these values:
       | Page title | Content page 2 |
       | Page contents | Second content page |
       | id_answer_editor_0 | Next page |
       | id_jumpto_0 | Next page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -223,7 +228,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -236,7 +241,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -249,7 +254,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Add a content page"
+    And I select "Add a content page" from the "qtype" singleselect
     And I set the following fields to these values:
       | Page title | Content page 1 |
       | Page contents | First content page |
@@ -275,6 +280,8 @@ Feature: In a lesson activity a student should
     And I should see "1+1=2"
     And I set the following fields to these values:
       | True | 1 |
+    # Add 1 sec delay so lesson knows a valid attempt has been made in past.
+    And I wait "1" seconds
     And I press "Submit"
     And I press "Continue"
     And I should see "2+2=4"
@@ -285,6 +292,8 @@ Feature: In a lesson activity a student should
     And I should see "2+2=4"
     And I set the following fields to these values:
       | True | 1 |
+    # Add 1 sec delay so lesson knows a valid attempt has been made in past.
+    And I wait "1" seconds
     And I press "Submit"
     And I press "Continue"
     And I should see "Second content page"
@@ -301,7 +310,6 @@ Feature: In a lesson activity a student should
     And I press "Continue"
     And I should see "Congratulations - end of lesson reached"
 
-  @javascript
   Scenario: resume a lesson with only question pages
     Given I follow "Add a question page"
     And I set the field "Select a question type" to "True/false"
@@ -316,7 +324,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -329,7 +337,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -342,7 +350,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -355,7 +363,7 @@ Feature: In a lesson activity a student should
       | id_response_editor_1 | Wrong |
       | id_jumpto_1 | This page |
     And I press "Save page"
-    And I set the field "qtype" to "Question"
+    And I select "Question" from the "qtype" singleselect
     And I set the field "Select a question type" to "True/false"
     And I press "Add a question page"
     And I set the following fields to these values:
@@ -385,6 +393,8 @@ Feature: In a lesson activity a student should
     And I should see "1+1=2"
     And I set the following fields to these values:
       | True | 1 |
+    # Add 1 sec delay so lesson knows a valid attempt has been made in past.
+    And I wait "1" seconds
     And I press "Submit"
     And I press "Continue"
     And I should see "2+2=4"
@@ -395,6 +405,8 @@ Feature: In a lesson activity a student should
     And I should see "2+2=4"
     And I set the following fields to these values:
       | True | 1 |
+    # Add 1 sec delay so lesson knows a valid attempt has been made in past.
+    And I wait "1" seconds
     And I press "Submit"
     And I press "Continue"
     And I should see "Kermit is a frog"
index 990c15f..ab8e166 100644 (file)
@@ -829,4 +829,77 @@ class mod_scorm_external extends external_api {
             )
         );
     }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.1
+     */
+    public static function launch_sco_parameters() {
+        return new external_function_parameters(
+            array(
+                'scormid' => new external_value(PARAM_INT, 'SCORM instance id'),
+                'scoid' => new external_value(PARAM_INT, 'SCO id (empty for launching the first SCO)', VALUE_DEFAULT, 0)
+            )
+        );
+    }
+
+    /**
+     * Trigger the course module viewed event.
+     *
+     * @param int $scormid the SCORM instance id
+     * @param int $scoid the SCO id
+     * @return array of warnings and status result
+     * @since Moodle 3.1
+     * @throws moodle_exception
+     */
+    public static function launch_sco($scormid, $scoid = 0) {
+        global $DB;
+
+        $params = self::validate_parameters(self::launch_sco_parameters(),
+                                            array(
+                                                'scormid' => $scormid,
+                                                'scoid' => $scoid
+                                            ));
+        $warnings = array();
+
+        // Request and permission validation.
+        $scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
+        list($course, $cm) = get_course_and_cm_from_instance($scorm, 'scorm');
+
+        $context = context_module::instance($cm->id);
+        self::validate_context($context);
+
+        // If the SCORM is not open this function will throw exceptions.
+        scorm_require_available($scorm);
+
+        if (!empty($params['scoid']) and !($sco = scorm_get_sco($params['scoid'], SCO_ONLY))) {
+            throw new moodle_exception('cannotfindsco', 'scorm');
+        }
+
+        list($sco, $scolaunchurl) = scorm_get_sco_and_launch_url($scorm, $params['scoid'], $context);
+        // Trigger the SCO launched event.
+        scorm_launch_sco($scorm, $sco, $cm, $context, $scolaunchurl);
+
+        $result = array();
+        $result['status'] = true;
+        $result['warnings'] = $warnings;
+        return $result;
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 3.1
+     */
+    public static function launch_sco_returns() {
+        return new external_single_structure(
+            array(
+                'status' => new external_value(PARAM_BOOL, 'status: true if success'),
+                'warnings' => new external_warnings()
+            )
+        );
+    }
 }
index 4042a0c..ac85f70 100644 (file)
@@ -84,5 +84,13 @@ $functions = array(
                             no courses are provided then all the scorm instances the user has access to will be returned.',
         'type'          => 'read',
         'capabilities'  => ''
-    )
+    ),
+
+    'mod_scorm_launch_sco' => array(
+        'classname'     => 'mod_scorm_external',
+        'methodname'    => 'launch_sco',
+        'description'   => 'Trigger the SCO launched event.',
+        'type'          => 'write',
+        'capabilities'  => ''
+    ),
 );
index 3641f47..07cc0b2 100644 (file)
@@ -65,104 +65,24 @@ scorm_require_available($scorm);
 
 $context = context_module::instance($cm->id);
 
-if (!empty($scoid)) {
-    // Direct SCO request.
-    if ($sco = scorm_get_sco($scoid)) {
-        if ($sco->launch == '') {
-            // Search for the next launchable sco.
-            if ($scoes = $DB->get_records_select(
-                    'scorm_scoes',
-                    'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true).' AND id > ?',
-                    array($scorm->id, $sco->id),
-                    'sortorder, id')) {
-                $sco = current($scoes);
-            }
-        }
-    }
-}
-
-// If no sco was found get the first of SCORM package.
-if (!isset($sco)) {
-    $scoes = $DB->get_records_select(
-        'scorm_scoes',
-        'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true),
-        array($scorm->id),
-        'sortorder, id'
-    );
-    $sco = current($scoes);
-}
+// Forge SCO URL.
+list($sco, $scolaunchurl) = scorm_get_sco_and_launch_url($scorm, $scoid, $context);
 
 if ($sco->scormtype == 'asset') {
     $attempt = scorm_get_last_attempt($scorm->id, $USER->id);
     $element = (scorm_version_check($scorm->version, SCORM_13)) ? 'cmi.completion_status' : 'cmi.core.lesson_status';
     $value = 'completed';
-    $result = scorm_insert_track($USER->id, $scorm->id, $sco->id, $attempt, $element, $value);
-}
-
-// Forge SCO URL.
-$connector = '';
-$version = substr($scorm->version, 0, 4);
-if ((isset($sco->parameters) && (!empty($sco->parameters))) || ($version == 'AICC')) {
-    if (stripos($sco->launch, '?') !== false) {
-        $connector = '&';
-    } else {
-        $connector = '?';
-    }
-    if ((isset($sco->parameters) && (!empty($sco->parameters))) && ($sco->parameters[0] == '?')) {
-        $sco->parameters = substr($sco->parameters, 1);
-    }
-}
-
-if ($version == 'AICC') {
-    require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php");
-    $aiccsid = scorm_aicc_get_hacp_session($scorm->id);
-    if (empty($aiccsid)) {
-        $aiccsid = sesskey();
-    }
-    $scoparams = '';
-    if (isset($sco->parameters) && (!empty($sco->parameters))) {
-        $scoparams = '&'. $sco->parameters;
-    }
-    $launcher = $sco->launch.$connector.'aicc_sid='.$aiccsid.'&aicc_url='.$CFG->wwwroot.'/mod/scorm/aicc.php'.$scoparams;
-} else {
-    if (isset($sco->parameters) && (!empty($sco->parameters))) {
-        $launcher = $sco->launch.$connector.$sco->parameters;
-    } else {
-        $launcher = $sco->launch;
-    }
-}
-
-if (scorm_external_link($sco->launch)) {
-    // TODO: does this happen?
-    $result = $launcher;
-} else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) {
-    // Remote learning activity.
-    $result = dirname($scorm->reference).'/'.$launcher;
-} else if ($scorm->scormtype === SCORM_TYPE_LOCAL && strtolower($scorm->reference) == 'imsmanifest.xml') {
-    // This SCORM content sits in a repository that allows relative links.
-    $result = "$CFG->wwwroot/pluginfile.php/$context->id/mod_scorm/imsmanifest/$scorm->revision/$launcher";
-} else if ($scorm->scormtype === SCORM_TYPE_LOCAL or $scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
-    // Note: do not convert this to use get_file_url() or moodle_url()
-    // SCORM does not work without slasharguments and moodle_url() encodes querystring vars.
-    $result = "$CFG->wwwroot/pluginfile.php/$context->id/mod_scorm/content/$scorm->revision/$launcher";
+    scorm_insert_track($USER->id, $scorm->id, $sco->id, $attempt, $element, $value);
 }
 
-// Trigger a Sco launched event.
-$event = \mod_scorm\event\sco_launched::create(array(
-    'objectid' => $sco->id,
-    'context' => $context,
-    'other' => array('instanceid' => $scorm->id, 'loadedcontent' => $result)
-));
-$event->add_record_snapshot('course_modules', $cm);
-$event->add_record_snapshot('scorm', $scorm);
-$event->add_record_snapshot('scorm_scoes', $sco);
-$event->trigger();
+// Trigger the SCO launched event.
+scorm_launch_sco($scorm, $sco, $cm, $context, $scolaunchurl);
 
 header('Content-Type: text/html; charset=UTF-8');
 
 if ($sco->scormtype == 'asset') {
     // HTTP 302 Found => Moved Temporarily.
-    header('Location: ' . $result);
+    header('Location: ' . $scolaunchurl);
     // Provide a short feedback in case of slow network connection.
     echo html_writer::start_tag('html');
     echo html_writer::tag('body', html_writer::tag('p', get_string('activitypleasewait', 'scorm')));
@@ -216,7 +136,7 @@ echo html_writer::tag('title', 'LoadSCO');
 
    function doredirect() {
         if (myGetAPIHandle() != null) {
-            location = "<?php echo $result ?>";
+            location = "<?php echo $scolaunchurl ?>";
         }
         else {
             document.body.innerHTML = "<p><?php echo get_string('activityloading', 'scorm');?>" +
@@ -231,7 +151,7 @@ echo html_writer::tag('title', 'LoadSCO');
                                             } else {
                                                 clearInterval(timer);
                                                 document.body.innerHTML = "<p><?php echo get_string('activitypleasewait', 'scorm');?></p>";
-                                                location = "<?php echo $result ?>";
+                                                location = "<?php echo $scolaunchurl ?>";
                                             }
                                         }, 1000);
         }
@@ -239,7 +159,7 @@ echo html_writer::tag('title', 'LoadSCO');
     //]]>
     </script>
     <noscript>
-        <meta http-equiv="refresh" content="0;url=<?php echo $result ?>" />
+        <meta http-equiv="refresh" content="0;url=<?php echo $scolaunchurl ?>" />
     </noscript>
 <?php
 echo html_writer::end_tag('head');
index 7fb480f..2ccf618 100644 (file)
@@ -2087,3 +2087,114 @@ function scorm_require_available($scorm, $checkviewreportcap = false, $context =
     }
 
 }
+
+/**
+ * Return a SCO object and the SCO launch URL
+ *
+ * @param  stdClass $scorm SCORM object
+ * @param  int $scoid The SCO id in database
+ * @param  stdClass $context context object
+ * @return array the SCO object and URL
+ * @since  Moodle 3.1
+ */
+function scorm_get_sco_and_launch_url($scorm, $scoid, $context) {
+    global $CFG, $DB;
+
+    if (!empty($scoid)) {
+        // Direct SCO request.
+        if ($sco = scorm_get_sco($scoid)) {
+            if ($sco->launch == '') {
+                // Search for the next launchable sco.
+                if ($scoes = $DB->get_records_select(
+                        'scorm_scoes',
+                        'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true).' AND id > ?',
+                        array($scorm->id, $sco->id),
+                        'sortorder, id')) {
+                    $sco = current($scoes);
+                }
+            }
+        }
+    }
+
+    // If no sco was found get the first of SCORM package.
+    if (!isset($sco)) {
+        $scoes = $DB->get_records_select(
+            'scorm_scoes',
+            'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true),
+            array($scorm->id),
+            'sortorder, id'
+        );
+        $sco = current($scoes);
+    }
+
+    $connector = '';
+    $version = substr($scorm->version, 0, 4);
+    if ((isset($sco->parameters) && (!empty($sco->parameters))) || ($version == 'AICC')) {
+        if (stripos($sco->launch, '?') !== false) {
+            $connector = '&';
+        } else {
+            $connector = '?';
+        }
+        if ((isset($sco->parameters) && (!empty($sco->parameters))) && ($sco->parameters[0] == '?')) {
+            $sco->parameters = substr($sco->parameters, 1);
+        }
+    }
+
+    if ($version == 'AICC') {
+        require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php");
+        $aiccsid = scorm_aicc_get_hacp_session($scorm->id);
+        if (empty($aiccsid)) {
+            $aiccsid = sesskey();
+        }
+        $scoparams = '';
+        if (isset($sco->parameters) && (!empty($sco->parameters))) {
+            $scoparams = '&'. $sco->parameters;
+        }
+        $launcher = $sco->launch.$connector.'aicc_sid='.$aiccsid.'&aicc_url='.$CFG->wwwroot.'/mod/scorm/aicc.php'.$scoparams;
+    } else {
+        if (isset($sco->parameters) && (!empty($sco->parameters))) {
+            $launcher = $sco->launch.$connector.$sco->parameters;
+        } else {
+            $launcher = $sco->launch;
+        }
+    }
+
+    if (scorm_external_link($sco->launch)) {
+        // TODO: does this happen?
+        $scolaunchurl = $launcher;
+    } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) {
+        // Remote learning activity.
+        $scolaunchurl = dirname($scorm->reference).'/'.$launcher;
+    } else if ($scorm->scormtype === SCORM_TYPE_LOCAL && strtolower($scorm->reference) == 'imsmanifest.xml') {
+        // This SCORM content sits in a repository that allows relative links.
+        $scolaunchurl = "$CFG->wwwroot/pluginfile.php/$context->id/mod_scorm/imsmanifest/$scorm->revision/$launcher";
+    } else if ($scorm->scormtype === SCORM_TYPE_LOCAL or $scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
+        // Note: do not convert this to use get_file_url() or moodle_url()
+        // SCORM does not work without slasharguments and moodle_url() encodes querystring vars.
+        $scolaunchurl = "$CFG->wwwroot/pluginfile.php/$context->id/mod_scorm/content/$scorm->revision/$launcher";
+    }
+    return array($sco, $scolaunchurl);
+}
+
+/**
+ * Trigger the scorm_launched event.
+ *
+ * @param  stdClass $scorm   scorm object
+ * @param  stdClass $sco     sco object
+ * @param  stdClass $cm      course module object
+ * @param  stdClass $context context object
+ * @param  string $scourl    SCO URL
+ * @since Moodle 3.1
+ */
+function scorm_launch_sco($scorm, $sco, $cm, $context, $scourl) {
+
+    $event = \mod_scorm\event\sco_launched::create(array(
+        'objectid' => $sco->id,
+        'context' => $context,
+        'other' => array('instanceid' => $scorm->id, 'loadedcontent' => $scourl)
+    ));
+    $event->add_record_snapshot('course_modules', $cm);
+    $event->add_record_snapshot('scorm', $scorm);
+    $event->add_record_snapshot('scorm_scoes', $sco);
+    $event->trigger();
+}
index 39dc0fd..5a71a6a 100644 (file)
@@ -795,4 +795,66 @@ class mod_scorm_external_testcase extends externallib_advanced_testcase {
         $result = external_api::clean_returnvalue($returndescription, $result);
         $this->assertEquals($expectedscorms, $result['scorms']);
     }
+
+    /**
+     * Test launch_sco
+     */
+    public function test_launch_sco() {
+        global $DB;
+
+        // Test invalid instance id.
+        try {
+            mod_scorm_external::launch_sco(0);
+            $this->fail('Exception expected due to invalid mod_scorm instance id.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('invalidrecord', $e->errorcode);
+        }
+
+        // Test not-enrolled user.
+        $user = self::getDataGenerator()->create_user();
+        $this->setUser($user);
+        try {
+            mod_scorm_external::launch_sco($this->scorm->id);
+            $this->fail('Exception expected due to not enrolled user.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('requireloginerror', $e->errorcode);
+        }
+
+        // Test user with full capabilities.
+        $this->setUser($this->student);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+
+        $scoes = scorm_get_scoes($this->scorm->id);
+        foreach ($scoes as $sco) {
+            // Find launchable SCO.
+            if ($sco->launch != '') {
+                break;
+            }
+        }
+
+        $result = mod_scorm_external::launch_sco($this->scorm->id, $sco->id);
+        $result = external_api::clean_returnvalue(mod_scorm_external::launch_sco_returns(), $result);
+
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = array_shift($events);
+
+        // Checking that the event contains the expected values.
+        $this->assertInstanceOf('\mod_scorm\event\sco_launched', $event);
+        $this->assertEquals($this->context, $event->get_context());
+        $moodleurl = new \moodle_url('/mod/scorm/player.php', array('id' => $this->cm->id, 'scoid' => $sco->id));
+        $this->assertEquals($moodleurl, $event->get_url());
+        $this->assertEventContextNotUsed($event);
+        $this->assertNotEmpty($event->get_name());
+
+        // Invalid SCO.
+        try {
+            mod_scorm_external::launch_sco($this->scorm->id, -1);
+            $this->fail('Exception expected due to invalid SCO id.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('cannotfindsco', $e->errorcode);
+        }
+    }
 }
index 22ee5e6..5175760 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015112700;    // The current module version (Date: YYYYMMDDXX).
+$plugin->version   = 2015112701;    // The current module version (Date: YYYYMMDDXX).
 $plugin->requires  = 2015111000;    // Requires this Moodle version.
 $plugin->component = 'mod_scorm';   // Full name of the plugin (used for diagnostics).
 $plugin->cron      = 300;
index ac161d1..a65c5bb 100644 (file)
@@ -44,8 +44,8 @@ class core_repositorylib_testcase extends advanced_testcase {
         $repositorypluginname = 'boxnet';
         // override repository permission
         $capability = 'repository/' . $repositorypluginname . ':view';
-        $allroles = $DB->get_records_menu('role', array(), 'id', 'archetype, id');
-        assign_capability($capability, CAP_ALLOW, $allroles['guest'], $syscontext->id, true);
+        $guestroleid = $DB->get_field('role', 'id', array('shortname' => 'guest'));
+        assign_capability($capability, CAP_ALLOW, $guestroleid, $syscontext->id, true);
 
         $plugintype = new repository_type($repositorypluginname);
         $pluginid = $plugintype->create(false);
index 1017b14..b657d95 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2015122300.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2015122300.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.