diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php
index 5f6dfc1f8a8..91ac3d2ac47 100644
--- a/mod/quiz/attemptlib.php
+++ b/mod/quiz/attemptlib.php
@@ -446,13 +446,18 @@ class quiz_attempt {
/** @var int maximum number of slots in the quiz for the review page to default to show all. */
const MAX_SLOTS_FOR_DEFAULT_REVIEW_SHOW_ALL = 50;
- // Basic data.
+ /** @var quiz object containing the quiz settings. */
protected $quizobj;
+
+ /** @var stdClass the quiz_attempts row. */
protected $attempt;
/** @var question_usage_by_activity the question usage for this quiz attempt. */
protected $quba;
+ /** @var array of quiz_slots rows. */
+ protected $slots;
+
/** @var array page no => array of slot numbers on the page in order. */
protected $pagelayout;
@@ -477,6 +482,8 @@ class quiz_attempt {
* of the state of each question. Else just set up the basic details of the attempt.
*/
public function __construct($attempt, $quiz, $cm, $course, $loadquestions = true) {
+ global $DB;
+
$this->attempt = $attempt;
$this->quizobj = new quiz($quiz, $cm, $course);
@@ -485,6 +492,10 @@ class quiz_attempt {
}
$this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
+ $this->slots = $DB->get_records('quiz_slots',
+ array('quizid' => $this->get_quizid()), 'slot',
+ 'slot, page, requireprevious, questionid, maxmark');
+
$this->determine_layout();
$this->number_questions();
}
@@ -987,6 +998,21 @@ class quiz_attempt {
}
/**
+ * Checks whether the question in this slot requires the previous question to have been completed.
+ *
+ * @param int $slot the number used to identify this question within this attempt.
+ * @return bool whether the previous question must have been completed before this one can be seen.
+ */
+ public function is_blocked_by_previous_question($slot) {
+ return $slot > 1 && $this->slots[$slot]->requireprevious &&
+ !$this->get_quiz()->shufflequestions &&
+ $this->get_navigation_method() != QUIZ_NAVMETHOD_SEQ &&
+ !$this->quba->get_question_state($slot - 1)->is_finished() &&
+ $this->quba->can_question_finish_during_attempt($slot - 1);
+ }
+
+ /**
+ * Get the displayed question number for a slot.
* @param int $slot the number used to identify this question within this attempt.
* @return string the displayed question number for the question in this slot.
* For example '1', '2', '3' or 'i'.
@@ -1168,36 +1194,6 @@ class quiz_attempt {
return new moodle_url('/mod/quiz/processattempt.php');
}
- /**
- * Return slot object for the given slotnumber in a given quizid
- *
- * @param int $quizid
- * @param int $slotnumber
- */
- public function get_slot_object($quizid, $slotnumber) {
- global $DB;
- return $DB->get_record('quiz_slots', array('slot' => $slotnumber, 'quizid' => $quizid));
- }
-
- /**
- * Checks whether it requires previous question. If the previous question is not completed
- * return a message in descripyiom question type format, otherwise returns null
- *
- * @param int $slot
- */
- public function require_previous_question($slot) {
- $quiz = $this->get_quiz();
- $currentslot = $this->get_slot_object($quiz->id, $slot);
- $previousslot = $this->get_slot_object($quiz->id, $currentslot->slot - 1);
-
- if ($currentslot->requireprevious && $previousslot) {
- if ($this->get_question_status($previousslot->slot, false) == 'Not yet answered') {
- return $this->quba->replace_question_with_a_description_qtye($currentslot);
- }
- }
- return null;
- }
-
/**
* @param int $slot indicates which question to link to.
* @param int $page if specified, the URL of this particular page of the attempt, otherwise
@@ -1285,11 +1281,63 @@ class quiz_attempt {
* @return string HTML for the question in its current state.
*/
public function render_question($slot, $reviewing, $thispageurl = null) {
+ if ($this->is_blocked_by_previous_question($slot)) {
+ $placeholderqa = $this->make_blocked_question_placeholder($slot);
+
+ $displayoptions = $this->get_display_options($reviewing);
+ $displayoptions->manualcomment = question_display_options::HIDDEN;
+ $displayoptions->history = question_display_options::HIDDEN;
+ $displayoptions->readonly = true;
+
+ return html_writer::div($placeholderqa->render($displayoptions,
+ $this->get_question_number($slot)),
+ 'mod_quiz-blocked_question_warning');
+ }
+
return $this->quba->render_question($slot,
$this->get_display_options_with_edit_link($reviewing, $slot, $thispageurl),
$this->get_question_number($slot));
}
+ /**
+ * Create a fake question to be displayed in place of a question that is blocked
+ * until the previous question has been answered.
+ *
+ * @param unknown $slot int slot number of the question to replace.
+ * @return question_definition the placeholde question.
+ */
+ protected function make_blocked_question_placeholder($slot) {
+ $replacedquestion = $this->get_question_attempt($slot)->get_question();
+
+ question_bank::load_question_definition_classes('description');
+ $question = new qtype_description_question();
+ $question->id = $replacedquestion->id;
+ $question->category = null;
+ $question->parent = 0;
+ $question->qtype = question_bank::get_qtype('description');
+ $question->name = '';
+ $question->questiontext = get_string('questiondependsonprevious', 'quiz');
+ $question->questiontextformat = FORMAT_HTML;
+ $question->generalfeedback = '';
+ $question->defaultmark = $this->quba->get_question_max_mark($slot);
+ $question->length = $replacedquestion->length;
+ $question->penalty = 0;
+ $question->stamp = '';
+ $question->version = 0;
+ $question->hidden = 0;
+ $question->timecreated = null;
+ $question->timemodified = null;
+ $question->createdby = null;
+ $question->modifiedby = null;
+
+ $placeholderqa = new question_attempt($question, $this->quba->get_id(),
+ null, $this->quba->get_question_max_mark($slot));
+ $placeholderqa->set_slot($slot);
+ $placeholderqa->start($this->get_quiz()->preferredbehaviour, 1);
+ $placeholderqa->set_flagged($this->is_question_flagged($slot));
+ return $placeholderqa;
+ }
+
/**
* Like {@link render_question()} but displays the question at the past step
* indicated by $seq, rather than showing the latest step.
diff --git a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
index aaa9e01b95d..a15bb810d1d 100644
--- a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
+++ b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
@@ -57,7 +57,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
$qinstances = new backup_nested_element('question_instances');
$qinstance = new backup_nested_element('question_instance', array('id'), array(
- 'slot', 'page', 'questionid', 'maxmark'));
+ 'slot', 'page', 'requireprevious', 'questionid', 'maxmark'));
$feedbacks = new backup_nested_element('feedbacks');
diff --git a/mod/quiz/classes/output/edit_renderer.php b/mod/quiz/classes/output/edit_renderer.php
index 12fb25d4184..60167f86d36 100644
--- a/mod/quiz/classes/output/edit_renderer.php
+++ b/mod/quiz/classes/output/edit_renderer.php
@@ -59,7 +59,7 @@ class edit_renderer extends \plugin_renderer_base {
// Information at the top.
$output .= $this->quiz_state_warnings($structure);
$output .= $this->quiz_information($structure);
- $output .= $this->maximum_grade_input($quizobj->get_quiz(), $this->page->url);
+ $output .= $this->maximum_grade_input($structure, $pageurl);
$output .= $this->repaginate_button($structure, $pageurl);
$output .= $this->total_marks($quizobj->get_quiz());
@@ -83,8 +83,7 @@ class edit_renderer extends \plugin_renderer_base {
$output .= $this->end_section_list();
// Inialise the JavaScript.
- $this->initialise_editing_javascript($quizobj->get_course(), $quizobj->get_quiz(),
- $structure, $contexts, $pagevars, $pageurl);
+ $this->initialise_editing_javascript($structure, $contexts, $pagevars, $pageurl);
// Include the contents of any other popups required.
if ($structure->can_be_edited()) {
@@ -152,11 +151,11 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Render the form for setting a quiz' overall grade
*
- * @param \stdClass $quiz the quiz settings from the database.
+ * @param structure $structure the quiz structure.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
- public function maximum_grade_input($quiz, \moodle_url $pageurl) {
+ public function maximum_grade_input($structure, \moodle_url $pageurl) {
$output = '';
$output .= html_writer::start_div('maxgrade');
$output .= html_writer::start_tag('form', array('method' => 'post', 'action' => 'edit.php',
@@ -165,8 +164,8 @@ class edit_renderer extends \plugin_renderer_base {
$output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
$output .= html_writer::input_hidden_params($pageurl);
$a = html_writer::empty_tag('input', array('type' => 'text', 'id' => 'inputmaxgrade',
- 'name' => 'maxgrade', 'size' => ($quiz->decimalpoints + 2),
- 'value' => quiz_format_grade($quiz, $quiz->grade)));
+ 'name' => 'maxgrade', 'size' => ($structure->get_decimal_places_for_grades() + 2),
+ 'value' => $structure->formatted_quiz_grade()));
$output .= html_writer::tag('label', get_string('maximumgradex', '', $a),
array('for' => 'inputmaxgrade'));
$output .= html_writer::empty_tag('input', array('type' => 'submit',
@@ -342,10 +341,8 @@ class edit_renderer extends \plugin_renderer_base {
$contexts, $pagevars, $pageurl) {
$output = '';
- $previousquestion = null;
- foreach ($structure->get_questions_in_section($section->id) as $question) {
- $output .= $this->question_row($structure, $question, $previousquestion, $contexts, $pagevars, $pageurl);
- $previousquestion = $question;
+ foreach ($structure->get_slots_in_section($section->id) as $slot) {
+ $output .= $this->question_row($structure, $slot, $contexts, $pagevars, $pageurl);
}
return html_writer::tag('ul', $output, array('class' => 'section img-text'));
}
@@ -354,31 +351,31 @@ class edit_renderer extends \plugin_renderer_base {
* Displays one question with the surrounding controls.
*
* @param structure $structure object containing the structure of the quiz.
- * @param \stdClass $question data from the question and quiz_slots tables.
- * @param \stdClass $previousquestion data from the question and quiz_slots tables.
+ * @param int $slot which slot we are outputting.
* @param \question_edit_contexts $contexts the relevant question bank contexts.
* @param array $pagevars the variables from {@link \question_edit_setup()}.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
- public function question_row(structure $structure, $question, $previousquestion, $contexts, $pagevars, $pageurl) {
+ public function question_row(structure $structure, $slot, $contexts, $pagevars, $pageurl) {
$output = '';
- $output .= $this->page_row($structure, $question, $contexts, $pagevars, $pageurl);
+ $output .= $this->page_row($structure, $slot, $contexts, $pagevars, $pageurl);
// Page split/join icon.
$joinhtml = '';
- if ($structure->can_be_edited() && !$structure->is_last_slot_in_quiz($question->slot)) {
- $joinhtml = $this->page_split_join_button($structure->get_quiz(),
- $question, !$structure->is_last_slot_on_page($question->slot));
+ if ($structure->can_be_edited() && !$structure->is_last_slot_in_quiz($slot)) {
+ $joinhtml = $this->page_split_join_button($structure, $slot);
}
// Question HTML.
- $questionhtml = $this->question($structure, $question, $previousquestion, $pageurl);
- $questionclasses = 'activity ' . $question->qtype . ' qtype_' . $question->qtype . ' slot';
+ $questionhtml = $this->question($structure, $slot, $pageurl);
+ $qtype = $structure->get_question_type_for_slot($slot);
+ $questionclasses = 'activity ' . $qtype . ' qtype_' . $qtype . ' slot';
$output .= html_writer::tag('li', $questionhtml . $joinhtml,
- array('class' => $questionclasses, 'id' => 'slot-' . $question->slotid));
+ array('class' => $questionclasses, 'id' => 'slot-' . $structure->get_slot_id_for_slot($slot),
+ 'data-canfinish' => $structure->can_finish_during_the_attempt($slot)));
return $output;
}
@@ -387,30 +384,32 @@ class edit_renderer extends \plugin_renderer_base {
* Displays one question with the surrounding controls.
*
* @param structure $structure object containing the structure of the quiz.
- * @param \stdClass $question data from the question and quiz_slots tables.
+ * @param int $slot the first slot on the page we are outputting.
* @param \question_edit_contexts $contexts the relevant question bank contexts.
* @param array $pagevars the variables from {@link \question_edit_setup()}.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
- public function page_row(structure $structure, $question, $contexts, $pagevars, $pageurl) {
+ public function page_row(structure $structure, $slot, $contexts, $pagevars, $pageurl) {
$output = '';
+ $pagenumber = $structure->get_page_number_for_slot($slot);
+
// Put page in a span for easier styling.
- $page = html_writer::tag('span', get_string('page') . ' ' . $question->page,
+ $page = html_writer::tag('span', get_string('page') . ' ' . $pagenumber,
array('class' => 'text'));
- if ($structure->is_first_slot_on_page($question->slot)) {
+ if ($structure->is_first_slot_on_page($slot)) {
// Add the add-menu at the page level.
$addmenu = html_writer::tag('span', $this->add_menu_actions($structure,
- $question->page, $pageurl, $contexts, $pagevars),
+ $pagenumber, $pageurl, $contexts, $pagevars),
array('class' => 'add-menu-outer'));
$addquestionform = $this->add_question_form($structure,
- $question->page, $pageurl, $pagevars);
+ $pagenumber, $pageurl, $pagevars);
$output .= html_writer::tag('li', $page . $addmenu . $addquestionform,
- array('class' => 'pagenumber activity yui3-dd-drop page', 'id' => 'page-' . $question->page));
+ array('class' => 'pagenumber activity yui3-dd-drop page', 'id' => 'page-' . $pagenumber));
}
return $output;
@@ -544,30 +543,29 @@ class edit_renderer extends \plugin_renderer_base {
* Display a question.
*
* @param structure $structure object containing the structure of the quiz.
- * @param \stdClass $question data from the question and quiz_slots tables.
- * @param \stdClass $previousquestion data from the question and quiz_slots tables.
+ * @param int $slot the first slot on the page we are outputting.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
- public function question(structure $structure, $question, $previousquestion, \moodle_url $pageurl) {
+ public function question(structure $structure, $slot, \moodle_url $pageurl) {
$output = '';
$output .= html_writer::start_tag('div');
if ($structure->can_be_edited()) {
- $output .= $this->question_move_icon($question);
+ $output .= $this->question_move_icon($structure, $slot);
}
$output .= html_writer::start_div('mod-indent-outer');
- $output .= $this->question_number($question->displayednumber);
+ $output .= $this->question_number($structure->get_displayed_number_for_slot($slot));
// This div is used to indent the content.
$output .= html_writer::div('', 'mod-indent');
// Display the link to the question (or do nothing if question has no url).
- if ($question->qtype == 'random') {
- $questionname = $this->random_question($structure, $question, $pageurl);
+ if ($structure->get_question_type_for_slot($slot) == 'random') {
+ $questionname = $this->random_question($structure, $slot, $pageurl);
} else {
- $questionname = $this->question_name($structure, $question, $pageurl);
+ $questionname = $this->question_name($structure, $slot, $pageurl);
}
// Start the div for the activity title, excluding the edit icons.
@@ -579,13 +577,15 @@ class edit_renderer extends \plugin_renderer_base {
// Action icons.
$questionicons = '';
- $questionicons .= $this->question_preview_icon($structure->get_quiz(), $question);
+ $questionicons .= $this->question_preview_icon($structure->get_quiz(), $structure->get_question_in_slot($slot));
if ($structure->can_be_edited()) {
- $questionicons .= $this->question_remove_icon($question, $pageurl);
- $questionicons .= $this->question_dependency_icon($structure->get_quiz(), $question, $previousquestion);
+ $questionicons .= $this->question_remove_icon($structure, $slot, $pageurl);
}
- $questionicons .= $this->marked_out_of_field($structure->get_quiz(), $question);
+ $questionicons .= $this->marked_out_of_field($structure, $slot);
$output .= html_writer::span($questionicons, 'actions'); // Required to add js spinner icon.
+ if ($structure->can_be_edited()) {
+ $output .= $this->question_dependency_icon($structure, $slot);
+ }
// End of indentation div.
$output .= html_writer::end_tag('div');
@@ -597,10 +597,11 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Render the move icon.
*
- * @param \stdClass $question data from the question and quiz_slots tables.
- * @return string The markup for the move action, or an empty string if not available.
+ * @param structure $structure object containing the structure of the quiz.
+ * @param int $slot the first slot on the page we are outputting.
+ * @return string The markup for the move action.
*/
- public function question_move_icon($question) {
+ public function question_move_icon(structure $structure, $slot) {
return html_writer::link(new \moodle_url('#'),
$this->pix_icon('i/dragdrop', get_string('move'), 'moodle', array('class' => 'iconsmall', 'title' => '')),
array('class' => 'editing_move', 'data-action' => 'move')
@@ -650,12 +651,13 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Render an icon to remove a question from the quiz.
*
- * @param object $question The module to produce a move button for.
+ * @param structure $structure object containing the structure of the quiz.
+ * @param int $slot the first slot on the page we are outputting.
* @param \moodle_url $pageurl the canonical URL of the edit page.
* @return string HTML to output.
*/
- public function question_remove_icon($question, $pageurl) {
- $url = new \moodle_url($pageurl, array('sesskey' => sesskey(), 'remove' => $question->slot));
+ public function question_remove_icon(structure $structure, $slot, $pageurl) {
+ $url = new \moodle_url($pageurl, array('sesskey' => sesskey(), 'remove' => $slot));
$strdelete = get_string('delete');
$image = $this->pix_icon('t/delete', $strdelete);
@@ -667,15 +669,14 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Display an icon to split or join two pages of the quiz.
*
- * @param \stdClass $quiz the quiz settings from the database.
- * @param \stdClass $question data from the question and quiz_slots tables.
- * @param bool $insertpagebreak if true, show an insert page break icon.
- * else show a join pages icon.
+ * @param structure $structure object containing the structure of the quiz.
+ * @param int $slot the first slot on the page we are outputting.
* @return string HTML to output.
*/
- public function page_split_join_button($quiz, $question, $insertpagebreak) {
- $url = new \moodle_url('repaginate.php', array('cmid' => $quiz->cmid, 'quizid' => $quiz->id,
- 'slot' => $question->slot, 'repag' => $insertpagebreak ? 2 : 1, 'sesskey' => sesskey()));
+ public function page_split_join_button($structure, $slot) {
+ $insertpagebreak = !$structure->is_last_slot_on_page($slot);
+ $url = new \moodle_url('repaginate.php', array('quizid' => $structure->get_quizid(),
+ 'slot' => $slot, 'repag' => $insertpagebreak ? 2 : 1, 'sesskey' => sesskey()));
if ($insertpagebreak) {
$title = get_string('addpagebreak', 'quiz');
@@ -689,8 +690,8 @@ class edit_renderer extends \plugin_renderer_base {
// Disable the link if quiz has attempts.
$disabled = null;
- if (quiz_has_attempts($quiz->id)) {
- $disabled = "disabled";
+ if (!$structure->can_be_edited()) {
+ $disabled = 'disabled';
}
return html_writer::span($this->action_link($url, $image, null, array('title' => $title,
'class' => 'page_split_join cm-edit-action', 'disabled' => $disabled, 'data-action' => $action)),
@@ -698,55 +699,42 @@ class edit_renderer extends \plugin_renderer_base {
}
/**
- * Display an icon Add/Remove dependency
+ * Display the icon for whether this question can only be seen if the previous
+ * one has been answered.
*
- * @param object $quiz
- * @param object $question
- * @param object $previousquestion
- * @param bool $dependencysetting
+ * @param structure $structure object containing the structure of the quiz.
+ * @param int $slot the first slot on the page we are outputting.
+ * @return string HTML to output.
*/
- public function question_dependency_icon($quiz, $question, $previousquestion, $dependencysetting = true) {
- if (!$dependencysetting) {
- return null;
- }
- // Check whether the current question qualifies for dependency.
- // What about random questions?
- $unqualifiedqtypes = array('description', 'essay');
-
- // Current question is not the first question in the quiz.
- if ($question->slot == 1) {
- return ' ';
- }
- // Current question is not a description or an essay question type.
- if (in_array($question->qtype, $unqualifiedqtypes)) {
- return ' ';
- }
- // Previous question is not a description or an essay question type.
- if (in_array($previousquestion->qtype, $unqualifiedqtypes)) {
- return ' ';
- }
- // Process qualified questions.
- $url = new \moodle_url('questiondependency.php', array('cmid' => $quiz->cmid, 'quizid' => $quiz->id,
- 'slotid' => $question->slotid, 'sesskey' => sesskey()));
-
- if ($question->requireprevious) {
- $title = get_string('removequestiondependency', 'quiz');
- $image = $this->pix_icon('e/remove_page_break', $title);
- $action = 'linkpage';
+ public function question_dependency_icon($structure, $slot) {
+ $a = array(
+ 'thisq' => $structure->get_displayed_number_for_slot($slot),
+ 'previousq' => $structure->get_displayed_number_for_slot(max($slot - 1, 1)),
+ );
+ if ($structure->is_question_dependent_on_previous_slot($slot)) {
+ $title = get_string('questiondependencyremove', 'quiz', $a);
+ $image = $this->pix_icon('t/locked', get_string('questiondependsonprevious', 'quiz'),
+ 'moodle', array('title' => ''));
+ $action = 'removedependency';
} else {
- $title = get_string('addquestiondependency', 'quiz');
- $image = $this->pix_icon('e/insert_page_break', $title);
- $action = 'unlinkpage';
+ $title = get_string('questiondependencyadd', 'quiz', $a);
+ $image = $this->pix_icon('t/unlocked', get_string('questiondependencyfree', 'quiz'),
+ 'moodle', array('title' => ''));
+ $action = 'adddependency';
}
// Disable the link if quiz has attempts.
$disabled = null;
- if (quiz_has_attempts($quiz->id)) {
- $disabled = "disabled";
+ if (!$structure->can_be_edited()) {
+ $disabled = 'disabled';
}
- return html_writer::span($this->action_link($url, $image, null, array('title' => $title,
- 'class' => 'question_dependency_icon', 'disabled' => $disabled, 'data-action' => $action)),
- 'page_split_join_wrapper');
+ $extraclass = '';
+ if (!$structure->can_question_depend_on_previous_slot($slot)) {
+ $extraclass = ' question_dependency_cannot_depend';
+ }
+ return html_writer::span($this->action_link('#', $image, null, array('title' => $title,
+ 'class' => 'cm-edit-action', 'disabled' => $disabled, 'data-action' => $action)),
+ 'question_dependency_wrapper' . $extraclass);
}
/**
@@ -756,13 +744,14 @@ class edit_renderer extends \plugin_renderer_base {
* without a link
*
* @param structure $structure object containing the structure of the quiz.
- * @param \stdClass $question data from the question and quiz_slots tables.
+ * @param int $slot which slot we are outputting.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
- public function question_name(structure $structure, $question, $pageurl) {
+ public function question_name(structure $structure, $slot, $pageurl) {
$output = '';
+ $question = $structure->get_question_in_slot($slot);
$editurl = new \moodle_url('/question/question.php', array(
'returnurl' => $pageurl->out_as_local_url(),
'cmid' => $structure->get_cmid(), 'id' => $question->id));
@@ -793,12 +782,13 @@ class edit_renderer extends \plugin_renderer_base {
* and also to see that category in the question bank.
*
* @param structure $structure object containing the structure of the quiz.
- * @param \stdClass $question data from the question and quiz_slots tables.
+ * @param int $slot which slot we are outputting.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
- public function random_question(structure $structure, $question, $pageurl) {
+ public function random_question(structure $structure, $slot, $pageurl) {
+ $question = $structure->get_question_in_slot($slot);
$editurl = new \moodle_url('/question/question.php', array(
'returnurl' => $pageurl->out_as_local_url(),
'cmid' => $structure->get_cmid(), 'id' => $question->id));
@@ -831,14 +821,14 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Display the 'marked out of' information for a question.
* Along with the regrade action.
- * @param \stdClass $quiz the quiz settings from the database.
- * @param \stdClass $question data from the question and quiz_slots tables.
+ * @param structure $structure object containing the structure of the quiz.
+ * @param int $slot which slot we are outputting.
* @return string HTML to output.
*/
- public function marked_out_of_field($quiz, $question) {
- if ($question->length == 0) {
+ public function marked_out_of_field(structure $structure, $slot) {
+ if (!$structure->is_real_question($slot)) {
$output = html_writer::span('',
- 'instancemaxmark decimalplaces_' . quiz_get_grade_format($quiz));
+ 'instancemaxmark decimalplaces_' . $structure->get_decimal_places_for_question_marks());
$output .= html_writer::span(
$this->pix_icon('spacer', '', 'moodle', array('class' => 'editicon visibleifjs', 'title' => '')),
@@ -846,8 +836,8 @@ class edit_renderer extends \plugin_renderer_base {
return html_writer::span($output, 'instancemaxmarkcontainer infoitem');
}
- $output = html_writer::span(quiz_format_question_grade($quiz, $question->maxmark),
- 'instancemaxmark decimalplaces_' . quiz_get_grade_format($quiz),
+ $output = html_writer::span($structure->formatted_question_grade($slot),
+ 'instancemaxmark decimalplaces_' . $structure->get_decimal_places_for_question_marks(),
array('title' => get_string('maxmark', 'quiz')));
$output .= html_writer::span(
@@ -912,30 +902,28 @@ class edit_renderer extends \plugin_renderer_base {
* Initialise the JavaScript for the general editing. (JavaScript for popups
* is handled with the specific code for those.)
*
- * @param \stdClass $course the course settings from the database.
- * @param \stdClass $quiz the quiz settings from the database.
* @param structure $structure object containing the structure of the quiz.
* @param \question_edit_contexts $contexts the relevant question bank contexts.
* @param array $pagevars the variables from {@link \question_edit_setup()}.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return bool Always returns true
*/
- protected function initialise_editing_javascript($course, $quiz, structure $structure,
+ protected function initialise_editing_javascript(structure $structure,
\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
$config = new \stdClass();
$config->resourceurl = '/mod/quiz/edit_rest.php';
$config->sectionurl = '/mod/quiz/edit_rest.php';
$config->pageparams = array();
- $config->questiondecimalpoints = $quiz->questiondecimalpoints;
+ $config->questiondecimalpoints = $structure->get_decimal_places_for_question_marks();
$config->pagehtml = $this->new_page_template($structure, $contexts, $pagevars, $pageurl);
- $config->addpageiconhtml = $this->add_page_icon_template($structure, $quiz);
+ $config->addpageiconhtml = $this->add_page_icon_template($structure);
$this->page->requires->yui_module('moodle-mod_quiz-toolboxes',
'M.mod_quiz.init_resource_toolbox',
array(array(
- 'courseid' => $course->id,
- 'quizid' => $quiz->id,
+ 'courseid' => $structure->get_courseid(),
+ 'quizid' => $structure->get_quizid(),
'ajaxurl' => $config->resourceurl,
'config' => $config,
))
@@ -946,9 +934,8 @@ class edit_renderer extends \plugin_renderer_base {
$this->page->requires->yui_module('moodle-mod_quiz-toolboxes',
'M.mod_quiz.init_section_toolbox',
array(array(
- 'courseid' => $course->id,
- 'quizid' => $quiz->id,
- 'format' => $course->format,
+ 'courseid' => $structure,
+ 'quizid' => $structure->get_quizid(),
'ajaxurl' => $config->sectionurl,
'config' => $config,
))
@@ -956,16 +943,16 @@ class edit_renderer extends \plugin_renderer_base {
$this->page->requires->yui_module('moodle-mod_quiz-dragdrop', 'M.mod_quiz.init_section_dragdrop',
array(array(
- 'courseid' => $course->id,
- 'quizid' => $quiz->id,
+ 'courseid' => $structure,
+ 'quizid' => $structure->get_quizid(),
'ajaxurl' => $config->sectionurl,
'config' => $config,
)), null, true);
$this->page->requires->yui_module('moodle-mod_quiz-dragdrop', 'M.mod_quiz.init_resource_dragdrop',
array(array(
- 'courseid' => $course->id,
- 'quizid' => $quiz->id,
+ 'courseid' => $structure,
+ 'quizid' => $structure->get_quizid(),
'ajaxurl' => $config->resourceurl,
'config' => $config,
)), null, true);
@@ -999,6 +986,10 @@ class edit_renderer extends \plugin_renderer_base {
'dragtostart',
'numquestionsx',
'removepagebreak',
+ 'questiondependencyadd',
+ 'questiondependencyfree',
+ 'questiondependencyremove',
+ 'questiondependsonprevious',
), 'quiz');
foreach (\question_bank::get_all_qtypes() as $qtype => $notused) {
@@ -1023,11 +1014,10 @@ class edit_renderer extends \plugin_renderer_base {
return '';
}
- $question = $structure->get_question_in_slot(1);
- $pagehtml = $this->page_row($structure, $question, $contexts, $pagevars, $pageurl);
+ $pagehtml = $this->page_row($structure, 1, $contexts, $pagevars, $pageurl);
// Normalise the page number.
- $pagenumber = $question->page;
+ $pagenumber = $structure->get_page_number_for_slot(1);
$strcontexts = array();
$strcontexts[] = 'page-';
$strcontexts[] = get_string('page') . ' ';
@@ -1049,17 +1039,15 @@ class edit_renderer extends \plugin_renderer_base {
* HTML for a page, with ids stripped, so it can be used as a javascript template.
*
* @param structure $structure object containing the structure of the quiz.
- * @param \stdClass $quiz the quiz settings.
* @return string HTML for a new icon
*/
- protected function add_page_icon_template(structure $structure, $quiz) {
+ protected function add_page_icon_template(structure $structure) {
if (!$structure->has_questions()) {
return '';
}
- $question = $structure->get_question_in_slot(1);
- $html = $this->page_split_join_button($quiz, $question, true);
+ $html = $this->page_split_join_button($structure, 1);
return str_replace('&slot=1&', '&slot=%%SLOT%%&', $html);
}
diff --git a/mod/quiz/classes/structure.php b/mod/quiz/classes/structure.php
index fa871fb0e8b..72600f11e66 100644
--- a/mod/quiz/classes/structure.php
+++ b/mod/quiz/classes/structure.php
@@ -118,6 +118,112 @@ class structure {
return $this->questions[$this->slotsinorder[$slotnumber]->questionid];
}
+ /**
+ * Get the displayed question number (or 'i') for a given slot.
+ * @param int $slotnumber the index of the slot in question.
+ * @return string the question number ot display for this slot.
+ */
+ public function get_displayed_number_for_slot($slotnumber) {
+ return $this->slotsinorder[$slotnumber]->displayednumber;
+ }
+
+ /**
+ * Get the page a given slot is on.
+ * @param int $slotnumber the index of the slot in question.
+ * @return int the page number of the page that slot is on.
+ */
+ public function get_page_number_for_slot($slotnumber) {
+ return $this->slotsinorder[$slotnumber]->page;
+ }
+
+ /**
+ * Get the slot id of a given slot slot.
+ * @param int $slotnumber the index of the slot in question.
+ * @return int the page number of the page that slot is on.
+ */
+ public function get_slot_id_for_slot($slotnumber) {
+ return $this->slotsinorder[$slotnumber]->id;
+ }
+
+ /**
+ * Get the question type in a given slot.
+ * @param int $slotnumber the index of the slot in question.
+ * @return string the question type (e.g. multichoice).
+ */
+ public function get_question_type_for_slot($slotnumber) {
+ return $this->questions[$this->slotsinorder[$slotnumber]->questionid]->qtype;
+ }
+
+ /**
+ * Whether it would be possible, given the question types, etc. for the
+ * question in the given slot to require that the previous question had been
+ * answered before this one is displayed.
+ * @param int $slotnumber the index of the slot in question.
+ * @return bool can this question require the previous one.
+ */
+ public function can_question_depend_on_previous_slot($slotnumber) {
+ return $slotnumber > 1 && $this->can_finish_during_the_attempt($slotnumber - 1);
+ }
+
+ /**
+ * Whether it is possible for another question to depend on this one finishing.
+ * Note that the answer is not exact, because of random questions, and sometimes
+ * questions cannot be depended upon because of quiz options.
+ * @param int $slotnumber the index of the slot in question.
+ * @return bool can this question finish naturally during the attempt?
+ */
+ public function can_finish_during_the_attempt($slotnumber) {
+ if ($this->quizobj->get_quiz()->shufflequestions ||
+ $this->quizobj->get_navigation_method() == QUIZ_NAVMETHOD_SEQ) {
+ return false;
+ }
+
+ if ($this->get_question_type_for_slot($slotnumber) == 'random') {
+ return true;
+ }
+
+ if (isset($this->slotsinorder[$slotnumber]->canfinish)) {
+ return $this->slotsinorder[$slotnumber]->canfinish;
+ }
+
+ $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $this->quizobj->get_context());
+ $tempslot = $quba->add_question(\question_bank::load_question(
+ $this->slotsinorder[$slotnumber]->questionid));
+ $quba->set_preferred_behaviour($this->quizobj->get_quiz()->preferredbehaviour);
+ $quba->start_all_questions();
+
+ $this->slotsinorder[$slotnumber]->canfinish = $quba->can_question_finish_during_attempt($tempslot);
+ return $this->slotsinorder[$slotnumber]->canfinish;
+ }
+
+ /**
+ * Whether it would be possible, given the question types, etc. for the
+ * question in the given slot to require that the previous question had been
+ * answered before this one is displayed.
+ * @param int $slotnumber the index of the slot in question.
+ * @return bool can this question require the previous one.
+ */
+ public function is_question_dependent_on_previous_slot($slotnumber) {
+ return $this->slotsinorder[$slotnumber]->requireprevious;
+ }
+
+ /**
+ * Is a particular question in this attempt a real question, or something like a description.
+ * @param int $slotnumber the index of the slot in question.
+ * @return bool whether that question is a real question.
+ */
+ public function is_real_question($slotnumber) {
+ return $this->get_question_in_slot($slotnumber)->length != 0;
+ }
+
+ /**
+ * Get the course id that the quiz belongs to.
+ * @return int the course.id for the quiz.
+ */
+ public function get_courseid() {
+ return $this->quizobj->get_courseid();
+ }
+
/**
* Get the course module id of the quiz.
* @return int the course_modules.id for the quiz.
@@ -258,18 +364,18 @@ class structure {
}
/**
- * Get all the questions in a section of the quiz.
+ * Get all the slots in a section of the quiz.
* @param int $sectionid the section id.
- * @return \stdClass[] of question/slot objects.
+ * @return int[] slot numbers.
*/
- public function get_questions_in_section($sectionid) {
- $questions = array();
+ public function get_slots_in_section($sectionid) {
+ $slots = array();
foreach ($this->slotsinorder as $slot) {
if ($slot->sectionid == $sectionid) {
- $questions[] = $this->questions[$slot->questionid];
+ $slots[] = $slot->slot;
}
}
- return $questions;
+ return $slots;
}
/**
@@ -280,6 +386,39 @@ class structure {
return $this->sections;
}
+ /**
+ * Get the overall quiz grade formatted for display.
+ * @return string the maximum grade for this quiz.
+ */
+ public function formatted_quiz_grade() {
+ return quiz_format_grade($this->get_quiz(), $this->get_quiz()->grade);
+ }
+
+ /**
+ * Get the maximum mark for a question, formatted for display.
+ * @param int $slotnumber the index of the slot in question.
+ * @return string the maximum mark for the question in this slot.
+ */
+ public function formatted_question_grade($slotnumber) {
+ return quiz_format_question_grade($this->get_quiz(), $this->slotsinorder[$slotnumber]->maxmark);
+ }
+
+ /**
+ * Get the number of decimal places for displyaing overall quiz grades or marks.
+ * @return int the number of decimal places.
+ */
+ public function get_decimal_places_for_grades() {
+ return $this->get_quiz()->decimalpoints;
+ }
+
+ /**
+ * Get the number of decimal places for displyaing question marks.
+ * @return int the number of decimal places.
+ */
+ public function get_decimal_places_for_question_marks() {
+ return quiz_get_grade_format($this->get_quiz());
+ }
+
/**
* Get any warnings to show at the top of the edit page.
* @return string[] array of strings.
@@ -453,11 +592,10 @@ class structure {
protected function populate_question_numbers() {
$number = 1;
foreach ($this->slots as $slot) {
- $question = $this->questions[$slot->questionid];
- if ($question->length == 0) {
- $question->displayednumber = get_string('infoshort', 'quiz');
+ if ($this->questions[$slot->questionid]->length == 0) {
+ $slot->displayednumber = get_string('infoshort', 'quiz');
} else {
- $question->displayednumber = $number;
+ $slot->displayednumber = $number;
$number += 1;
}
}
@@ -661,21 +799,13 @@ class structure {
}
/**
- * Change require previous for a slot..
- * @param \stdClass $slot row from the quiz_slots table.
+ * Set whether the question in a particular slot requires the previous one.
+ * @param int $slotid id of slot.
+ * @param bool $requireprevious if true, set this question to require the previous one.
*/
- public function update_question_dependency($slot) {
+ public function update_question_dependency($slotid, $requireprevious) {
global $DB;
- $trans = $DB->start_delegated_transaction();
-
- // Swap dependency setting.
- if ($slot->requireprevious == 1) {
- $slot->requireprevious = 0;
- } else {
- $slot->requireprevious = 1;
- }
- $DB->update_record('quiz_slots', $slot);
- $trans->allow_commit();
+ $DB->set_field('quiz_slots', 'requireprevious', $requireprevious, array('id' => $slotid));
}
/**
diff --git a/mod/quiz/db/upgrade.php b/mod/quiz/db/upgrade.php
index 37a271433a6..0318ea07e47 100644
--- a/mod/quiz/db/upgrade.php
+++ b/mod/quiz/db/upgrade.php
@@ -402,6 +402,10 @@ function xmldb_quiz_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2013031900, 'quiz');
}
+ // Moodle v2.5.0 release upgrade line.
+ // Put any upgrade step following this.
+
+
// Moodle v2.6.0 release upgrade line.
// Put any upgrade step following this.
@@ -803,10 +807,10 @@ function xmldb_quiz_upgrade($oldversion) {
// Moodle v2.8.0 release upgrade line.
// Put any upgrade step following this.
- if ($oldversion < 2014111000) {
+ if ($oldversion < 2015022600) {
// Define field requireprevious to be added to quiz_slots.
$table = new xmldb_table('quiz_slots');
- $field = new xmldb_field('requireprevious', XMLDB_TYPE_INTEGER, '4', null, null, null, null, 'maxmark');
+ $field = new xmldb_field('requireprevious', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, 0, 'page');
// Conditionally launch add field page.
if (!$dbman->field_exists($table, $field)) {
@@ -814,8 +818,8 @@ function xmldb_quiz_upgrade($oldversion) {
}
// Quiz savepoint reached.
- upgrade_mod_savepoint(true, 2014111000, 'quiz');
+ upgrade_mod_savepoint(true, 2015022600, 'quiz');
}
+
return true;
}
-
diff --git a/mod/quiz/edit_rest.php b/mod/quiz/edit_rest.php
index 7396ba739ae..8ffe131d27a 100644
--- a/mod/quiz/edit_rest.php
+++ b/mod/quiz/edit_rest.php
@@ -106,6 +106,7 @@ switch($requestmethod) {
echo json_encode(array('instancemaxmark' => quiz_format_question_grade($quiz, $maxmark),
'newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades)));
break;
+
case 'updatepagebreak':
require_capability('mod/quiz:manage', $modcontext);
$slots = $structure->update_page_break($quiz, $id, $value);
@@ -116,10 +117,15 @@ switch($requestmethod) {
}
echo json_encode(array('slots' => $json));
break;
- }
- break;
- case 'course':
+ case 'updatedependency':
+ require_capability('mod/quiz:manage', $modcontext);
+ $slot = $structure->get_slot_by_id($id);
+ $value = (bool) $value;
+ $structure->update_question_dependency($slot->id, $value);
+ echo json_encode(array('requireprevious' => $value));
+ break;
+ }
break;
}
break;
diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php
index e4a2b870478..29dfe7ee839 100644
--- a/mod/quiz/lang/en/quiz.php
+++ b/mod/quiz/lang/en/quiz.php
@@ -45,7 +45,6 @@ $string['addpagebreak'] = 'Add page break';
$string['addpagehere'] = 'Add page here';
$string['addquestion'] = 'Add question';
$string['addquestionfrombanktopage'] = 'Add from the question bank to page {$a}';
-$string['addquestiondependency'] = 'Add question dependency';
$string['addquestions'] = 'Add questions';
$string['addquestionstoquiz'] = 'Add questions to current quiz';
$string['addrandom'] = 'Add {$a} random questions';
@@ -616,10 +615,10 @@ $string['questionbankmanagement'] = 'Question bank management';
$string['questionbehaviour'] = 'Question behaviour';
$string['questioncats'] = 'Question categories';
$string['questiondeleted'] = 'This question has been deleted. Please contact your teacher';
-$string['questiondependency'] = 'Question dependency';
-$string['configquestiondependency'] = 'Question dependency can be used for displaying question n+1 only if students have completed question n, because question n +1 may exposed the answer for question n.';
-$string['questiondependency_help'] = 'Question dependency can be used for displaying question n+1 only if students have completed question n, because question n +1 may exposed the answer for question n.';
-$string['questiondependsonprevious'] = 'You have to complete the previous question first, then you would be able to see the content of this question.';
+$string['questiondependencyadd'] = 'No restriction on when question {$a->thisq} can be attempted • Click to change';
+$string['questiondependencyfree'] = 'No restriction on this question';
+$string['questiondependencyremove'] = 'Question {$a->thisq} cannot be attempted until the previous question {$a->previousq} has been completed • Click to change';
+$string['questiondependsonprevious'] = 'This question cannot be attempted until the previous question has been completed.';
$string['questioninuse'] = 'The question \'{$a->questionname}\' is currently being used in:
{$a->quiznames}
The question will not be deleted from these quizzes but only from the category list.';
$string['questionmissing'] = 'Question for this session is missing';
$string['questionname'] = 'Question name';
@@ -700,7 +699,6 @@ $string['removeallquizattempts'] = 'Delete all quiz attempts';
$string['removeemptypage'] = 'Remove empty page';
$string['removepagebreak'] = 'Remove page break';
$string['removeselected'] = 'Remove selected';
-$string['removequestiondependency'] = 'Remove question dependency';
$string['rename'] = 'Rename';
$string['renderingserverconnectfailed'] = 'The server {$a} failed to process an RQP request. Check that the URL is correct.';
$string['reorderquestions'] = 'Reorder questions';
diff --git a/mod/quiz/questiondependency.php b/mod/quiz/questiondependency.php
deleted file mode 100644
index 7baf852e2a4..00000000000
--- a/mod/quiz/questiondependency.php
+++ /dev/null
@@ -1,43 +0,0 @@
-.
-
-/**
- * Set question dependency.
- *
- * @package mod_quiz
- * @copyright 2014 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require_once(__DIR__ . '/../../config.php');
-require_once($CFG->dirroot . '/mod/quiz/locallib.php');
-
-$cmid = required_param('cmid', PARAM_INT);
-$quizid = required_param('quizid', PARAM_INT);
-$slotid = required_param('slotid', PARAM_INT);
-
-require_sesskey();
-$quizobj = quiz::create($quizid);
-require_login($quizobj->get_course(), false, $quizobj->get_cm());
-require_capability('mod/quiz:manage', $quizobj->get_context());
-
-$structure = $quizobj->get_structure();
-
-// Update dependency settings on this slot.
-$slot = $structure->get_slot_by_id($slotid);
-$structure->update_question_dependency($slot);
-
-redirect(new moodle_url('edit.php', array('cmid' => $quizobj->get_cmid())));
diff --git a/mod/quiz/renderer.php b/mod/quiz/renderer.php
index ff4db1a82b9..9b746718655 100644
--- a/mod/quiz/renderer.php
+++ b/mod/quiz/renderer.php
@@ -456,14 +456,11 @@ class mod_quiz_renderer extends plugin_renderer_base {
'enctype' => 'multipart/form-data', 'accept-charset' => 'utf-8',
'id' => 'responseform'));
$output .= html_writer::start_tag('div');
+
+ // Print all the questions.
foreach ($slots as $slot) {
- $requireprevious = $attemptobj->require_previous_question($slot);
- if ($requireprevious) {
- $output .= $requireprevious;
- } else {
- $output .= $attemptobj->render_question($slot, false,
- $attemptobj->attempt_url($slot, $page));
- }
+ $output .= $attemptobj->render_question($slot, false,
+ $attemptobj->attempt_url($slot, $page));
}
$output .= html_writer::start_tag('div', array('class' => 'submitbtns'));
diff --git a/mod/quiz/repaginate.php b/mod/quiz/repaginate.php
index 9b430144498..58aef9abbc8 100644
--- a/mod/quiz/repaginate.php
+++ b/mod/quiz/repaginate.php
@@ -25,7 +25,6 @@
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
-$cmid = required_param('cmid', PARAM_INT);
$quizid = required_param('quizid', PARAM_INT);
$slotnumber = required_param('slot', PARAM_INT);
$repagtype = required_param('repag', PARAM_INT);
@@ -38,7 +37,7 @@ 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);
+ new moodle_url('/mod/quiz/edit.php', array('cmid' => $quizobj->get_cmid())), $reportlink);
}
$slotnumber++;
diff --git a/mod/quiz/styles.css b/mod/quiz/styles.css
index b42c024d887..e4fdb227758 100644
--- a/mod/quiz/styles.css
+++ b/mod/quiz/styles.css
@@ -24,6 +24,12 @@
text-align: right;
}
+#page-mod-quiz-attempt .mod_quiz-blocked_question_warning .que .formulation,
+#page-mod-quiz-review .mod_quiz-blocked_question_warning .que .formulation {
+ background: #eee;
+ border: 1px solid #dcdcdc;
+}
+
body.jsenabled .questionflagcheckbox {
display: none;
}
@@ -495,47 +501,32 @@ table.quizreviewsummary td.cell {
}
/** Mod quiz edit **/
-#page-mod-quiz-edit h2.main {
- display: inline;
- padding-right: 1em;
- clear: left;
-}
-#page-mod-quiz-edit.dir-rtl h2.main {
- padding-left: 1em;
- padding-right: 0;
-}
-
#page-mod-quiz-edit .statusbar {
margin: 0.6em 0.4em;
}
#page-mod-quiz-edit .statusdisplay {
background-color: #ffc;
clear: both;
- margin: 0.3em 1em 0.3em 0;
- padding: 1px ;
- /* Stop margin collapse. */
-}
-#page-mod-quiz-edit.dir-rtl .statusdisplay {
- margin: 0.3em 0 0.3em 1em;
+ margin: 0.3em 0;
+ padding: 1px 10px;
}
#page-mod-quiz-edit .statusdisplay p {
- margin: 0.4em;
+ margin: 4px 0;
}
-
#page-mod-quiz-edit .maxgrade,
#page-mod-quiz-edit .totalpoints {
display: block;
float: right;
- margin: -2.5em 1em 0em 1em;
+ margin: -2.5em 0 0;
padding: .2em;
}
-#page-mod-quiz-edit .maxgrade label {
- display: inline;
-}
#page-mod-quiz-edit.dir-rtl .maxgrade,
#page-mod-quiz-edit.dir-rtl .totalpoints {
float: left;
}
+#page-mod-quiz-edit .maxgrade label {
+ display: inline;
+}
#page-mod-quiz-edit li.activity > div,
#page-mod-quiz-edit li.pagenumber {
@@ -545,6 +536,7 @@ table.quizreviewsummary td.cell {
#page-mod-quiz-edit .last-add-menu {
position: relative;
height: 1.5em;
+ margin: 0 20px;
}
#page-mod-quiz-edit .add-menu-outer {
position: absolute;
@@ -564,12 +556,16 @@ table.quizreviewsummary td.cell {
display: inline-block;
}
+#page-mod-quiz-edit ul.section {
+ margin: 0;
+ padding: 0 20px;
+}
#page-mod-quiz-edit ul.slots li.section {
border: 0;
}
#page-mod-quiz-edit ul.slots li.section .content {
background-color:#FAFAFA;
- padding:5px 10px;
+ padding: 0;
}
#page-mod-quiz-edit ul.slots li.section .content h3 {
margin: 0;
@@ -587,7 +583,7 @@ table.quizreviewsummary td.cell {
}
#page-mod-quiz-edit ul.slots li.section {
list-style: none;
- margin: 0 0 5px 0;
+ margin: 0;
padding: 0;
}
#page-mod-quiz-edit ul.slots li.section .left {
@@ -611,7 +607,7 @@ table.quizreviewsummary td.cell {
}
#page-mod-quiz-edit ul.slots li.section li.activity {
background: #E6E6E6;
- margin: 3px 0 3px 0;
+ margin: 3px 0;
padding: 0.2em;
}
#page-mod-quiz-edit ul.slots li.section li.activity.page {
@@ -690,11 +686,29 @@ table.quizreviewsummary td.cell {
margin: 0 2px;
}
-#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_icon {
- -position: relative;
- vertical-align: top;
- margin-left: 5px;
- margin-right: 5px;
+#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper {
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+#page-mod-quiz-edit.dir-rtl ul.slots li.section li.activity .question_dependency_wrapper {
+ left: 0;
+ right: auto;
+}
+#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper.question_dependency_cannot_depend {
+ display: none;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper .currentlink,
+#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper .cm-edit-action {
+ position: relative;
+ left: 20px;
+ top: -1em;
+}
+#page-mod-quiz-edit.dir-rtl ul.slots li.section li.activity .question_dependency_wrapper .currentlink,
+#page-mod-quiz-edit.dir-rtl ul.slots li.section li.activity .question_dependency_wrapper .cm-edit-action {
+ right: 20px;
+ left: auto;
}
#page-mod-quiz-edit ul.slots li.section li.activity .activityinstance {
diff --git a/mod/quiz/tests/behat/attempt_require_previous.feature b/mod/quiz/tests/behat/attempt_require_previous.feature
new file mode 100644
index 00000000000..7903bfc3332
--- /dev/null
+++ b/mod/quiz/tests/behat/attempt_require_previous.feature
@@ -0,0 +1,218 @@
+@mod @mod_quiz
+Feature: Attemp a quiz where some questions require that the previous question has been answered.
+ In order to complete a quiz where questions require previous ones to be complete
+ As a student
+ I need later questions to appear once earlier ones have been answered.
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student | Student | One | student@moodle.com |
+ | teacher | Teacher | One | teacher@moodle.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | student | C1 | student |
+ | teacher | C1 | teacher |
+ And the following "question categories" exist:
+ | contextlevel | reference | name |
+ | Course | C1 | Test questions |
+
+ @javascript
+ Scenario: A question that requires the previous one is initally blocked
+ Given the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 0 |
+ | TF2 | 1 | 1 |
+
+ When I log in as "student"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I press "Attempt quiz now"
+
+ Then I should see "First question"
+ And I should see "This question cannot be attempted until the previous question has been completed."
+ And I should not see "Second question"
+ And I log out
+ And I log in as "teacher"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Attempts: 1"
+ And I follow "Review attempt"
+ And I should see "First question"
+ And I should see "This question cannot be attempted until the previous question has been completed."
+ And I should not see "Second question"
+
+ @javascript
+ Scenario: A question requires the previous one becomes available when the first one is answered
+ Given the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 0 |
+ | TF2 | 1 | 1 |
+
+ When I log in as "student"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I press "Attempt quiz now"
+ And I click on "True" "radio" in the "First question" "question"
+ And I press "Check"
+
+ Then I should see "First question"
+ And I should not see "This question cannot be attempted until the previous question has been completed."
+ And I should see "Second question"
+
+ @javascript
+ Scenario: After quiz submitted, all questions show on the review page
+ Given the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 0 |
+ | TF2 | 1 | 1 |
+
+ When I log in as "student"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I press "Attempt quiz now"
+ And I press "Next"
+ And I press "Submit all and finish"
+ And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue"
+
+ Then the state of "First question" question is shown as "Not answered"
+ And the state of "Second question" question is shown as "Not answered"
+
+ @javascript
+ Scenario: A questions cannot be blocked in a deferred feedback quiz (despite what is set in the DB).
+ Given the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | deferredfeedback |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 0 |
+ | TF2 | 1 | 1 |
+
+ When I log in as "student"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I press "Attempt quiz now"
+
+ Then I should see "First question"
+ And I should see "Second question"
+ And I should not see "This question cannot be attempted until the previous question has been completed."
+
+ @javascript
+ Scenario: A questions cannot be blocked in a shuffled quiz (despite what is set in the DB).
+ Given the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour | shufflequestions | questionsperpage |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback | 1 | 2 |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 1 |
+ | TF2 | 1 | 1 |
+
+ When I log in as "student"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I press "Attempt quiz now"
+
+ Then I should see "First question"
+ And I should see "Second question"
+ And I should not see "This question cannot be attempted until the previous question has been completed."
+
+ @javascript
+ Scenario: A questions cannot be blocked in sequential quiz (despite what is set in the DB).
+ Given the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour | navmethod |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback | sequential |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 1 |
+ | TF2 | 1 | 1 |
+
+ When I log in as "student"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I press "Attempt quiz now"
+
+ Then I should see "First question"
+ And I should see "Second question"
+ And I should not see "This question cannot be attempted until the previous question has been completed."
+
+ @javascript
+ Scenario: A questions not blocked if the previous one cannot finish, e.g. essay (despite what is set in the DB).
+ Given the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | essay | Story | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | Story | 1 | 0 |
+ | TF2 | 1 | 1 |
+
+ When I log in as "student"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I press "Attempt quiz now"
+
+ Then I should see "First question"
+ And I should see "Second question"
+ And I should not see "This question cannot be attempted until the previous question has been completed."
+
+ @javascript
+ Scenario: A questions not blocked if the previous one cannot finish, e.g. description (despite what is set in the DB).
+ Given the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | description | Info | Read me |
+ | Test questions | truefalse | TF1 | First question |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | Info | 1 | 0 |
+ | TF1 | 1 | 1 |
+
+ When I log in as "student"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I press "Attempt quiz now"
+
+ Then I should see "Read me"
+ And I should see "First question"
+ And I should not see "This question cannot be attempted until the previous question has been completed."
diff --git a/mod/quiz/tests/behat/behat_mod_quiz.php b/mod/quiz/tests/behat/behat_mod_quiz.php
index 1e073e2c845..306e707e9ad 100644
--- a/mod/quiz/tests/behat/behat_mod_quiz.php
+++ b/mod/quiz/tests/behat/behat_mod_quiz.php
@@ -44,13 +44,14 @@ class behat_mod_quiz extends behat_question_base {
* Put the specified questions on the specified pages of a given quiz.
*
* The first row should be column names:
- * | question | page | maxmark |
+ * | question | page | maxmark | requireprevious |
* The first two of those are required. The others are optional.
*
* question needs to uniquely match a question name.
* page is a page number. Must start at 1, and on each following
* row should be the same as the previous, or one more.
* maxmark What the question is marked out of. Defaults to question.defaultmark.
+ * requireprevious The question can only be attempted after the previous one was completed.
*
* Then there should be a number of rows of data, one for each question you want to add.
*
@@ -130,6 +131,19 @@ class behat_mod_quiz extends behat_question_base {
// Add the question.
quiz_add_quiz_question($questionid, $quiz, $page, $maxmark);
+
+ // Require previous.
+ if (array_key_exists('requireprevious', $questiondata)) {
+ if ($questiondata['requireprevious'] === '1') {
+ $slot = $DB->get_field('quiz_slots', 'MAX(slot)', array('quizid' => $quiz->id));
+ $DB->set_field('quiz_slots', 'requireprevious', 1,
+ array('quizid' => $quiz->id, 'slot' => $slot));
+ } else if ($questiondata['requireprevious'] !== '' && $questiondata['requireprevious'] !== '0') {
+ throw new ExpectationException('Require previous for question "' .
+ $questiondata['question'] . '" should be 0, 1 or blank.',
+ $this->getSession());
+ }
+ }
}
quiz_update_sumgrades($quiz);
diff --git a/mod/quiz/tests/behat/editing_questiondependency.feature b/mod/quiz/tests/behat/editing_questiondependency.feature
deleted file mode 100644
index 89c90ea8018..00000000000
--- a/mod/quiz/tests/behat/editing_questiondependency.feature
+++ /dev/null
@@ -1,83 +0,0 @@
-
-@mod @mod_quiz @questiondependency
-Feature: Edit quiz page - pagination
- In order to build a quiz laid out in pages with n question(s) on each page, where n >=1.
- I need to be able to add and remove question dependency on any qualified question
- in quiz editing page.
-
- Background:
- Given the following "users" exist:
- | username | firstname | lastname | email |
- | teacher1 | T1 | Teacher1 | teacher1@moodle.com |
- And the following "courses" exist:
- | fullname | shortname | category |
- | Course 1 | C1 | 0 |
- And the following "course enrolments" exist:
- | user | course | role |
- | teacher1 | C1 | editingteacher |
- And the following "activities" exist:
- | activity | name | intro | course | idnumber |
- | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 |
-
- When I log in as "teacher1"
- And I follow "Course 1"
- And I follow "Quiz 1"
- And I follow "Edit quiz"
-
- @javascript
- Scenario: There is no dependency setting on question in the quiz.
- I can add or remove dependency to a given question by clicking on "Add question dependency"
- or "Remove question dependency" icons. Then I can attempt the quiz and see the effect of the
- dependency settings.
-
- Then I should see "Editing quiz: Quiz 1"
-
- # Add the first true false question.
- And I add a "True/False" question to the "Quiz 1" quiz with:
- | Question name | TF 001 |
- | Question text | Answer the TF 001 question |
- | General feedback | Thank you, this is the general feedback |
- | Correct answer | False |
- | Feedback for the response 'True'. | So you think it is true |
- | Feedback for the response 'False'. | So you think it is false |
- And I should see "TF 001"
-
- # Add the second true false question.
- And I add a "True/False" question to the "Quiz 1" quiz with:
- | Question name | TF 002 |
- | Question text | Answer the TF 002 question |
- | General feedback | Thank you, this is the general feedback |
- | Correct answer | False |
- | Feedback for the response 'True'. | So you think it is true |
- | Feedback for the response 'False'. | So you think it is false |
- And I should see "TF 001"
- #And I should not see "Add question dependency" "link" in the "TF 001" "table_row"
- And I should see "TF 002"
- And I follow "Add question dependency"
-
- # Add the third true false question.
- And I add a "True/False" question to the "Quiz 1" quiz with:
- | Question name | TF 003 |
- | Question text | Answer the TF 003 question |
- | General feedback | Thank you, this is the general feedback |
- | Correct answer | False |
- | Feedback for the response 'True'. | So you think it is true |
- | Feedback for the response 'False'. | So you think it is false |
- And I should see "TF 001"
- And I should see "TF 002"
- And I should see "TF 003"
-
- # Attempt the quiz
- And I follow "Quiz 1"
- When I press "Preview quiz now"
- And I should see "You have to complete the previous question first, then you would be able to see the content of this question."
-
- # Back to the quiz editing page
- And I follow "Quiz 1"
- When I follow "Edit quiz"
- Then I should see "Editing quiz: Quiz 1"
- And I follow "Remove question dependency"
- And I follow "Quiz 1"
- When I press "Continue the last preview"
- And I press "Start a new preview"
- And I should not see "You have to complete the previous question first, then you would be able to see the content of this question."
\ No newline at end of file
diff --git a/mod/quiz/tests/behat/editing_require_previous.feature b/mod/quiz/tests/behat/editing_require_previous.feature
new file mode 100644
index 00000000000..fc0f2c92903
--- /dev/null
+++ b/mod/quiz/tests/behat/editing_require_previous.feature
@@ -0,0 +1,212 @@
+@mod @mod_quiz
+Feature: Edit quizzes where some questions require the previous one to have been completed
+ In order to create quizzes where later questions can only be seen after earlier ones are answered
+ As a teacher
+ I need to be able to configure this on the Edit quiz page
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | T1 | Teacher1 | teacher1@moodle.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And the following "question categories" exist:
+ | contextlevel | reference | name |
+ | Course | C1 | Test questions |
+ And I log in as "teacher1"
+
+ @javascript
+ Scenario: The first question cannot depend on the previous (whatever is in the DB)
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 1 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ Then "be attempted" "link" should not be visible
+ # The text "be attempted" is used as a relatively unique string in both the add and remove links.
+
+ @javascript
+ Scenario: If the second question depends on the first, that is shown
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 0 |
+ | TF2 | 1 | 1 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ Then "This question cannot be attempted until the previous question has been completed." "link" should be visible
+
+ @javascript
+ Scenario: The second question can be set to depend on the first
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ | Test questions | truefalse | TF3 | Third question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 0 |
+ | TF2 | 1 | 0 |
+ | TF3 | 1 | 0 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ When I follow "No restriction on when question 2 can be attempted • Click to change"
+ Then "Question 2 cannot be attempted until the previous question 1 has been completed • Click to change" "link" should be visible
+ And "No restriction on when question 3 can be attempted • Click to change" "link" should be visible
+
+ @javascript
+ Scenario: A question that did depend on the previous can be un-linked
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ | Test questions | truefalse | TF3 | Third question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 0 |
+ | TF2 | 1 | 1 |
+ | TF3 | 1 | 1 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ When I follow "Question 3 cannot be attempted until the previous question 2 has been completed • Click to change"
+ Then "Question 2 cannot be attempted until the previous question 1 has been completed • Click to change" "link" should be visible
+ And "No restriction on when question 3 can be attempted • Click to change" "link" should be visible
+
+ @javascript
+ Scenario: Question dependency cannot apply to deferred feedback quizzes so UI is hidden
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | deferredfeedback |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 0 |
+ | TF2 | 1 | 1 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ Then "be attempted" "link" in the "TF2" "list_item" should not be visible
+
+ @javascript
+ Scenario: Question dependency cannot apply to quizzes where the questions are shuffled so UI is hidden
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour | shufflequestions | questionsperpage |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback | 1 | 2 |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 1 |
+ | TF2 | 1 | 1 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ Then "be attempted" "link" in the "TF2" "list_item" should not be visible
+
+ @javascript
+ Scenario: Question dependency cannot apply to quizzes with sequential navigation so UI is hidden
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour | navmethod |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback | sequential |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 1 |
+ | TF2 | 1 | 1 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ Then "be attempted" "link" in the "TF2" "list_item" should not be visible
+
+ @javascript
+ Scenario: A question can never depend on an essay
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | essay | Story | First question |
+ | Test questions | truefalse | TF1 | First question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | Story | 1 | 0 |
+ | TF1 | 1 | 0 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ Then "be attempted" "link" in the "TF1" "list_item" should not be visible
+
+ @javascript
+ Scenario: A question can never depend on a description
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | description | Info | Read me |
+ | Test questions | truefalse | TF1 | First question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | Info | 1 | 0 |
+ | TF1 | 1 | 0 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ Then "be attempted" "link" in the "TF1" "list_item" should not be visible
+
+ @javascript
+ Scenario: When questions are reordered, the dependency icons are updated correctly
+ Given the following "activities" exist:
+ | activity | name | intro | course | idnumber | preferredbehaviour |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | TF1 | First question |
+ | Test questions | truefalse | TF2 | Second question |
+ | Test questions | truefalse | TF3 | Third question |
+ And quiz "Quiz 1" contains the following questions:
+ | question | page | requireprevious |
+ | TF1 | 1 | 0 |
+ | TF2 | 1 | 1 |
+ | TF3 | 1 | 1 |
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ When I move "Question 1" to "After Question 3" in the quiz by clicking the move icon
+ Then "Question 2 cannot be attempted until the previous question 1 has been completed • Click to change" "link" should be visible
+ And "No restriction on when question 3 can be attempted • Click to change" "link" should be visible
+ And "be attempted" "link" in the "TF2" "list_item" should not be visible
diff --git a/mod/quiz/tests/structure_test.php b/mod/quiz/tests/structure_test.php
index c9117c8c655..7ea030ba69d 100644
--- a/mod/quiz/tests/structure_test.php
+++ b/mod/quiz/tests/structure_test.php
@@ -50,7 +50,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance(array('course' => $course->id, 'questionsperpage' => 0,
- 'grade' => 100.0, 'sumgrades' => 2));
+ 'grade' => 100.0, 'sumgrades' => 2, 'preferredbehaviour' => 'immediatefeedback'));
$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
@@ -405,4 +405,36 @@ class mod_quiz_structure_testcase extends advanced_testcase {
quiz_add_quiz_question($numq->id, $quiz, $pagenumber);
}
}
+
+ /**
+ * Test updating pagebreaks in the quiz.
+ */
+ public function test_update_question_dependency() {
+ // Create a test quiz with 8 questions.
+ list($quiz, $cm, $course) = $this->prepare_quiz_data();
+ $this->add_eight_questions_to_the_quiz($quiz);
+ $quizobj = new quiz($quiz, $cm, $course);
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+ // Store the original order of slots, so we can assert what has changed.
+ $originalslotids = array();
+ foreach ($structure->get_slots() as $slot) {
+ $originalslotids[$slot->slot] = $slot->id;
+ }
+
+ // Test adding a dependency.
+ $slotid = $structure->get_slot_id_for_slot(3);
+ $structure->update_question_dependency($slotid, true);
+
+ // Having called update page break, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assertEquals(1, $structure->is_question_dependent_on_previous_slot(3));
+
+ // Test removing a dependency.
+ $structure->update_question_dependency($slotid, false);
+
+ // Having called update page break, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assertEquals(0, $structure->is_question_dependent_on_previous_slot(3));
+ }
}
diff --git a/mod/quiz/upgrade.txt b/mod/quiz/upgrade.txt
index 682124c733d..5db9d21c156 100644
--- a/mod/quiz/upgrade.txt
+++ b/mod/quiz/upgrade.txt
@@ -1,5 +1,17 @@
This files describes API changes in the quiz code.
+=== 2.9 ===
+
+* There have been changes in classes/output/edit_renderer.php for MDL-40990.
+ + Some methods use to take $structure & $question as the first two arguments.
+ They now take $structure & $slot number. If you need $question, you can get
+ it using $question = $structure->get_question_in_slot($slot);
+ + Some methods used to take $quiz & $question. They now take $structure & $slot
+ number. You can get $question as above. $quiz is $structure->get_quiz().
+ + initialise_editing_javascript has had some redundant arguments removed.
+ Hopefully, with these changes, we will have less need to make other changes in future.
+
+
=== 2.8 ===
* Classes that were defined in various lib files have been moved to the classes
diff --git a/mod/quiz/version.php b/mod/quiz/version.php
index a2459a78437..d9ddae57917 100644
--- a/mod/quiz/version.php
+++ b/mod/quiz/version.php
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2014111000; // The current module version (Date: YYYYMMDDXX).
-$plugin->requires = 2014110400; // Requires this Moodle version.
-$plugin->component = 'mod_quiz'; // Full name of the plugin (used for diagnostics).
+$plugin->version = 2015030500;
+$plugin->requires = 2014110400;
+$plugin->component = 'mod_quiz';
$plugin->cron = 60;
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js
index 4bb67ed08bc..df0a58317c3 100644
--- a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js
+++ b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js
@@ -11,7 +11,7 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
*/
// The CSS classes we use.
- var CSS = {
+var CSS = {
ACTIVITYINSTANCE : 'activityinstance',
AVAILABILITYINFODIV : 'div.availabilityinfo',
CONTENTWITHOUTLINK : 'contentwithoutlink',
@@ -24,7 +24,6 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
JOIN: 'page_join',
MODINDENTCOUNT : 'mod-indent-',
MODINDENTHUGE : 'mod-indent-huge',
- MODULEIDPREFIX : 'slot-',
PAGE: 'page',
SECTIONHIDDENCLASS : 'hidden',
SECTIONIDPREFIX : 'section-',
@@ -38,7 +37,6 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
ACTIONLINKTEXT : '.actionlinktext',
ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_maxmark',
ACTIVITYFORM : 'span.instancemaxmarkcontainer form',
- ACTIVITYICON : 'img.activityicon',
ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
ACTIVITYLI : 'li.activity',
@@ -58,7 +56,6 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
PAGELI : 'li.page',
SECTIONUL : 'ul.section',
SHOW : 'a.' + CSS.SHOW,
- SHOWHIDE : 'a.editing_showhide',
SLOTLI : 'li.slot',
SUMMARKS : '.mod_quiz_summarks'
},
@@ -276,8 +273,8 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
*/
initializer: function() {
M.mod_quiz.quizbase.register_module(this);
- BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
+ Y.delegate('click', this.handle_data_action, BODY, SELECTOR.DEPENDENCY_LINK, this);
},
/**
@@ -322,6 +319,11 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// The user is adding or removing a page break.
this.update_page_break(ev, node, activity, action);
break;
+ case 'adddependency':
+ case 'removedependency':
+ // The user is adding or removing a dependency between questions.
+ this.update_dependency(ev, node, activity, action);
+ break;
default:
// Nothing to do here!
break;
@@ -390,8 +392,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
- } else {
- window.location.reload(true);
}
});
@@ -414,8 +414,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
*/
edit_maxmark : function(ev, button, activity) {
// Get the element we're working on
- var activityid = Y.Moodle.mod_quiz.util.slot.getId(activity),
- instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
+ var instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
currentmaxmark = instancemaxmark.get('firstChild'),
oldmaxmark = currentmaxmark.get('data'),
@@ -425,7 +424,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
data = {
'class' : 'resource',
'field' : 'getmaxmark',
- 'id' : activityid
+ 'id' : Y.Moodle.mod_quiz.util.slot.getId(activity)
};
// Prevent the default actions.
@@ -575,6 +574,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} action The action, addpagebreak or removepagebreak.
* @chainable
*/
update_page_break: function(ev, button, activity, action) {
@@ -582,21 +582,16 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
ev.preventDefault();
var nextactivity = activity.next('li.activity.slot');
- var spinner = this.add_spinner(nextactivity),
- slotid = 0;
+ var spinner = this.add_spinner(nextactivity);
var value = action === 'removepagebreak' ? 1 : 2;
var data = {
'class': 'resource',
'field': 'updatepagebreak',
- 'id': slotid,
+ 'id': Y.Moodle.mod_quiz.util.slot.getId(nextactivity),
'value': value
};
- slotid = Y.Moodle.mod_quiz.util.slot.getId(nextactivity);
- if (slotid) {
- data.id = Number(slotid);
- }
this.send_request(data, spinner, function(response) {
if (response.slots) {
if (action === 'addpagebreak') {
@@ -606,8 +601,39 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
Y.Moodle.mod_quiz.util.page.remove(page, true);
}
this.reorganise_edit_page();
- } else {
- window.location.reload(true);
+ }
+ });
+
+ return this;
+ },
+
+ /**
+ * Updates a slot to either require the question in the previous slot to
+ * have been answered, or not,
+ *
+ * @protected
+ * @method update_page_break
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} action The action, adddependency or removedependency.
+ * @chainable
+ */
+ update_dependency: function(ev, button, activity, action) {
+ // Prevent the default button action.
+ ev.preventDefault();
+ var spinner = this.add_spinner(activity);
+
+ var data = {
+ 'class': 'resource',
+ 'field': 'updatedependency',
+ 'id': Y.Moodle.mod_quiz.util.slot.getId(activity),
+ 'value': action === 'adddependency' ? 1 : 0
+ };
+
+ this.send_request(data, spinner, function(response) {
+ if (response.hasOwnProperty('requireprevious')) {
+ Y.Moodle.mod_quiz.util.slot.updateDependencyIcon(activity, response.requireprevious);
}
});
@@ -624,6 +650,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
Y.Moodle.mod_quiz.util.slot.reorderSlots();
Y.Moodle.mod_quiz.util.slot.reorderPageBreaks();
Y.Moodle.mod_quiz.util.page.reorderPages();
+ Y.Moodle.mod_quiz.util.slot.updateAllDependencyIcons();
},
NAME : 'mod_quiz-resource-toolbox',
@@ -677,134 +704,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
*/
initializer : function() {
M.mod_quiz.quizbase.register_module(this);
-
- // Section Highlighting.
- Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
-
- // Section Visibility.
- Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
- },
-
- toggle_hide_section : function(e) {
- // Prevent the default button action.
- e.preventDefault();
-
- // Get the section we're working on.
- var section = e.target.ancestor(M.mod_quiz.format.get_section_selector(Y)),
- button = e.target.ancestor('a', true),
- hideicon = button.one('img'),
-
- // The value to submit
- value,
-
- // The text for strings and images. Also determines the icon to display.
- action,
- nextaction;
-
- if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
- section.addClass(CSS.SECTIONHIDDENCLASS);
- value = 0;
- action = 'hide';
- nextaction = 'show';
- } else {
- section.removeClass(CSS.SECTIONHIDDENCLASS);
- value = 1;
- action = 'show';
- nextaction = 'hide';
- }
-
- var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
- hideicon.setAttrs({
- 'alt' : newstring,
- 'src' : M.util.image_url('i/' + nextaction)
- });
- button.set('title', newstring);
-
- // Change the highlight status
- var data = {
- 'class' : 'section',
- 'field' : 'visible',
- 'id' : Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true)),
- 'value' : value
- };
-
- var lightbox = M.util.add_lightbox(Y, section);
- lightbox.show();
-
- this.send_request(data, lightbox, function(response) {
- var activities = section.all(SELECTOR.ACTIVITYLI);
- activities.each(function(node) {
- var button;
- if (node.one(SELECTOR.SHOW)) {
- button = node.one(SELECTOR.SHOW);
- } else {
- button = node.one(SELECTOR.HIDE);
- }
- var activityid = Y.Moodle.mod_quiz.util.slot.getId(node);
-
- // NOTE: resourcestotoggle is returned as a string instead
- // of a Number so we must cast our activityid to a String.
- if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
- M.mod_quiz.resource_toolbox.handle_resource_dim(button, node, action);
- }
- }, this);
- });
- },
-
- /**
- * Toggle highlighting the current section.
- *
- * @method toggle_highlight
- * @param {EventFacade} e
- */
- toggle_highlight : function(e) {
- // Prevent the default button action.
- e.preventDefault();
-
- // Get the section we're working on.
- var section = e.target.ancestor(M.mod_quiz.edit.get_section_selector(Y));
- var button = e.target.ancestor('a', true);
- var buttonicon = button.one('img');
-
- // Determine whether the marker is currently set.
- var togglestatus = section.hasClass('current');
- var value = 0;
-
- // Set the current highlighted item text.
- var old_string = M.util.get_string('markthistopic', 'moodle');
- Y.one(SELECTOR.PAGECONTENT)
- .all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
- .set('title', old_string);
- Y.one(SELECTOR.PAGECONTENT)
- .all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
- .set('alt', old_string)
- .set('src', M.util.image_url('i/marker'));
-
- // Remove the highlighting from all sections.
- Y.one(SELECTOR.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(Y))
- .removeClass('current');
-
- // Then add it if required to the selected section.
- if (!togglestatus) {
- section.addClass('current');
- value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
- var new_string = M.util.get_string('markedthistopic', 'moodle');
- button
- .set('title', new_string);
- buttonicon
- .set('alt', new_string)
- .set('src', M.util.image_url('i/marked'));
- }
-
- // Change the highlight status.
- var data = {
- 'class' : 'course',
- 'field' : 'marker',
- 'value' : value
- };
- var lightbox = M.util.add_lightbox(Y, section);
- lightbox.show();
- this.send_request(data, lightbox);
}
}, {
NAME : 'mod_quiz-section-toolbox',
@@ -814,9 +713,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
},
quizid : {
'value' : 0
- },
- format : {
- 'value' : 'topics'
}
}
});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js
index 98349eb22be..06dce00fc05 100644
--- a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js
+++ b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js
@@ -1,2 +1,2 @@
-YUI.add("moodle-mod_quiz-toolboxes",function(e,t){var n={ACTIVITYINSTANCE:"activityinstance",AVAILABILITYINFODIV:"div.availabilityinfo",CONTENTWITHOUTLINK:"contentwithoutlink",CONDITIONALHIDDEN:"conditionalhidden",DIMCLASS:"dimmed",DIMMEDTEXT:"dimmed_text",EDITINSTRUCTIONS:"editinstructions",EDITINGMAXMARK:"editor_displayed",HIDE:"hide",JOIN:"page_join",MODINDENTCOUNT:"mod-indent-",MODINDENTHUGE:"mod-indent-huge",MODULEIDPREFIX:"slot-",PAGE:"page",SECTIONHIDDENCLASS:"hidden",SECTIONIDPREFIX:"section-",SLOT:"slot",SHOW:"editing_show",TITLEEDITOR:"titleeditor"},r={ACTIONAREA:".actions",ACTIONLINKTEXT:".actionlinktext",ACTIVITYACTION:"a.cm-edit-action[data-action], a.editing_maxmark",ACTIVITYFORM:"span.instancemaxmarkcontainer form",ACTIVITYICON:"img.activityicon",ACTIVITYINSTANCE:"."+n.ACTIVITYINSTANCE,ACTIVITYLINK:"."+n.ACTIVITYINSTANCE+" > a",ACTIVITYLI:"li.activity",ACTIVITYMAXMARK:"input[name=maxmark]",COMMANDSPAN:".commands",CONTENTAFTERLINK:"div.contentafterlink",CONTENTWITHOUTLINK:"div.contentwithoutlink",EDITMAXMARK:"a.editing_maxmark",HIDE:"a.editing_hide",HIGHLIGHT:"a.editing_highlight",INSTANCENAME:"span.instancename",INSTANCEMAXMARK:"span.instancemaxmark",MODINDENTDIV:".mod-indent",MODINDENTOUTER:".mod-indent-outer",NUMQUESTIONS:".numberofquestions",PAGECONTENT:"div#page-content",PAGELI:"li.page",SECTIONUL:"ul.section",SHOW:"a."+n.SHOW,SHOWHIDE:"a.editing_showhide",SLOTLI:"li.slot",SUMMARKS:".mod_quiz_summarks"},i=e.one(document.body);M.mod_quiz=M.mod_quiz||{};var s=function(){s.superclass.constructor.apply(this,arguments)};e.extend(s,e.Base,{send_request:function(t,n,i,s){t||(t={});var o=this.get("config").pageparams,u;for(u in o)t[u]=o[u];t.sesskey=M.cfg.sesskey,t.courseid=this.get("courseid"),t.quizid=this.get("quizid");var a=M.cfg.wwwroot+this.get("ajaxurl"),f=[],l={method:"POST",data:t,on:{success:function(t,s){try{f=e.JSON.parse(s.responseText),f.error&&new M.core.ajaxException(f)}catch(o){}f.hasOwnProperty("newsummarks")&&e.one(r.SUMMARKS).setHTML(f.newsummarks),f.hasOwnProperty("newnumquestions")&&e.one(r.NUMQUESTIONS).setHTML(M.util.get_string("numquestionsx","quiz",f.newnumquestions)),i&&e.bind(i,this,f)(),n&&window.setTimeout(function(){n.hide()},400)},failure:function(e,t){n&&n.hide(),new M.core.ajaxException(t)}},context:this};if(s)for(u in s)l[u]=s[u];return n&&n.show(),e.io(a,l),this}},{NAME:"mod_quiz-toolbox",ATTRS:{courseid:{value:0},quizid:{value:0},ajaxurl:{value:null},config:{value:{}}}});var o=function(){o.superclass.constructor.apply(this,arguments)};e.extend(o,s,{editmaxmarkevents:[],NODE_PAGE:1,NODE_SLOT:2,NODE_JOIN:3,initializer:function(){M.mod_quiz.quizbase.register_module(this),i.delegate("key",this.handle_data_action,"down:enter",r.ACTIVITYACTION,this),e.delegate("click",this.handle_data_action,i,r.ACTIVITYACTION,this)},handle_data_action:function(e){var t=e.target;t.test("a")||(t=t.ancestor(r.ACTIVITYACTION));var n=t.getData("action"),i=t.ancestor(r.ACTIVITYLI);if(!t.test("a")||!n||!i)return;switch(n){case"editmaxmark":this.edit_maxmark(e,t,i,n);break;case"delete":this.delete_with_confirmation(e,t,i,n);break;case"addpagebreak":case"removepagebreak":this.update_page_break(e,t,i,n);break;default:}},add_spinner:function(t){var n=t.one(r.ACTIONAREA);return n?M.util.add_spinner(e,n):null},delete_with_confirmation:function(t,n,r){t.preventDefault();var i=r,s="",o=M.util.get_string("pluginname","qtype_"+i.getAttribute("class").match(/qtype_([^\s]*)/)[1]);s=M.util.get_string("confirmremovequestion","quiz",o);var u=new M.core.confirm({question:s,modal:!0});return u.on("complete-yes",function(){var t=this.add_spinner(i),n={"class":"resource",action:"DELETE",id:e.Moodle.mod_quiz.util.slot.getId(i)};this.send_request(n,t,function(t){t.deleted?(e.Moodle.mod_quiz.util.slot.remove(i),this.reorganise_edit_page(),M.core.actionmenu&&M.core.actionmenu.instance&&M.core.actionmenu.instance.hideMenu()):window.location.reload(!0)})},this),this},edit_maxmark:function(t,i,s){var o=e.Moodle.mod_quiz.util.slot.getId(s),u=s.one(r.INSTANCEMAXMARK),a=s.one(r.ACTIVITYINSTANCE),f=u.get("firstChild"),l=f.get("data"),c=l,h,p=u,d={"class":"resource",field:"getmaxmark",id:o};t.preventDefault(),this.send_request(d,null,function(t){M.core.actionmenu&&M.core.actionmenu.instance&&M.core.actionmenu.instance.hideMenu(),t.instancemaxmark&&(c=t.instancemaxmark);var r=e.Node.create('