Merge branch 'MDL-47465-master' of git://github.com/ankitagarwal/moodle
authorSam Hemelryk <sam@moodle.com>
Mon, 3 Nov 2014 02:30:08 +0000 (15:30 +1300)
committerSam Hemelryk <sam@moodle.com>
Mon, 3 Nov 2014 02:30:08 +0000 (15:30 +1300)
15 files changed:
admin/tool/monitor/classes/rule_form.php
admin/tool/monitor/classes/subscription_manager.php
admin/tool/monitor/edit.php
admin/tool/monitor/lang/en/tool_monitor.php
admin/tool/monitor/tests/subscription_manager_test.php [new file with mode: 0644]
lang/en/question.php
mod/quiz/classes/structure.php
mod/quiz/edit.php
mod/quiz/edit_rest.php
mod/quiz/repaginate.php
mod/quiz/styles.css
mod/quiz/tests/repaginate_test.php
mod/quiz/tests/structure_test.php
question/preview.php
question/tests/behat/preview_question.feature

index a069779..4c80c55 100644 (file)
@@ -46,6 +46,7 @@ class rule_form extends \moodleform {
         $pluginlist = $this->_customdata['pluginlist'];
         $rule = $this->_customdata['rule'];
         $courseid = $this->_customdata['courseid'];
+        $subscriptioncount = $this->_customdata['subscriptioncount'];
 
         // General section header.
         $mform->addElement('header', 'general', get_string('general'));
@@ -100,6 +101,14 @@ class rule_form extends \moodleform {
         $mform->addRule('eventname', get_string('required'), 'required');
         $mform->addHelpButton('eventname', 'selectevent', 'tool_monitor');
 
+        // Freeze plugin and event fields for editing if there's a subscription for this rule.
+        if ($subscriptioncount > 0) {
+            $mform->freeze('plugin');
+            $mform->setConstant('plugin', $rule->plugin);
+            $mform->freeze('eventname');
+            $mform->setConstant('eventname', $rule->eventname);
+        }
+
         // Description field.
         $mform->addElement('editor', 'description', get_string('description', 'tool_monitor'), $editoroptions);
         $mform->addHelpButton('description', 'description', 'tool_monitor');
index 99003d6..1ecf565 100644 (file)
@@ -365,4 +365,19 @@ class subscription_manager {
         }
         return $result;
     }
+
+    /**
+     * Get count of subscriptions for a given rule.
+     *
+     * @param int $ruleid rule id of the subscription.
+     *
+     * @return int number of subscriptions
+     */
+    public static function count_rule_subscriptions($ruleid) {
+        global $DB;
+        $sql = self::get_subscription_join_rule_sql(true);
+        $sql .= "WHERE s.ruleid = :ruleid";
+
+        return $DB->count_records_sql($sql, array('ruleid' => $ruleid));
+    }
 }
index c69e92e..ed43f1d 100644 (file)
@@ -78,12 +78,14 @@ if (empty($courseid)) {
 if (!empty($ruleid)) {
     $rule = \tool_monitor\rule_manager::get_rule($ruleid)->get_mform_set_data();
     $rule->minutes = $rule->timewindow / MINSECS;
+    $subscriptioncount = \tool_monitor\subscription_manager::count_rule_subscriptions($ruleid);
 } else {
     $rule = new stdClass();
+    $subscriptioncount = 0;
 }
 
 $mform = new tool_monitor\rule_form(null, array('eventlist' => $eventlist, 'pluginlist' => $pluginlist, 'rule' => $rule,
-        'courseid' => $courseid));
+        'courseid' => $courseid, 'subscriptioncount' => $subscriptioncount));
 
 if ($mformdata = $mform->get_data()) {
     $rule = \tool_monitor\rule_manager::clean_ruledata_form($mformdata);
@@ -98,6 +100,10 @@ if ($mformdata = $mform->get_data()) {
 } else {
     echo $OUTPUT->header();
     $mform->set_data($rule);
+    // If there's any subscription for this rule, display an information message.
+    if ($subscriptioncount > 0) {
+        echo $OUTPUT->notification(get_string('disablefieldswarning', 'tool_monitor'), 'notifyproblem');
+    }
     $mform->display();
     echo $OUTPUT->footer();
 }
index 5433d09..efa38b1 100644 (file)
@@ -37,6 +37,7 @@ $string['defaultmessagetpl'] = 'Rule "{rulename}" has happened. You can find fur
 $string['deleterule'] = 'Delete rule';
 $string['deletesubscription'] = 'Delete subscription';
 $string['description'] = 'Description:';
+$string['disablefieldswarning'] = 'Plugin and events fields can not be edited because this rule already has subscriptions.';
 $string['duplicaterule'] = 'Duplicate rule';
 $string['editrule'] = 'Edit rule';
 $string['eventnotfound'] = 'Event not found';
diff --git a/admin/tool/monitor/tests/subscription_manager_test.php b/admin/tool/monitor/tests/subscription_manager_test.php
new file mode 100644 (file)
index 0000000..3a905ef
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for subscription manager api.
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2014 onwards Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+/**
+ * Tests for subscription manager.
+ *
+ * Class tool_monitor_subscription_manager_testcase.
+ */
+class tool_monitor_subscription_manager_testcase extends advanced_testcase {
+
+    /**
+     * Test count_rule_subscriptions method.
+     */
+    public function test_count_rule_subscriptions() {
+
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        // Create users.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        // Create few rules.
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $rule1 = $monitorgenerator->create_rule();
+        $rule2 = $monitorgenerator->create_rule();
+        $subs = \tool_monitor\subscription_manager::count_rule_subscriptions($rule1->id);
+
+        // No subscriptions at this point.
+        $this->assertEquals(0, $subs);
+
+        // Subscribe user 1 to rule 1.
+        $record = new stdClass;
+        $record->ruleid = $rule1->id;
+        $record->userid = $user1->id;
+        $monitorgenerator->create_subscription($record);
+
+        // Subscribe user 2 to rule 1.
+        $record->userid = $user2->id;
+        $monitorgenerator->create_subscription($record);
+
+        // Subscribe user 2 to rule 2.
+        $record->ruleid = $rule2->id;
+        $monitorgenerator->create_subscription($record);
+
+        // Should have 2 subscriptions for rule 1 and 1 subscription for rule 2
+        $subs1 = \tool_monitor\subscription_manager::count_rule_subscriptions($rule1->id);
+        $subs2 = \tool_monitor\subscription_manager::count_rule_subscriptions($rule2->id);
+        $this->assertEquals(2, $subs1);
+        $this->assertEquals(1, $subs2);
+    }
+}
\ No newline at end of file
index 81c9ef4..e19ac9f 100644 (file)
@@ -426,8 +426,10 @@ $string['technicalinfo_help'] = 'This technical information is probably only use
 $string['technicalinfominfraction'] = 'Minimum fraction: {$a}';
 $string['technicalinfomaxfraction'] = 'Maximum fraction: {$a}';
 $string['technicalinfoquestionsummary'] = 'Question summary: {$a}';
+$string['technicalinforesponsesummary'] = 'Response summary: {$a}';
 $string['technicalinforightsummary'] = 'Right answer summary: {$a}';
 $string['technicalinfostate'] = 'Question state: {$a}';
+$string['technicalinfovariant'] = 'Question variant: {$a}';
 $string['unknownbehaviour'] = 'Unknown behaviour: {$a}.';
 $string['unknownorunhandledtype'] = 'Unknown or unhandled question type: {$a}';
 $string['unknownquestion'] = 'Unknown question: {$a}.';
index ac6405b..d1f477b 100644 (file)
@@ -70,25 +70,15 @@ class structure {
         return new self();
     }
 
-    /**
-     * Create an instance of this class representing the structure of a given quiz.
-     * @param \stdClass $quiz the quiz settings.
-     * @return structure
-     */
-    public static function create_for($quiz) {
-        $structure = self::create();
-        $structure->populate_structure($quiz);
-        return $structure;
-    }
-
     /**
      * Create an instance of this class representing the structure of a given quiz.
      * @param \quiz $quizobj the quiz.
      * @return structure
      */
     public static function create_for_quiz($quizobj) {
-        $structure = self::create_for($quizobj->get_quiz());
+        $structure = self::create();
         $structure->quizobj = $quizobj;
+        $structure->populate_structure($quizobj->get_quiz());
         return $structure;
     }
 
@@ -181,6 +171,19 @@ class structure {
         return $this->canbeedited;
     }
 
+    /**
+     * This quiz can only be edited if they have not been attempted.
+     * Throw an exception if this is not the case.
+     */
+    public function check_can_be_edited() {
+        if (!$this->can_be_edited()) {
+            $reportlink = quiz_attempt_summary_link_to_reports($this->get_quiz(),
+                    $this->quizobj->get_cm(), $this->quizobj->get_context());
+            throw new \moodle_exception('cannoteditafterattempts', 'quiz',
+                    new \moodle_url('/mod/quiz/edit.php', array('cmid' => $this->get_cmid())), $reportlink);
+        }
+    }
+
     /**
      * How many questions are allowed per page in the quiz.
      * This setting controls how frequently extra page-breaks should be inserted
@@ -472,6 +475,8 @@ class structure {
     public function move_slot($idmove, $idbefore, $page) {
         global $DB;
 
+        $this->check_can_be_edited();
+
         $movingslot = $this->slots[$idmove];
         if (empty($movingslot)) {
             throw new moodle_exception('Bad slot ID ' . $idmove);
@@ -575,6 +580,8 @@ class structure {
      */
     public function refresh_page_numbers_and_update_db($quiz) {
         global $DB;
+        $this->check_can_be_edited();
+
         $slots = $this->refresh_page_numbers($quiz);
 
         // Record new page order.
@@ -594,6 +601,8 @@ class structure {
     public function remove_slot($quiz, $slotnumber) {
         global $DB;
 
+        $this->check_can_be_edited();
+
         $slot = $DB->get_record('quiz_slots', array('quizid' => $quiz->id, 'slot' => $slotnumber));
         $maxslot = $DB->get_field_sql('SELECT MAX(slot) FROM {quiz_slots} WHERE quizid = ?', array($quiz->id));
         if (!$slot) {
@@ -663,6 +672,8 @@ class structure {
     public function update_page_break($quiz, $slotid, $type) {
         global $DB;
 
+        $this->check_can_be_edited();
+
         $quizslots = $DB->get_records('quiz_slots', array('quizid' => $quiz->id), 'slot');
         $repaginate = new \mod_quiz\repaginate($quiz->id, $quizslots);
         $repaginate->repaginate_slots($quizslots[$slotid]->slot, $type);
index 6f9af09..0b90650 100644 (file)
@@ -98,6 +98,7 @@ if ($scrollpos) {
 
 if (optional_param('repaginate', false, PARAM_BOOL) && confirm_sesskey()) {
     // Re-paginate the quiz.
+    $structure->check_can_be_edited();
     $questionsperpage = optional_param('questionsperpage', $quiz->questionsperpage, PARAM_INT);
     quiz_repaginate_questions($quiz->id, $questionsperpage );
     quiz_delete_previews($quiz);
@@ -106,6 +107,7 @@ if (optional_param('repaginate', false, PARAM_BOOL) && confirm_sesskey()) {
 
 if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sesskey()) {
     // Add a single question to the current quiz.
+    $structure->check_can_be_edited();
     quiz_require_question_use($addquestion);
     $addonpage = optional_param('addonpage', 0, PARAM_INT);
     quiz_add_quiz_question($addquestion, $quiz, $addonpage);
@@ -116,6 +118,7 @@ if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sess
 }
 
 if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
+    $structure->check_can_be_edited();
     $addonpage = optional_param('addonpage', 0, PARAM_INT);
     // Add selected questions to the current quiz.
     $rawdata = (array) data_submitted();
@@ -133,6 +136,7 @@ if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
 
 if ((optional_param('addrandom', false, PARAM_BOOL)) && confirm_sesskey()) {
     // Add random questions to the quiz.
+    $structure->check_can_be_edited();
     $recurse = optional_param('recurse', 0, PARAM_BOOL);
     $addonpage = optional_param('addonpage', 0, PARAM_INT);
     $categoryid = required_param('categoryid', PARAM_INT);
@@ -145,6 +149,7 @@ if ((optional_param('addrandom', false, PARAM_BOOL)) && confirm_sesskey()) {
 }
 
 if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
+    $structure->check_can_be_edited();
     $deletepreviews = false;
     $recomputesummarks = false;
 
index 9bfd958..7396ba7 100644 (file)
@@ -53,6 +53,7 @@ $quiz = $DB->get_record('quiz', array('id' => $quizid), '*', MUST_EXIST);
 $cm = get_coursemodule_from_instance('quiz', $quiz->id, $quiz->course);
 $course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
 require_login($course, false, $cm);
+
 $quizobj = new quiz($quiz, $cm, $course);
 $structure = $quizobj->get_structure();
 $modcontext = context_module::instance($cm->id);
index ee46e73..9b43014 100644 (file)
@@ -34,6 +34,12 @@ require_sesskey();
 $quizobj = quiz::create($quizid);
 require_login($quizobj->get_course(), false, $quizobj->get_cm());
 require_capability('mod/quiz:manage', $quizobj->get_context());
+if (quiz_has_attempts($quizid)) {
+    $reportlink = quiz_attempt_summary_link_to_reports($quizobj->get_quiz(),
+                    $quizobj->get_cm(), $quizobj->get_context());
+    throw new \moodle_exception('cannoteditafterattempts', 'quiz',
+            new moodle_url('/mod/quiz/edit.php', array('cmid' => $cmid)), $reportlink);
+}
 
 $slotnumber++;
 $repage = new \mod_quiz\repaginate($quizid);
index 511d477..f63dbad 100644 (file)
@@ -555,6 +555,11 @@ table.quizreviewsummary td.cell {
     right: auto;
     left: 0;
 }
+
+#page-mod-quiz-edit .mod-quiz-edit-content .moodle-actionmenu[data-enhanced].show .menu a {
+    white-space: nowrap;
+}
+
 #page-mod-quiz-edit .slotnumber {
     background-color: #D3D3D3;
     text-align: center;
index 06cda7d..f82c20c 100644 (file)
@@ -90,6 +90,7 @@ class mod_quiz_repaginate_test extends advanced_testcase {
 
         $quiz = $quizgenerator->create_instance(array(
                 'course' => $SITE->id, 'questionsperpage' => 0, 'grade' => 100.0, 'sumgrades' => 2));
+        $cm = get_coursemodule_from_instance('quiz', $quiz->id, $SITE->id);
 
         // Create five questions.
         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
@@ -109,7 +110,8 @@ class mod_quiz_repaginate_test extends advanced_testcase {
         quiz_add_quiz_question($match->id, $quiz);
 
         // Return the quiz object.
-        return \mod_quiz\structure::create_for($quiz);
+        $quizobj = new quiz($quiz, $cm, $SITE);
+        return \mod_quiz\structure::create_for_quiz($quizobj);
     }
 
     /**
index 0a1d84c..c9117c8 100644 (file)
@@ -282,6 +282,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
         // Setup a quiz with 1 standard and 1 random question.
         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
         $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
+        $cm = get_coursemodule_from_instance('quiz', $quiz->id, $SITE->id);
 
         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
         $cat = $questiongenerator->create_question_category();
@@ -293,7 +294,8 @@ class mod_quiz_structure_testcase extends advanced_testcase {
         // Get the random question.
         $randomq = $DB->get_record('question', array('qtype' => 'random'));
 
-        $structure = \mod_quiz\structure::create_for($quiz);
+        $quizobj = new quiz($quiz, $cm, $SITE);
+        $structure = \mod_quiz\structure::create_for_quiz($quizobj);
 
         // Check that the setup looks right.
         $this->assertEquals(2, $structure->get_question_count());
@@ -303,7 +305,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
         // Remove the standard question.
         $structure->remove_slot($quiz, 1);
 
-        $alteredstructure = \mod_quiz\structure::create_for($quiz);
+        $alteredstructure = \mod_quiz\structure::create_for_quiz($quizobj);
 
         // Check the new ordering, and that the slot number was updated.
         $this->assertEquals(1, $alteredstructure->get_question_count());
@@ -314,7 +316,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
 
         // Remove the random question.
         $structure->remove_slot($quiz, 1);
-        $alteredstructure = \mod_quiz\structure::create_for($quiz);
+        $alteredstructure = \mod_quiz\structure::create_for_quiz($quizobj);
 
         // Check that new ordering.
         $this->assertEquals(0, $alteredstructure->get_question_count());
index 50b22c6..a67aeb0 100644 (file)
@@ -227,8 +227,10 @@ $technical[] = get_string('behaviourbeingused', 'question',
         question_engine::get_behaviour_name($qa->get_behaviour_name()));
 $technical[] = get_string('technicalinfominfraction',     'question', $qa->get_min_fraction());
 $technical[] = get_string('technicalinfomaxfraction',     'question', $qa->get_max_fraction());
+$technical[] = get_string('technicalinfovariant',         'question', $qa->get_variant());
 $technical[] = get_string('technicalinfoquestionsummary', 'question', s($qa->get_question_summary()));
 $technical[] = get_string('technicalinforightsummary',    'question', s($qa->get_right_answer_summary()));
+$technical[] = get_string('technicalinforesponsesummary', 'question', s($qa->get_response_summary()));
 $technical[] = get_string('technicalinfostate',           'question', '' . $qa->get_state());
 
 // Start output.
index 1065fb3..f453e2e 100644 (file)
@@ -7,39 +7,55 @@ Feature: A teacher can preview questions in the question bank
   @javascript
   Scenario: Preview a previously created question
     Given the following "users" exist:
-      | username | firstname | lastname | email |
-      | teacher1 | Teacher | 1 | teacher1@asd.com |
+      | username | firstname | lastname | email            |
+      | teacher1 | Teacher   | 1        | teacher1@asd.com |
     And the following "courses" exist:
       | fullname | shortname | format |
-      | Course 1 | C1 | weeks |
+      | Course 1 | C1        | weeks  |
     And the following "course enrolments" exist:
-      | user | course | role |
-      | teacher1 | C1 | editingteacher |
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
     And I log in as "teacher1"
     And I follow "Course 1"
     And I add a "Numerical" question filling the form with:
       | Question name | Test question to be previewed |
-      | Question text | How much is 1 + 1 |
-      | answer[0] | 2 |
-      | fraction[0] | 100% |
-      | answer[1] | * |
-      | fraction[1] | None |
+      | Question text | How much is 1 + 1             |
+      | answer[0]     | 2                             |
+      | fraction[0]   | 100%                          |
+      | answer[1]     | *                             |
+      | fraction[1]   | None                          |
+
     When I click on "Preview" "link" in the "Test question to be previewed" "table_row"
     And I switch to "questionpreview" window
     And I set the following fields to these values:
-      | Whether correct | Shown |
+      | Whether correct      | Shown             |
       | How questions behave | Deferred feedback |
     And I press "Start again with these options"
-    Then I should see "Not yet answered"
-    And I set the field "Answer:" to "2"
-    And I press "Submit and finish"
-    And the state of "How much is 1 + 1" question is shown as "Correct"
-    And I press "Start again"
-    And the state of "How much is 1 + 1" question is shown as "Not yet answered"
+
+    Then I should see "How much is 1 + 1"
+    And I should see "Technical information"
+    And I should see "Attempt options"
+    And I should see "Display options"
+
     And I set the field "Answer:" to "1"
+    And I press "Save"
+    And the state of "How much is 1 + 1" question is shown as "Answer saved"
+
     And I press "Submit and finish"
     And the state of "How much is 1 + 1" question is shown as "Incorrect"
+
     And I press "Start again"
+    And the state of "How much is 1 + 1" question is shown as "Not yet answered"
+
     And I press "Fill in correct responses"
     And the field "Answer:" matches value "2"
+    And the state of "How much is 1 + 1" question is shown as "Answer saved"
+
+    And I set the field "Whether correct" to "Not shown"
+    And I press "Update display options"
+    And the state of "How much is 1 + 1" question is shown as "Answer saved"
+
+    And I press "Submit and finish"
+    And the state of "How much is 1 + 1" question is shown as "Complete"
+
     And I switch to the main window