Merge branch 'MDL-31500_master-revert-ui-changes' of git://github.com/dmonllao/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 25 Nov 2014 13:59:33 +0000 (13:59 +0000)
committerDan Poltawski <dan@moodle.com>
Tue, 25 Nov 2014 13:59:33 +0000 (13:59 +0000)
20 files changed:
admin/tool/uploadcourse/classes/course.php
admin/tool/uploadcourse/classes/helper.php
admin/tool/uploadcourse/classes/processor.php
admin/tool/uploadcourse/classes/tracker.php
admin/tool/uploadcourse/lang/en/tool_uploadcourse.php
admin/tool/uploadcourse/tests/course_test.php
admin/tool/uploadcourse/tests/helper_test.php
grade/edit/letter/index.php
grade/tests/behat/grade_override_letter.feature [new file with mode: 0644]
group/group_form.php
group/lib.php
lib/classes/update/checker.php
lib/navigationlib.php
lib/weblib.php
mod/assign/locallib.php
mod/chat/chatd.php
mod/forum/subscribers.php
mod/forum/tests/behat/forum_subscriptions_management.feature [new file with mode: 0644]
mod/lesson/continue.php
mod/lesson/tests/behat/lesson_navigation.feature

index ff22833..f082eab 100644 (file)
@@ -108,6 +108,9 @@ class tool_uploadcourse_course {
     static protected $importoptionsdefaults = array('canrename' => false, 'candelete' => false, 'canreset' => false,
         'reset' => false, 'restoredir' => null, 'shortnametemplate' => null);
 
+    /** @var array special fields used when processing the enrolment methods. */
+    static protected $enrolmentspecialfields = array('delete', 'disable', 'startdate', 'enddate', 'enrolperiod', 'role');
+
     /**
      * Constructor
      *
@@ -655,9 +658,18 @@ class tool_uploadcourse_course {
             return false;
         }
 
+        // Getting the enrolment data.
+        $errors = array();
+        $this->enrolmentdata = tool_uploadcourse_helper::get_enrolment_data($this->rawdata, $errors);
+        if (!empty($errors)) {
+            foreach ($errors as $key => $message) {
+                $this->error($key, $message);
+            }
+            return false;
+        }
+
         // Saving data.
         $this->data = $coursedata;
-        $this->enrolmentdata = tool_uploadcourse_helper::get_enrolment_data($this->rawdata);
 
         // Restore data.
         // TODO Speed up things by not really extracting the backup just yet, but checking that
@@ -769,10 +781,20 @@ class tool_uploadcourse_course {
             return;
         }
 
+        $cannotaddmethods = array();
         $enrolmentplugins = tool_uploadcourse_helper::get_enrolment_plugins();
         $instances = enrol_get_instances($course->id, false);
         foreach ($enrolmentdata as $enrolmethod => $method) {
 
+            $plugin = $enrolmentplugins[$enrolmethod];
+
+            // TODO MDL-48362 Abstract the logic to prevent it to be tied to the
+            // user interface. Ideally a plugin should have a method that returns
+            // whether or not a new instance can be added to the course rather than
+            // using enrol_plugin::get_newinstance_link() to figure that out.
+            $canadd = $plugin->get_newinstance_link($course->id);
+
+            // TODO MDL-43820 Handle multiple instances of the same type.
             $instance = null;
             foreach ($instances as $i) {
                 if ($i->enrol == $enrolmethod) {
@@ -806,30 +828,57 @@ class tool_uploadcourse_course {
                     }
                 }
             } else {
-                $plugin = null;
                 if (empty($instance)) {
-                    $plugin = $enrolmentplugins[$enrolmethod];
+
+                    // Check if we can create a new instance of this enrolment method.
+                    if (!$canadd) {
+                        $cannotaddmethods[] = $enrolmethod;
+                        continue;
+                    }
+
+                    // Some plugins do not implement enrol_plugin::add_default_instance(),
+                    // but we will try anyway and call enrol_plugin::add_instance() if needed.
+                    $id = $plugin->add_default_instance($course);
+                    if (empty($id)) {
+                        $id = $plugin->add_instance($course);
+                    }
+
                     $instance = new stdClass();
-                    $instance->id = $plugin->add_default_instance($course);
+                    $instance->id = $id;
                     $instance->roleid = $plugin->get_config('roleid');
                     $instance->status = ENROL_INSTANCE_ENABLED;
                 } else {
-                    $plugin = $enrolmentplugins[$instance->enrol];
                     $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
                 }
 
                 // Now update values.
                 foreach ($method as $k => $v) {
+                    if (in_array($k, self::$enrolmentspecialfields)) {
+                        // Skip the special import keys.
+                        continue;
+                    }
                     $instance->{$k} = $v;
                 }
 
                 // Sort out the start, end and date.
-                $instance->enrolstartdate = (isset($method['startdate']) ? strtotime($method['startdate']) : 0);
-                $instance->enrolenddate = (isset($method['enddate']) ? strtotime($method['enddate']) : 0);
+                if (isset($method['startdate'])) {
+                    $instance->enrolstartdate = strtotime($method['startdate']);
+                } else if (!isset($instance->enrolstartdate)) {
+                    $instance->enrolstartdate = 0;
+                }
+                if (isset($method['enddate'])) {
+                    $instance->enrolenddate = strtotime($method['enddate']);
+                } else if (!isset($instance->enrolenddate)) {
+                    $instance->enrolenddate = 0;
+                }
 
                 // Is the enrolment period set?
-                if (isset($method['enrolperiod']) && ! empty($method['enrolperiod'])) {
-                    if (preg_match('/^\d+$/', $method['enrolperiod'])) {
+                if (isset($method['enrolperiod'])) {
+                    if (empty($method['enrolperiod'])) {
+                        // Let's just make sure that it's 0.
+                        $method['enrolperiod'] = 0;
+                    } else if (preg_match('/^\d+$/', $method['enrolperiod'])) {
+                        // Cast integers to integers.
                         $method['enrolperiod'] = (int) $method['enrolperiod'];
                     } else {
                         // Try and convert period to seconds.
@@ -859,6 +908,11 @@ class tool_uploadcourse_course {
                 $DB->update_record('enrol', $instance);
             }
         }
+
+        if (!empty($cannotaddmethods)) {
+            $this->error('cannotaddenrolmentmethods', new lang_string('cannotaddenrolmentmethods', 'tool_uploadcourse',
+                implode(', ', $cannotaddmethods)));
+        }
     }
 
     /**
index 2873f3b..0a3e12e 100644 (file)
@@ -134,9 +134,10 @@ class tool_uploadcourse_helper {
      * )
      *
      * @param array $data data to extract the enrolment data from.
+     * @param array $errors will be populated with errors found.
      * @return array
      */
-    public static function get_enrolment_data($data) {
+    public static function get_enrolment_data($data, &$errors = array()) {
         $enrolmethods = array();
         $enroloptions = array();
         foreach ($data as $field => $value) {
@@ -158,16 +159,26 @@ class tool_uploadcourse_helper {
 
         // Combining enrolment methods and their options in a single array.
         $enrolmentdata = array();
+        $unknownmethods = array();
+        $methodsnotsupported = array();
         if (!empty($enrolmethods)) {
             $enrolmentplugins = self::get_enrolment_plugins();
             foreach ($enrolmethods as $key => $method) {
                 if (!array_key_exists($method, $enrolmentplugins)) {
-                    // Error!
+                    // Unknown enrolment method.
+                    $unknownmethods[] = $method;
                     continue;
                 }
                 $enrolmentdata[$enrolmethods[$key]] = $enroloptions[$key];
             }
         }
+
+        // Logging errors related to enrolment methods.
+        if (!empty($unknownmethods)) {
+            $errors['unknownenrolmentmethods'] = new lang_string('unknownenrolmentmethods', 'tool_uploadcourse',
+                implode(', ', $unknownmethods));
+        }
+
         return $enrolmentdata;
     }
 
index 8963f7e..2743846 100644 (file)
@@ -207,6 +207,7 @@ class tool_uploadcourse_processor {
             $course = $this->get_course($data);
             if ($course->prepare()) {
                 $course->proceed();
+                $outcome = 1;
 
                 $status = $course->get_statuses();
                 if (array_key_exists('coursecreated', $status)) {
@@ -217,11 +218,19 @@ class tool_uploadcourse_processor {
                     $deleted++;
                 }
 
+                // Errors can occur even though the course preparation returned true, often because
+                // some checks could not be done in course::prepare() because it requires the course to exist.
+                if ($course->has_errors()) {
+                    $status += $course->get_errors();
+                    $errors++;
+                    $outcome = 2;
+                }
+
                 $data = array_merge($data, $course->get_data(), array('id' => $course->get_id()));
-                $tracker->output($this->linenb, true, $status, $data);
+                $tracker->output($this->linenb, $outcome, $status, $data);
             } else {
                 $errors++;
-                $tracker->output($this->linenb, false, $course->get_errors(), $data);
+                $tracker->output($this->linenb, 0, $course->get_errors(), $data);
             }
         }
 
index 0daa845..3ac78b4 100644 (file)
@@ -136,7 +136,8 @@ class tool_uploadcourse_tracker {
      * Output one more line.
      *
      * @param int $line line number.
-     * @param bool $outcome success or not?
+     * @param int $outcome 0 for failure, 1 for success, 2 for success with errors. Use 2 when
+     *                     most of the process succeeded but there might have been outstanding errors.
      * @param array $status array of statuses.
      * @param array $data extra data to display.
      * @return void
@@ -148,9 +149,16 @@ class tool_uploadcourse_tracker {
         }
 
         if ($this->outputmode == self::OUTPUT_PLAIN) {
+            if ($outcome == 1) {
+                $ok = 'OK';
+            } else if (!$outcome) {
+                $ok = 'NOK';        // Not OK.
+            } else {
+                $ok = 'EOK';        // Errors, but OK.
+            }
             $message = array(
                 $line,
-                $outcome ? 'OK' : 'NOK',
+                $ok,
                 isset($data['id']) ? $data['id'] : '',
                 isset($data['shortname']) ? $data['shortname'] : '',
                 isset($data['fullname']) ? $data['fullname'] : '',
@@ -168,10 +176,12 @@ class tool_uploadcourse_tracker {
             if (is_array($status)) {
                 $status = implode(html_writer::empty_tag('br'), $status);
             }
-            if ($outcome) {
+            if ($outcome == 1) {
                 $outcome = $OUTPUT->pix_icon('i/valid', '');
-            } else {
+            } else if (!$outcome) {
                 $outcome = $OUTPUT->pix_icon('i/invalid', '');
+            } else {
+                $outcome = $OUTPUT->pix_icon('i/caution', '');
             }
             echo html_writer::start_tag('tr', array('class' => 'r' . $this->rownb % 2));
             echo html_writer::tag('td', $line, array('class' => 'c' . $ci++));
index 96b847c..64e96c5 100644 (file)
@@ -29,6 +29,7 @@ $string['allowrenames_help'] = 'Whether the rename field is accepted or not.';
 $string['allowresets'] = 'Allow resets';
 $string['allowresets_help'] = 'Whether the reset field is accepted or not.';
 $string['cachedef_helper'] = 'Helper caching';
+$string['cannotaddenrolmentmethods'] = 'Cannot add the enrolment methods: {$a}';
 $string['cannotdeletecoursenotexist'] = 'Cannot delete a course that does not exist';
 $string['cannotgenerateshortnameupdatemode'] = 'Cannot generate a shortname when updates are allowed';
 $string['cannotreadbackupfile'] = 'Cannot read the backup file';
@@ -109,6 +110,7 @@ $string['shortnametemplate'] = 'Template to generate a shortname';
 $string['shortnametemplate_help'] = 'The short name of the course is displayed in the navigation. You may use template syntax here (%f = fullname, %i = idnumber), or enter an initial value that is incremented.';
 $string['templatefile'] = 'Restore from this file after upload';
 $string['templatefile_help'] = 'Select a file to use as a template for the creation of all courses.';
+$string['unknownenrolmentmethods'] = 'Unknown enrolment methods: {$a}';
 $string['unknownimportmode'] = 'Unknown import mode';
 $string['updatemissing'] = 'Fill in missing items from CSV data and defaults';
 $string['updatemode'] = 'Update mode';
index a173e72..9d12b80 100644 (file)
@@ -900,8 +900,11 @@ class tool_uploadcourse_course_testcase extends advanced_testcase {
     }
 
     public function test_enrolment_data() {
+        global $DB;
         $this->resetAfterTest(true);
+        $this->setAdminUser();
 
+        // Adding an enrolment method to a new course.
         $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
         $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
         $data = array('shortname' => 'c1', 'summary' => 'S', 'fullname' => 'FN', 'category' => '1');
@@ -921,11 +924,135 @@ class tool_uploadcourse_course_testcase extends advanced_testcase {
             $enroldata[$instance->enrol] = $instance;
         }
 
+        $this->assertFalse($co->has_errors());
         $this->assertNotEmpty($enroldata['manual']);
         $this->assertEquals(ENROL_INSTANCE_ENABLED, $enroldata['manual']->status);
         $this->assertEquals(strtotime($data['enrolment_1_startdate']), $enroldata['manual']->enrolstartdate);
         $this->assertEquals(strtotime('1970-01-01 GMT + ' . $data['enrolment_1_enrolperiod']), $enroldata['manual']->enrolperiod);
         $this->assertEquals(strtotime('12th July 2013'), $enroldata['manual']->enrolenddate);
+
+        // Updating a course's enrolment method.
+        $c2 = $this->getDataGenerator()->create_course(array('shortname' => 'c2'));
+        $enroldata = array();
+        $instances = enrol_get_instances($c2->id, false);
+        foreach ($instances as $instance) {
+            $enroldata[$instance->enrol] = $instance;
+        }
+        $manual = $enroldata['manual'];
+        $manual->enrolstartdate = strtotime('1st January 2000');
+        $manual->enrolenddate = strtotime('2nd January 2001');
+        $manual->status = ENROL_INSTANCE_DISABLED;
+        $DB->update_record('enrol', $manual);
+
+        $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+        $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+        $data = array('shortname' => 'c2');
+        $data['enrolment_1'] = 'manual';
+        $data['enrolment_1_role'] = 'teacher';
+        $data['enrolment_1_enddate'] = '2nd August 2013';
+        $data['enrolment_1_enrolperiod'] = '10 days';
+        $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+        $this->assertTrue($co->prepare());
+        $co->proceed();
+
+        // Get the enrolment methods data.
+        $enroldata = array();
+        $instances = enrol_get_instances($co->get_id(), false);
+        foreach ($instances as $instance) {
+            $enroldata[$instance->enrol] = $instance;
+        }
+
+        $this->assertFalse($co->has_errors());
+        $this->assertNotEmpty($enroldata['manual']);
+        $this->assertEquals(ENROL_INSTANCE_ENABLED, $enroldata['manual']->status);
+        $this->assertEquals($manual->enrolstartdate, $enroldata['manual']->enrolstartdate);
+        $this->assertEquals(strtotime('1970-01-01 GMT + ' . $data['enrolment_1_enrolperiod']),
+            $enroldata['manual']->enrolperiod);
+        $this->assertEquals(strtotime('11th January 2000'), $enroldata['manual']->enrolenddate);
+
+        // Disabling an enrolment method.
+        $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+        $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+        $data = array('shortname' => 'c2');
+        $data['enrolment_1'] = 'manual';
+        $data['enrolment_1_disable'] = '1';
+        $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+        $this->assertTrue($co->prepare());
+        $co->proceed();
+
+        $enroldata = array();
+        $instances = enrol_get_instances($co->get_id(), false);
+        foreach ($instances as $instance) {
+            $enroldata[$instance->enrol] = $instance;
+        }
+        $this->assertFalse($co->has_errors());
+        $this->assertNotEmpty($enroldata['manual']);
+        $this->assertEquals(ENROL_INSTANCE_DISABLED, $enroldata['manual']->status);
+
+        // Deleting an enrolment method.
+        $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+        $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+        $data = array('shortname' => 'c2');
+        $data['enrolment_1'] = 'manual';
+        $data['enrolment_1_delete'] = '1';
+        $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+        $this->assertTrue($co->prepare());
+        $co->proceed();
+
+        $enroldata = array();
+        $instances = enrol_get_instances($co->get_id(), false);
+        foreach ($instances as $instance) {
+            $enroldata[$instance->enrol] = $instance;
+        }
+        $this->assertFalse($co->has_errors());
+        $this->assertArrayNotHasKey('manual', $enroldata);
+
+        // Trying to add a non-existing enrolment method.
+        $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+        $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+        $data = array('shortname' => 'c2');
+        $data['enrolment_1'] = 'notexisting';
+        $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+        $this->assertFalse($co->prepare());
+        $this->assertArrayHasKey('unknownenrolmentmethods', $co->get_errors());
+
+        // Trying to add a non-compatible enrolment method.
+        $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+        $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+        $data = array('shortname' => 'c2');
+        $data['enrolment_1'] = 'database';
+        $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+        $this->assertTrue($co->prepare());
+        $co->proceed();
+
+        $enroldata = array();
+        $instances = enrol_get_instances($co->get_id(), false);
+        foreach ($instances as $instance) {
+            $enroldata[$instance->enrol] = $instance;
+        }
+        $this->assertTrue($co->has_errors());
+        $this->assertArrayNotHasKey('database', $enroldata);
+        $this->assertArrayHasKey('cannotaddenrolmentmethods', $co->get_errors());
+
+        // Testing cohort enrolment method.
+        $cohort = $this->getDataGenerator()->create_cohort();
+        $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+        $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+        $data = array('shortname' => 'c2');
+        $data['enrolment_1'] = 'cohort';
+        $data['enrolment_1_customint1'] = $cohort->id;
+        $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+        $this->assertTrue($co->prepare());
+        $co->proceed();
+
+        $enroldata = array();
+        $instances = enrol_get_instances($co->get_id(), false);
+        foreach ($instances as $instance) {
+            $enroldata[$instance->enrol] = $instance;
+        }
+        $this->assertFalse($co->has_errors());
+        $this->assertArrayHasKey('cohort', $enroldata);
+        $this->assertEquals($cohort->id, $enroldata['cohort']->customint1);
     }
 
     public function test_idnumber_problems() {
index 41a838c..22569e3 100644 (file)
@@ -101,7 +101,9 @@ class tool_uploadcourse_helper_testcase extends advanced_testcase {
                 'test1' => 'test1',
             )
         );
-        $this->assertSame(tool_uploadcourse_helper::get_enrolment_data($data), $expected);
+        $errors = array();
+        $this->assertSame(tool_uploadcourse_helper::get_enrolment_data($data, $errors), $expected);
+        $this->assertArrayHasKey('unknownenrolmentmethods', $errors);
     }
 
     public function test_get_enrolment_plugins() {
index 89a5912..025584b 100644 (file)
@@ -104,6 +104,7 @@ if (!$edit) {
     echo $editlink;
 
     $table = new html_table();
+    $table->id = 'grade-letters-view';
     $table->head  = array(get_string('max', 'grades'), get_string('min', 'grades'), get_string('letter', 'grades'));
     $table->size  = array('30%', '30%', '40%');
     $table->align = array('left', 'left', 'left');
diff --git a/grade/tests/behat/grade_override_letter.feature b/grade/tests/behat/grade_override_letter.feature
new file mode 100644 (file)
index 0000000..3e20562
--- /dev/null
@@ -0,0 +1,207 @@
+@core @core_grades
+Feature: Grade letters can be overridden
+  In order to test the grade letters functionality
+  As a teacher I override site defaults
+  and alter the grade letters
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "users" exist:
+      | username | firstname | lastname | email            | idnumber |
+      | teacher1 | Teacher   | 1        | teacher1@asd.com | t1       |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Grades"
+    And I follow "Letters"
+    And I follow "Edit grade letters"
+
+  Scenario Outline: Grade letters can be completely overridden
+    When I set the following fields to these values:
+      | override               | 1    |
+      | Grade letter 1         | <l1> |
+      | gradeboundary1         | <b1> |
+      | Grade letter 2         | <l2> |
+      | gradeboundary2         | <b2> |
+      | Grade letter 3         | <l3> |
+      | gradeboundary3         | <b3> |
+      | Grade letter 4         | <l4> |
+      | gradeboundary4         | <b4> |
+      | Grade letter 5         | <l5> |
+      | gradeboundary5         | <b5> |
+      | Grade letter 6         | <l6> |
+      | gradeboundary6         | <b6> |
+      | Grade letter 7         | <l7> |
+      | gradeboundary7         | <b7> |
+      | Grade letter 8         | <l8> |
+      | gradeboundary8         | <b8> |
+      | Grade letter 9         | <l9> |
+      | gradeboundary9         | <b9> |
+      | Grade letter 10        |      |
+      | gradeboundary10        |      |
+      | Grade letter 11        |      |
+      | gradeboundary11        |      |
+      | Grade letter 12        |      |
+      | gradeboundary12        |      |
+      | Grade letter 13        |      |
+      | gradeboundary13        |      |
+      | Grade letter 14        |      |
+      | gradeboundary14        |      |
+    And I press "Save changes"
+    Then I should see "The default grade letters are currently overridden."
+    And the following should exist in the "grade-letters-view" table:
+      | Highest | Lowest | Letter    |
+      | <high1> | <low1> | <letter1> |
+      | <high2> | <low2> | <letter2> |
+      | <high3> | <low3> | <letter3> |
+      | <high4> | <low4> | <letter4> |
+      | <high5> | <low5> | <letter5> |
+      | <high6> | <low6> | <letter6> |
+
+    Examples:
+    | l1 | b1    | l2 | b2    | l3 | b3    | l4 | b4    | l5 | b5    | l6 | b6    | l7 | b7 | l8 | b8   | l9 | b9 | high1    | low1     | letter1 | high2   | low2    | letter2 | high3    | low3    | letter3 | high4    | low4    | letter4 | high5    | low5    | letter5 | high6    | low6    | letter6 |
+    | Z  | 95    | Y  | 85    | X  | 75    | W  | 65    | V  | 55    | U  | 45    |    |    |    |      |    |    | 100.00 % | 95.00 %  | Z       | 94.99 % | 85.00 % | Y       | 84.99 %  | 75.00 % | X       | 74.99 %  | 65.00 % | W       | 64.99 %  | 55.00 % | V       | 54.99 %  | 45.00 % | U       |
+    | 5  | 100   | 4  | 80    | 3  | 60    | 2  | 40    | 1  | 20    | 0  | 0     |    |    |    |      |    |    | 100.00 % | 100.00 % | 5       | 99.99 % | 80.00 % | 4       | 79.99 %  | 60.00 % | 3       | 59.99 %  | 40.00 % | 2       | 39.99 %  | 20.00 % | 1       | 19.99 %  | 0.00 %  | 0       |
+    | A  | 95.25 | B  | 76.75 | C  | 50.01 | D  | 40    | F  | 0.01  | F- | 0     |    |    |    |      |    |    | 100.00 % | 95.25 %  | A       | 95.24 % | 76.75 % | B       | 76.74 %  | 50.01 % | C       | 50.00 %  | 40.00 % | D       | 39.99 %  | 0.01 %  | F       | 0.00 %   | 0.00 %  | F-      |
+    |    |       |    |       |    |       | A  | 95.25 | B  | 76.75 | C  | 50.01 | D  | 40 | F  | 0.01 | F- | 0  | 100.00 % | 95.25 %  | A       | 95.24 % | 76.75 % | B       | 76.74 %  | 50.01 % | C       | 50.00 %  | 40.00 % | D       | 39.99 %  | 0.01 %  | F       | 0.00 %   | 0.00 %  | F-      |
+    |    |       | A  | 95.25 | B  | 76.75 | C  | 50.01 |    |       |    |       | D  | 40 | F  | 0.01 | F- | 0  | 100.00 % | 95.25 %  | A       | 95.24 % | 76.75 % | B       | 76.74 %  | 50.01 % | C       | 50.00 %  | 40.00 % | D       | 39.99 %  | 0.01 %  | F       | 0.00 %   | 0.00 %  | F-      |
+
+  Scenario: I delete a grade letter
+    Given I set the following fields to these values:
+      | override               | 1  |
+      | Grade letter 1         | A  |
+      | gradeboundary1         | 90 |
+      | Grade letter 2         | B  |
+      | gradeboundary2         | 80 |
+      | Grade letter 3         | C  |
+      | gradeboundary3         | 50 |
+      | Grade letter 4         | D  |
+      | gradeboundary4         | 40 |
+      | Grade letter 5         | E  |
+      | gradeboundary5         | 20 |
+      | Grade letter 6         | F  |
+      | gradeboundary6         | 0  |
+      | Grade letter 7         |    |
+      | gradeboundary7         |    |
+      | Grade letter 8         |    |
+      | gradeboundary8         |    |
+      | Grade letter 9         |    |
+      | gradeboundary9         |    |
+      | Grade letter 10        |    |
+      | gradeboundary10        |    |
+      | Grade letter 11        |    |
+      | gradeboundary11        |    |
+      | Grade letter 12        |    |
+      | gradeboundary12        |    |
+      | Grade letter 13        |    |
+      | gradeboundary13        |    |
+      | Grade letter 14        |    |
+      | gradeboundary14        |    |
+    And I press "Save changes"
+    And I should see "The default grade letters are currently overridden."
+    And the following should exist in the "grade-letters-view" table:
+      | Highest  | Lowest   | Letter |
+      | 100.00 % | 90.00 %  | A      |
+      | 89.99 %  | 80.00 %  | B      |
+      | 79.99 %  | 50.00 %  | C      |
+      | 49.99 %  | 40.00 %  | D      |
+      | 39.99 %  | 20.00 %  | E      |
+      | 19.99 %  | 0.00 %   | F      |
+    When I follow "Edit grade letters"
+    And I set the following fields to these values:
+      | override               | 1  |
+      | Grade letter 1         | A  |
+      | gradeboundary1         | 90 |
+      | Grade letter 2         | B  |
+      | gradeboundary2         | 80 |
+      | Grade letter 3         | C  |
+      | gradeboundary3         | 50 |
+      | Grade letter 4         | D  |
+      | gradeboundary4         | 40 |
+      | Grade letter 5         |    |
+      | gradeboundary5         |    |
+      | Grade letter 6         | F  |
+      | gradeboundary6         | 0  |
+    And I press "Save changes"
+    Then I should see "The default grade letters are currently overridden."
+    And the following should exist in the "grade-letters-view" table:
+      | Highest  | Lowest   | Letter |
+      | 100.00 % | 90.00 %  | A      |
+      | 89.99 %  | 80.00 %  | B      |
+      | 79.99 %  | 50.00 %  | C      |
+      | 49.99 %  | 40.00 %  | D      |
+      | 39.99 %  | 0.00 %   | F      |
+
+  Scenario: I override grade letters for a second time
+    Given I set the following fields to these values:
+      | override               | 1  |
+      | Grade letter 1         | A+ |
+      | gradeboundary1         | 90 |
+      | Grade letter 2         | A  |
+      | gradeboundary2         | 80 |
+      | Grade letter 3         | B+ |
+      | gradeboundary3         | 70 |
+      | Grade letter 4         | B  |
+      | gradeboundary4         | 60 |
+      | Grade letter 5         | C  |
+      | gradeboundary5         | 50 |
+      | Grade letter 6         | D  |
+      | gradeboundary6         | 40 |
+      | Grade letter 7         | F  |
+      | gradeboundary7         | 0  |
+      | Grade letter 8         |    |
+      | gradeboundary8         |    |
+      | Grade letter 9         |    |
+      | gradeboundary9         |    |
+      | Grade letter 10        |    |
+      | gradeboundary10        |    |
+      | Grade letter 11        |    |
+      | gradeboundary11        |    |
+      | Grade letter 12        |    |
+      | gradeboundary12        |    |
+      | Grade letter 13        |    |
+      | gradeboundary13        |    |
+      | Grade letter 14        |    |
+      | gradeboundary14        |    |
+    And I press "Save changes"
+    And I should see "The default grade letters are currently overridden."
+    And the following should exist in the "grade-letters-view" table:
+      | Highest  | Lowest   | Letter |
+      | 100.00 % | 90.00 %  | A+     |
+      | 89.99 %  | 80.00 %  | A      |
+      | 79.99 %  | 70.00 %  | B+     |
+      | 69.99 %  | 60.00 %  | B      |
+      | 59.99 %  | 50.00 %  | C      |
+      | 49.99 %  | 40.00 %  | D      |
+      | 39.99 %  | 0.00 %   | F      |
+    When I follow "Edit grade letters"
+    And I set the following fields to these values:
+      | override               | 1  |
+      | Grade letter 1         | α  |
+      | gradeboundary1         | 95 |
+      | Grade letter 2         | β  |
+      | gradeboundary2         | 85 |
+      | Grade letter 3         | γ  |
+      | gradeboundary3         | 70 |
+      | Grade letter 4         | δ  |
+      | gradeboundary4         | 55 |
+      | Grade letter 5         |    |
+      | gradeboundary5         |    |
+      | Grade letter 6         | Ω  |
+      | gradeboundary6         | 0  |
+      | Grade letter 7         | π  |
+      | gradeboundary7         | 90 |
+    And I press "Save changes"
+    Then I should see "The default grade letters are currently overridden."
+    And the following should exist in the "grade-letters-view" table:
+      | Highest  | Lowest   | Letter |
+      | 100.00 % | 95.00 %  | α      |
+      | 94.99 %  | 90.00 %  | π      |
+      | 89.99 %  | 85.00 %  | β      |
+      | 84.99 %  | 70.00 %  | γ      |
+      | 69.99 %  | 55.00 %  | δ      |
+      | 54.99 %  | 0.00 %   | Ω      |
\ No newline at end of file
index c29947a..1ebf853 100644 (file)
@@ -66,6 +66,11 @@ class group_form extends moodleform {
         $mform->addHelpButton('enrolmentkey', 'enrolmentkey', 'group');
         $mform->setType('enrolmentkey', PARAM_RAW);
 
+        $mform->addElement('static', 'currentpicture', get_string('currentpicture'));
+
+        $mform->addElement('checkbox', 'deletepicture', get_string('delete'));
+        $mform->setDefault('deletepicture', 0);
+
         $options = array(get_string('no'), get_string('yes'));
         $mform->addElement('select', 'hidepicture', get_string('hidepicture'), $options);
 
@@ -81,6 +86,37 @@ class group_form extends moodleform {
         $this->add_action_buttons();
     }
 
+    /**
+     * Extend the form definition after the data has been parsed.
+     */
+    public function definition_after_data() {
+        global $COURSE, $DB;
+
+        $mform = $this->_form;
+        $groupid = $mform->getElementValue('id');
+
+        if ($group = $DB->get_record('groups', array('id' => $groupid))) {
+
+            // Print picture.
+            if (!($pic = print_group_picture($group, $COURSE->id, true, true, false))) {
+                $pic = get_string('none');
+                if ($mform->elementExists('deletepicture')) {
+                    $mform->removeElement('deletepicture');
+                }
+            }
+            $imageelement = $mform->getElement('currentpicture');
+            $imageelement->setValue($pic);
+        } else {
+            if ($mform->elementExists('currentpicture')) {
+                $mform->removeElement('currentpicture');
+            }
+            if ($mform->elementExists('deletepicture')) {
+                $mform->removeElement('deletepicture');
+            }
+        }
+
+    }
+
     /**
      * Form validation
      *
index c338911..f97c026 100644 (file)
@@ -344,18 +344,25 @@ function groups_update_group_icon($group, $data, $editform) {
 
     $fs = get_file_storage();
     $context = context_course::instance($group->courseid, MUST_EXIST);
-
-    //TODO: it would make sense to allow picture deleting too (skodak)
-    if ($iconfile = $editform->save_temp_file('imagefile')) {
-        if (process_new_icon($context, 'group', 'icon', $group->id, $iconfile)) {
-            $DB->set_field('groups', 'picture', 1, array('id'=>$group->id));
-            $group->picture = 1;
+    $newpicture = $group->picture;
+
+    if (!empty($data->deletepicture)) {
+        $fs->delete_area_files($context->id, 'group', 'icon', $group->id);
+        $newpicture = 0;
+    } else if ($iconfile = $editform->save_temp_file('imagefile')) {
+        if ($rev = process_new_icon($context, 'group', 'icon', $group->id, $iconfile)) {
+            $newpicture = $rev;
         } else {
             $fs->delete_area_files($context->id, 'group', 'icon', $group->id);
-            $DB->set_field('groups', 'picture', 0, array('id'=>$group->id));
-            $group->picture = 0;
+            $newpicture = 0;
         }
         @unlink($iconfile);
+    }
+
+    if ($newpicture != $group->picture) {
+        $DB->set_field('groups', 'picture', $newpicture, array('id' => $group->id));
+        $group->picture = $newpicture;
+
         // Invalidate the group data as we've updated the group record.
         cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($group->courseid));
     }
index b6ad407..3ec3f98 100644 (file)
@@ -116,9 +116,14 @@ class checker {
      * @throws checker_exception
      */
     public function fetch() {
+
         $response = $this->get_response();
         $this->validate_response($response);
         $this->store_response($response);
+
+        // We need to reset plugin manager's caches - the currently existing
+        // singleton is not aware of eventually available updates we just fetched.
+        \core_plugin_manager::reset_caches();
     }
 
     /**
@@ -629,9 +634,13 @@ class checker {
     protected function cron_notifications(array $changes) {
         global $CFG;
 
+        if (empty($changes)) {
+            return;
+        }
+
         $notifications = array();
         $pluginman = \core_plugin_manager::instance();
-        $plugins = $pluginman->get_plugins(true);
+        $plugins = $pluginman->get_plugins();
 
         foreach ($changes as $component => $componentchanges) {
             if (empty($componentchanges)) {
@@ -691,6 +700,7 @@ class checker {
         global $CFG;
 
         if (empty($notifications)) {
+            $this->cron_mtrace('nothing to notify about. ', '');
             return;
         }
 
index 0f431d4..3a53d86 100644 (file)
@@ -4419,7 +4419,8 @@ class settings_navigation extends navigation_node {
         }
 
         // Assign local roles
-        if (!empty(get_assignable_roles($catcontext))) {
+        $assignableroles = get_assignable_roles($catcontext);
+        if (!empty($assignableroles)) {
             $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $catcontext->id));
             $categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING, null, 'roles', new pix_icon('i/assignroles', ''));
         }
index dd91f8d..c843fa2 100644 (file)
@@ -2267,6 +2267,7 @@ function print_group_picture($group, $courseid, $large=false, $return=false, $li
     }
 
     $grouppictureurl = moodle_url::make_pluginfile_url($context->id, 'group', 'icon', $group->id, '/', $file);
+    $grouppictureurl->param('rev', $group->picture);
     $output .= '<img class="grouppicture" src="'.$grouppictureurl.'"'.
         ' alt="'.s(get_string('group').' '.$group->name).'" title="'.s($group->name).'"/>';
 
index 7b5067b..e5b2873 100644 (file)
@@ -4131,6 +4131,11 @@ class assign {
         if ($this->can_view_grades()) {
             $draft = ASSIGN_SUBMISSION_STATUS_DRAFT;
             $submitted = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+
+            // Group selector will only be displayed if necessary.
+            $currenturl = new moodle_url('/mod/assign/view.php', array('id' => $this->get_course_module()->id));
+            $o .= groups_print_activity_menu($this->get_course_module(), $currenturl->out(), true);
+
             if ($instance->teamsubmission) {
                 $summary = new assign_grading_summary($this->count_teams(),
                                                       $instance->submissiondrafts,
@@ -4144,7 +4149,9 @@ class assign {
                                                       $instance->teamsubmission);
                 $o .= $this->get_renderer()->render($summary);
             } else {
-                $summary = new assign_grading_summary($this->count_participants(0),
+                // The active group has already been updated in groups_print_activity_menu().
+                $countparticipants = $this->count_participants(groups_get_activity_group($this->get_course_module()));
+                $summary = new assign_grading_summary($countparticipants,
                                                       $instance->submissiondrafts,
                                                       $this->count_submissions_with_status($draft),
                                                       $this->is_any_submission_plugin_enabled(),
index 69b6a68..37462c0 100644 (file)
@@ -130,7 +130,10 @@ class ChatDaemon {
                     foreach ($chatroom['users'] as $sessionid => $userid) {
                         // We will be polling each user as required.
                         $this->trace('...shall we poll '.$sessionid.'?');
-                        if ($this->sets_info[$sessionid]['chatuser']->lastmessageping < $this->_last_idle_poll) {
+                        if (!empty($this->sets_info[$sessionid]) && isset($this->sets_info[$sessionid]['chatuser']) &&
+                                // Having tried to exclude race conditions as already done in user_lazy_update()
+                                // please do the real job by checking the last poll.
+                                ($this->sets_info[$sessionid]['chatuser']->lastmessageping < $this->_last_idle_poll)) {
                             $this->trace('YES!');
                             // This user hasn't been polled since his last message.
                             $result = $this->write_data($this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL], '<!-- poll -->');
@@ -209,6 +212,11 @@ class ChatDaemon {
             return false;
         }
 
+        // Does promote_final() already finish its job?
+        if (!isset($this->sets_info[$sessionid]['lastinfocommit'])) {
+            return false;
+        }
+
         $now = time();
 
         // We 'll be cheating a little, and NOT updating the record data as
@@ -231,6 +239,7 @@ class ChatDaemon {
         $timenow = time();
 
         if (empty($str)) {
+            $str = new stdClass();
             $str->idle  = get_string("idle", "chat");
             $str->beep  = get_string("beep", "chat");
             $str->day   = get_string("day");
@@ -700,7 +709,12 @@ EOD;
         $monitor = array();
         if (!empty($this->conn_ufo)) {
             foreach ($this->conn_ufo as $ufoid => $ufo) {
-                $monitor[$ufoid] = $ufo->handle;
+                // Avoid socket_select() warnings by preventing the check over invalid resources.
+                if (is_resource($ufo->handle)) {
+                    $monitor[$ufoid] = $ufo->handle;
+                } else {
+                    $this->dismiss_ufo($ufo->handle, false);
+                }
             }
         }
 
@@ -735,21 +749,24 @@ EOD;
 
                 // Simply give them the message.
                 $output = chat_format_message_manually($message, $info['courseid'], $sender, $info['user']);
-                $this->trace('Delivering message "'.$output->text.'" to '.$this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL]);
+                if ($output !== false) {
+                    $this->trace('Delivering message "'.$output->text.'" to ' .
+                        $this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL]);
 
-                if ($output->beep) {
-                    $this->write_data($this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL],
-                                      '<embed src="'.$this->_beepsoundsrc.'" autostart="true" hidden="true" />');
-                }
+                    if ($output->beep) {
+                        $this->write_data($this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL],
+                                          '<embed src="'.$this->_beepsoundsrc.'" autostart="true" hidden="true" />');
+                    }
 
-                if ($info['quirks'] & QUIRK_CHUNK_UPDATE) {
-                    $output->html .= $GLOBALS['CHAT_DUMMY_DATA'];
-                    $output->html .= $GLOBALS['CHAT_DUMMY_DATA'];
-                    $output->html .= $GLOBALS['CHAT_DUMMY_DATA'];
-                }
+                    if ($info['quirks'] & QUIRK_CHUNK_UPDATE) {
+                        $output->html .= $GLOBALS['CHAT_DUMMY_DATA'];
+                        $output->html .= $GLOBALS['CHAT_DUMMY_DATA'];
+                        $output->html .= $GLOBALS['CHAT_DUMMY_DATA'];
+                    }
 
-                if (!$this->write_data($this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL], $output->html)) {
-                    $this->disconnect_session($sessionid);
+                    if (!$this->write_data($this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL], $output->html)) {
+                        $this->disconnect_session($sessionid);
+                    }
                 }
             }
         }
@@ -950,6 +967,12 @@ while (true) {
                     continue;
                 }
 
+                // Ignore desktop browser fake "favorite icon" requests.
+                if (strpos($data, 'GET /favicon.ico HTTP') === 0) {
+                    // Known malformed data, drop it without any further notice.
+                    continue;
+                }
+
                 if (!preg_match('/win=(chat|users|message|beep).*&chat_sid=([a-zA-Z0-9]*) HTTP/', $data, $info)) {
                     // Malformed data.
                     $daemon->trace('UFO with '.$handle.': Request with malformed data; connection closed', E_USER_WARNING);
index e49271e..3b121a7 100644 (file)
@@ -101,10 +101,10 @@ $PAGE->navbar->add($strsubscribers);
 $PAGE->set_title($strsubscribers);
 $PAGE->set_heading($COURSE->fullname);
 if (has_capability('mod/forum:managesubscriptions', $context)) {
-    $PAGE->set_button(forum_update_subscriptions_button($course->id, $id));
     if ($edit != -1) {
         $USER->subscriptionsediting = $edit;
     }
+    $PAGE->set_button(forum_update_subscriptions_button($course->id, $id));
 } else {
     unset($USER->subscriptionsediting);
 }
diff --git a/mod/forum/tests/behat/forum_subscriptions_management.feature b/mod/forum/tests/behat/forum_subscriptions_management.feature
new file mode 100644 (file)
index 0000000..af651ca
--- /dev/null
@@ -0,0 +1,46 @@
+@mod @mod_forum
+Feature: A teacher can control the subscription to a forum
+  In order to change individual user's subscriptions
+  As a course administrator
+  I can change subscription setting for my users
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher  | Teacher   | Tom      | teacher@example.com   |
+      | student1 | Student   | 1        | student.1@example.com |
+      | student2 | Student   | 2        | student.2@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher  | C1     | editingteacher |
+      | student1 | C1     | student        |
+      | student2 | C1     | student        |
+    And I log in as "teacher"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Forum" to section "1" and I fill the form with:
+      | Forum name        | Test forum name                |
+      | Forum type        | Standard forum for general use |
+      | Description       | Test forum description         |
+      | Subscription mode | Auto subscription              |
+
+  Scenario: A teacher can change toggle subscription editing on and off
+    Given I follow "Test forum name"
+    And I follow "Show/edit current subscribers"
+    Then ".userselector" "css_element" should not exist
+    And "Turn editing on" "button" should exist
+    And I press "Turn editing on"
+    And ".userselector" "css_element" should exist
+    And "Turn editing off" "button" should exist
+    And I press "Turn editing off"
+    And ".userselector" "css_element" should not exist
+    And "Turn editing on" "button" should exist
+    And I press "Turn editing on"
+    And ".userselector" "css_element" should exist
+    And "Turn editing off" "button" should exist
+    And I press "Turn editing off"
+    And ".userselector" "css_element" should not exist
+    And "Turn editing on" "button" should exist
index 9106cfb..8ea44cb 100644 (file)
@@ -161,11 +161,11 @@ if ($canmanage) {
     }
 }
 // Report attempts remaining
-if ($result->attemptsremaining != 0 && !$lesson->review && !$reviewmode) {
+if ($result->attemptsremaining != 0 && $lesson->review && !$reviewmode) {
     $lesson->add_message(get_string('attemptsremaining', 'lesson', $result->attemptsremaining));
 }
 // Report if max attempts reached
-if ($result->maxattemptsreached != 0 && !$lesson->review && !$reviewmode) {
+if ($result->maxattemptsreached != 0 && $lesson->review && !$reviewmode) {
     $lesson->add_message('('.get_string("maximumnumberofattemptsreached", "lesson").')');
 }
 
@@ -183,7 +183,9 @@ if ($lesson->displayleft) {
 if ($lesson->ongoing && !$reviewmode) {
     echo $lessonoutput->ongoing_score($lesson);
 }
-echo $result->feedback;
+if (!$result->maxattemptsreached && !$reviewmode) {
+    echo $result->feedback;
+}
 
 // User is modifying attempts - save button and some instructions
 if (isset($USER->modattempts[$lesson->id])) {
@@ -198,7 +200,7 @@ if (isset($USER->modattempts[$lesson->id])) {
 }
 
 // Review button back
-if (!$result->correctanswer && !$result->noanswer && !$result->isessayquestion && !$reviewmode && $lesson->review) {
+if (!$result->correctanswer && !$result->noanswer && !$result->isessayquestion && !$reviewmode && $lesson->review && !$result->maxattemptsreached) {
     $url = $CFG->wwwroot.'/mod/lesson/view.php';
     $content = html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'id', 'value'=>$cm->id));
     $content .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'pageid', 'value'=>$page->id));
@@ -207,7 +209,7 @@ if (!$result->correctanswer && !$result->noanswer && !$result->isessayquestion &
 }
 
 $url = new moodle_url('/mod/lesson/view.php', array('id'=>$cm->id, 'pageid'=>$result->newpageid));
-if ($lesson->review && !$result->correctanswer && !$result->noanswer && !$result->isessayquestion) {
+if ($lesson->review && !$result->correctanswer && !$result->noanswer && !$result->isessayquestion && !$result->maxattemptsreached) {
     // Review button continue
     echo $OUTPUT->single_button($url, get_string('reviewquestioncontinue', 'lesson'));
 } else {
index 5cbad7c..7a2a34f 100644 (file)
@@ -4,8 +4,7 @@ Feature: In a lesson activity, students can navigate through a series of pages i
   As a teacher
   I need to add pages and questions with links between them
 
-  @javascript
-  Scenario: Student navigation with pages and questions
+  Background:
     Given the following "users" exist:
       | username | firstname | lastname | email |
       | teacher1 | Teacher | 1 | teacher1@asd.com |
@@ -20,7 +19,10 @@ Feature: In a lesson activity, students can navigate through a series of pages i
     And I log in as "teacher1"
     And I follow "Course 1"
     And I turn editing mode on
-    And I add a "Lesson" to section "1" and I fill the form with:
+
+  @javascript
+  Scenario: Student navigation with pages and questions
+    Given I add a "Lesson" to section "1" and I fill the form with:
       | Name | Test lesson name |
       | Description | Test lesson description |
     And I follow "Test lesson name"
@@ -87,3 +89,45 @@ Feature: In a lesson activity, students can navigate through a series of pages i
     And I press "Continue"
     And I should see "Congratulations - end of lesson reached"
     And I should see "Your score is 0 (out of 1)."
+
+  @javascript
+  Scenario: Student reattempts a question until out of attempts
+    Given I add a "Lesson" to section "1" and I fill the form with:
+      | Name | Test lesson name |
+      | Description | Test lesson description |
+      | id_review | Yes |
+      | id_maxattempts | 3 |
+    And I follow "Test lesson name"
+    And I follow "Add a question page"
+    And I set the following fields to these values:
+      | id_qtype | True/false |
+    And I press "Add a question page"
+    And I set the following fields to these values:
+      | Page title | Test question |
+      | Page contents | Test content |
+      | id_answer_editor_0 | right |
+      | id_answer_editor_1 | wrong |
+    And I press "Save page"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    When I follow "Test lesson name"
+    Then I should see "Test content"
+    And I set the following fields to these values:
+      | wrong | 1 |
+    And I press "Submit"
+    And I should see "You have 2 attempt(s) remaining"
+    And I press "Yes, I'd like to try again"
+    And I should see "Test content"
+    And I set the following fields to these values:
+      | wrong | 1 |
+    And I press "Submit"
+    And I should see "You have 1 attempt(s) remaining"
+    And I press "Yes, I'd like to try again"
+    And I should see "Test content"
+    And I set the following fields to these values:
+      | wrong | 1 |
+    And I press "Submit"
+    And I should see "(Maximum number of attempts reached - Moving to next page)"
+    And I press "Continue"
+    And I should see "Congratulations - end of lesson reached"